##// END OF EJS Templates
Merge spelling fixes
Bryan O'Sullivan -
r17537:31f32a96 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,1293 +1,1293
1 1 ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
2 2
3 3 ;; Copyright (C) 2005, 2006 Bryan O'Sullivan
4 4
5 5 ;; Author: Bryan O'Sullivan <bos@serpentine.com>
6 6
7 7 ;; mercurial.el is free software; you can redistribute it and/or
8 8 ;; modify it under the terms of the GNU General Public License version
9 9 ;; 2 or any later version.
10 10
11 11 ;; mercurial.el is distributed in the hope that it will be useful, but
12 12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 14 ;; General Public License for more details.
15 15
16 16 ;; You should have received a copy of the GNU General Public License
17 17 ;; along with mercurial.el, GNU Emacs, or XEmacs; see the file COPYING
18 18 ;; (`C-h C-l'). If not, see <http://www.gnu.org/licenses/>.
19 19
20 20 ;;; Commentary:
21 21
22 22 ;; mercurial.el builds upon Emacs's VC mode to provide flexible
23 23 ;; integration with the Mercurial distributed SCM tool.
24 24
25 25 ;; To get going as quickly as possible, load mercurial.el into Emacs and
26 26 ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
27 27 ;; usage overview.
28 28
29 29 ;; Much of the inspiration for mercurial.el comes from Rajesh
30 30 ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
31 31 ;; job for the commercial Perforce SCM product. In fact, substantial
32 32 ;; chunks of code are adapted from p4.el.
33 33
34 34 ;; This code has been developed under XEmacs 21.5, and may not work as
35 35 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
36 36 ;; enhance the portability of this code, fix bugs, and add features
37 37 ;; are most welcome.
38 38
39 39 ;; As of version 22.3, GNU Emacs's VC mode has direct support for
40 40 ;; Mercurial, so this package may not prove as useful there.
41 41
42 42 ;; Please send problem reports and suggestions to bos@serpentine.com.
43 43
44 44
45 45 ;;; Code:
46 46
47 47 (eval-when-compile (require 'cl))
48 48 (require 'diff-mode)
49 49 (require 'easymenu)
50 50 (require 'executable)
51 51 (require 'vc)
52 52
53 53 (defmacro hg-feature-cond (&rest clauses)
54 54 "Test CLAUSES for feature at compile time.
55 55 Each clause is (FEATURE BODY...)."
56 56 (dolist (x clauses)
57 57 (let ((feature (car x))
58 58 (body (cdr x)))
59 59 (when (or (eq feature t)
60 60 (featurep feature))
61 61 (return (cons 'progn body))))))
62 62
63 63
64 64 ;;; XEmacs has view-less, while GNU Emacs has view. Joy.
65 65
66 66 (hg-feature-cond
67 67 (xemacs (require 'view-less))
68 68 (t (require 'view)))
69 69
70 70
71 71 ;;; Variables accessible through the custom system.
72 72
73 73 (defgroup mercurial nil
74 74 "Mercurial distributed SCM."
75 75 :group 'tools)
76 76
77 77 (defcustom hg-binary
78 78 (or (executable-find "hg")
79 79 (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
80 80 (when (file-executable-p path)
81 81 (return path))))
82 82 "The path to Mercurial's hg executable."
83 83 :type '(file :must-match t)
84 84 :group 'mercurial)
85 85
86 86 (defcustom hg-mode-hook nil
87 87 "Hook run when a buffer enters hg-mode."
88 88 :type 'sexp
89 89 :group 'mercurial)
90 90
91 91 (defcustom hg-commit-mode-hook nil
92 92 "Hook run when a buffer is created to prepare a commit."
93 93 :type 'sexp
94 94 :group 'mercurial)
95 95
96 96 (defcustom hg-pre-commit-hook nil
97 97 "Hook run before a commit is performed.
98 98 If you want to prevent the commit from proceeding, raise an error."
99 99 :type 'sexp
100 100 :group 'mercurial)
101 101
102 102 (defcustom hg-log-mode-hook nil
103 103 "Hook run after a buffer is filled with log information."
104 104 :type 'sexp
105 105 :group 'mercurial)
106 106
107 107 (defcustom hg-global-prefix "\C-ch"
108 108 "The global prefix for Mercurial keymap bindings."
109 109 :type 'sexp
110 110 :group 'mercurial)
111 111
112 112 (defcustom hg-commit-allow-empty-message nil
113 113 "Whether to allow changes to be committed with empty descriptions."
114 114 :type 'boolean
115 115 :group 'mercurial)
116 116
117 117 (defcustom hg-commit-allow-empty-file-list nil
118 118 "Whether to allow changes to be committed without any modified files."
119 119 :type 'boolean
120 120 :group 'mercurial)
121 121
122 122 (defcustom hg-rev-completion-limit 100
123 123 "The maximum number of revisions that hg-read-rev will offer to complete.
124 124 This affects memory usage and performance when prompting for revisions
125 125 in a repository with a lot of history."
126 126 :type 'integer
127 127 :group 'mercurial)
128 128
129 129 (defcustom hg-log-limit 50
130 130 "The maximum number of revisions that hg-log will display."
131 131 :type 'integer
132 132 :group 'mercurial)
133 133
134 134 (defcustom hg-update-modeline t
135 135 "Whether to update the modeline with the status of a file after every save.
136 136 Set this to nil on platforms with poor process management, such as Windows."
137 137 :type 'boolean
138 138 :group 'mercurial)
139 139
140 140 (defcustom hg-incoming-repository "default"
141 141 "The repository from which changes are pulled from by default.
142 142 This should be a symbolic repository name, since it is used for all
143 143 repository-related commands."
144 144 :type 'string
145 145 :group 'mercurial)
146 146
147 147 (defcustom hg-outgoing-repository ""
148 148 "The repository to which changes are pushed to by default.
149 149 This should be a symbolic repository name, since it is used for all
150 150 repository-related commands."
151 151 :type 'string
152 152 :group 'mercurial)
153 153
154 154
155 155 ;;; Other variables.
156 156
157 157 (defvar hg-mode nil
158 158 "Is this file managed by Mercurial?")
159 159 (make-variable-buffer-local 'hg-mode)
160 160 (put 'hg-mode 'permanent-local t)
161 161
162 162 (defvar hg-status nil)
163 163 (make-variable-buffer-local 'hg-status)
164 164 (put 'hg-status 'permanent-local t)
165 165
166 166 (defvar hg-prev-buffer nil)
167 167 (make-variable-buffer-local 'hg-prev-buffer)
168 168 (put 'hg-prev-buffer 'permanent-local t)
169 169
170 170 (defvar hg-root nil)
171 171 (make-variable-buffer-local 'hg-root)
172 172 (put 'hg-root 'permanent-local t)
173 173
174 174 (defvar hg-view-mode nil)
175 175 (make-variable-buffer-local 'hg-view-mode)
176 176 (put 'hg-view-mode 'permanent-local t)
177 177
178 178 (defvar hg-view-file-name nil)
179 179 (make-variable-buffer-local 'hg-view-file-name)
180 180 (put 'hg-view-file-name 'permanent-local t)
181 181
182 182 (defvar hg-output-buffer-name "*Hg*"
183 183 "The name to use for Mercurial output buffers.")
184 184
185 185 (defvar hg-file-history nil)
186 186 (defvar hg-repo-history nil)
187 187 (defvar hg-rev-history nil)
188 188 (defvar hg-repo-completion-table nil) ; shut up warnings
189 189
190 190
191 191 ;;; Random constants.
192 192
193 193 (defconst hg-commit-message-start
194 194 "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
195 195
196 196 (defconst hg-commit-message-end
197 197 "--- Files in bold will be committed. Click to toggle selection. ---\n")
198 198
199 199 (defconst hg-state-alist
200 200 '((?M . modified)
201 201 (?A . added)
202 202 (?R . removed)
203 203 (?! . deleted)
204 204 (?C . normal)
205 205 (?I . ignored)
206 206 (?? . nil)))
207 207
208 208 ;;; hg-mode keymap.
209 209
210 210 (defvar hg-prefix-map
211 211 (let ((map (make-sparse-keymap)))
212 212 (hg-feature-cond (xemacs (set-keymap-name map 'hg-prefix-map))) ; XEmacs
213 213 (set-keymap-parent map vc-prefix-map)
214 214 (define-key map "=" 'hg-diff)
215 215 (define-key map "c" 'hg-undo)
216 216 (define-key map "g" 'hg-annotate)
217 217 (define-key map "i" 'hg-add)
218 218 (define-key map "l" 'hg-log)
219 219 (define-key map "n" 'hg-commit-start)
220 220 ;; (define-key map "r" 'hg-update)
221 221 (define-key map "u" 'hg-revert-buffer)
222 222 (define-key map "~" 'hg-version-other-window)
223 223 map)
224 224 "This keymap overrides some default vc-mode bindings.")
225 225
226 226 (defvar hg-mode-map
227 227 (let ((map (make-sparse-keymap)))
228 228 (define-key map "\C-xv" hg-prefix-map)
229 229 map))
230 230
231 231 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
232 232
233 233
234 234 ;;; Global keymap.
235 235
236 236 (defvar hg-global-map
237 237 (let ((map (make-sparse-keymap)))
238 238 (define-key map "," 'hg-incoming)
239 239 (define-key map "." 'hg-outgoing)
240 240 (define-key map "<" 'hg-pull)
241 241 (define-key map "=" 'hg-diff-repo)
242 242 (define-key map ">" 'hg-push)
243 243 (define-key map "?" 'hg-help-overview)
244 244 (define-key map "A" 'hg-addremove)
245 245 (define-key map "U" 'hg-revert)
246 246 (define-key map "a" 'hg-add)
247 247 (define-key map "c" 'hg-commit-start)
248 248 (define-key map "f" 'hg-forget)
249 249 (define-key map "h" 'hg-help-overview)
250 250 (define-key map "i" 'hg-init)
251 251 (define-key map "l" 'hg-log-repo)
252 252 (define-key map "r" 'hg-root)
253 253 (define-key map "s" 'hg-status)
254 254 (define-key map "u" 'hg-update)
255 255 map))
256 256
257 257 (global-set-key hg-global-prefix hg-global-map)
258 258
259 259 ;;; View mode keymap.
260 260
261 261 (defvar hg-view-mode-map
262 262 (let ((map (make-sparse-keymap)))
263 263 (hg-feature-cond (xemacs (set-keymap-name map 'hg-view-mode-map))) ; XEmacs
264 264 (define-key map (hg-feature-cond (xemacs [button2])
265 265 (t [mouse-2]))
266 266 'hg-buffer-mouse-clicked)
267 267 map))
268 268
269 269 (add-minor-mode 'hg-view-mode "" hg-view-mode-map)
270 270
271 271
272 272 ;;; Commit mode keymaps.
273 273
274 274 (defvar hg-commit-mode-map
275 275 (let ((map (make-sparse-keymap)))
276 276 (define-key map "\C-c\C-c" 'hg-commit-finish)
277 277 (define-key map "\C-c\C-k" 'hg-commit-kill)
278 278 (define-key map "\C-xv=" 'hg-diff-repo)
279 279 map))
280 280
281 281 (defvar hg-commit-mode-file-map
282 282 (let ((map (make-sparse-keymap)))
283 283 (define-key map (hg-feature-cond (xemacs [button2])
284 284 (t [mouse-2]))
285 285 'hg-commit-mouse-clicked)
286 286 (define-key map " " 'hg-commit-toggle-file)
287 287 (define-key map "\r" 'hg-commit-toggle-file)
288 288 map))
289 289
290 290
291 291 ;;; Convenience functions.
292 292
293 293 (defsubst hg-binary ()
294 294 (if hg-binary
295 295 hg-binary
296 296 (error "No `hg' executable found!")))
297 297
298 298 (defsubst hg-replace-in-string (str regexp newtext &optional literal)
299 299 "Replace all matches in STR for REGEXP with NEWTEXT string.
300 300 Return the new string. Optional LITERAL non-nil means do a literal
301 301 replacement.
302 302
303 303 This function bridges yet another pointless impedance gap between
304 304 XEmacs and GNU Emacs."
305 305 (hg-feature-cond
306 306 (xemacs (replace-in-string str regexp newtext literal))
307 307 (t (replace-regexp-in-string regexp newtext str nil literal))))
308 308
309 309 (defsubst hg-strip (str)
310 310 "Strip leading and trailing blank lines from a string."
311 311 (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
312 312 "\\`[ \t\r\n]*[\r\n]" ""))
313 313
314 314 (defsubst hg-chomp (str)
315 315 "Strip trailing newlines from a string."
316 316 (hg-replace-in-string str "[\r\n]+\\'" ""))
317 317
318 318 (defun hg-run-command (command &rest args)
319 319 "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
320 320 The list ARGS contains a list of arguments to pass to the command."
321 321 (let* (exit-code
322 322 (output
323 323 (with-output-to-string
324 324 (with-current-buffer
325 325 standard-output
326 326 (setq exit-code
327 327 (apply 'call-process command nil t nil args))))))
328 328 (cons exit-code output)))
329 329
330 330 (defun hg-run (command &rest args)
331 331 "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
332 332 (apply 'hg-run-command (hg-binary) command args))
333 333
334 334 (defun hg-run0 (command &rest args)
335 335 "Run the Mercurial command COMMAND, returning its output.
336 336 If the command does not exit with a zero status code, raise an error."
337 337 (let ((res (apply 'hg-run-command (hg-binary) command args)))
338 338 (if (not (eq (car res) 0))
339 339 (error "Mercurial command failed %s - exit code %s"
340 340 (cons command args)
341 341 (car res))
342 342 (cdr res))))
343 343
344 344 (defmacro hg-do-across-repo (path &rest body)
345 345 (let ((root-name (make-symbol "root-"))
346 346 (buf-name (make-symbol "buf-")))
347 347 `(let ((,root-name (hg-root ,path)))
348 348 (save-excursion
349 349 (dolist (,buf-name (buffer-list))
350 350 (set-buffer ,buf-name)
351 351 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
352 352 ,@body))))))
353 353
354 354 (put 'hg-do-across-repo 'lisp-indent-function 1)
355 355
356 356 (defun hg-sync-buffers (path)
357 357 "Sync buffers visiting PATH with their on-disk copies.
358 358 If PATH is not being visited, but is under the repository root, sync
359 359 all buffers visiting files in the repository."
360 360 (let ((buf (find-buffer-visiting path)))
361 361 (if buf
362 362 (with-current-buffer buf
363 363 (vc-buffer-sync))
364 364 (hg-do-across-repo path
365 365 (vc-buffer-sync)))))
366 366
367 367 (defun hg-buffer-commands (pnt)
368 368 "Use the properties of a character to do something sensible."
369 369 (interactive "d")
370 370 (let ((rev (get-char-property pnt 'rev))
371 371 (file (get-char-property pnt 'file)))
372 372 (cond
373 373 (file
374 374 (find-file-other-window file))
375 375 (rev
376 376 (hg-diff hg-view-file-name rev rev))
377 377 ((message "I don't know how to do that yet")))))
378 378
379 379 (defsubst hg-event-point (event)
380 380 "Return the character position of the mouse event EVENT."
381 381 (hg-feature-cond (xemacs (event-point event))
382 382 (t (posn-point (event-start event)))))
383 383
384 384 (defsubst hg-event-window (event)
385 385 "Return the window over which mouse event EVENT occurred."
386 386 (hg-feature-cond (xemacs (event-window event))
387 387 (t (posn-window (event-start event)))))
388 388
389 389 (defun hg-buffer-mouse-clicked (event)
390 390 "Translate the mouse clicks in a HG log buffer to character events.
391 391 These are then handed off to `hg-buffer-commands'.
392 392
393 393 Handle frickin' frackin' gratuitous event-related incompatibilities."
394 394 (interactive "e")
395 395 (select-window (hg-event-window event))
396 396 (hg-buffer-commands (hg-event-point event)))
397 397
398 398 (defsubst hg-abbrev-file-name (file)
399 399 "Portable wrapper around abbreviate-file-name."
400 400 (hg-feature-cond (xemacs (abbreviate-file-name file t))
401 401 (t (abbreviate-file-name file))))
402 402
403 403 (defun hg-read-file-name (&optional prompt default)
404 404 "Read a file or directory name, or a pattern, to use with a command."
405 405 (save-excursion
406 406 (while hg-prev-buffer
407 407 (set-buffer hg-prev-buffer))
408 408 (let ((path (or default
409 409 (buffer-file-name)
410 410 (expand-file-name default-directory))))
411 411 (if (or (not path) current-prefix-arg)
412 412 (expand-file-name
413 413 (eval (list* 'read-file-name
414 414 (format "File, directory or pattern%s: "
415 415 (or prompt ""))
416 416 (and path (file-name-directory path))
417 417 nil nil
418 418 (and path (file-name-nondirectory path))
419 419 (hg-feature-cond
420 420 (xemacs (cons (quote 'hg-file-history) nil))
421 421 (t nil)))))
422 422 path))))
423 423
424 424 (defun hg-read-number (&optional prompt default)
425 425 "Read a integer value."
426 426 (save-excursion
427 427 (if (or (not default) current-prefix-arg)
428 428 (string-to-number
429 429 (eval (list* 'read-string
430 430 (or prompt "")
431 431 (if default (cons (format "%d" default) nil) nil))))
432 432 default)))
433 433
434 434 (defun hg-read-config ()
435 435 "Return an alist of (key . value) pairs of Mercurial config data.
436 436 Each key is of the form (section . name)."
437 437 (let (items)
438 438 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
439 439 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
440 440 (let* ((left (substring line (match-beginning 1) (match-end 1)))
441 441 (right (substring line (match-beginning 2) (match-end 2)))
442 442 (key (split-string left "\\."))
443 443 (value (hg-replace-in-string right "\\\\n" "\n" t)))
444 444 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
445 445
446 446 (defun hg-config-section (section config)
447 447 "Return an alist of (name . value) pairs for SECTION of CONFIG."
448 448 (let (items)
449 449 (dolist (item config items)
450 450 (when (equal (caar item) section)
451 451 (setq items (cons (cons (cdar item) (cdr item)) items))))))
452 452
453 453 (defun hg-string-starts-with (sub str)
454 454 "Indicate whether string STR starts with the substring or character SUB."
455 455 (if (not (stringp sub))
456 456 (and (> (length str) 0) (equal (elt str 0) sub))
457 457 (let ((sub-len (length sub)))
458 458 (and (<= sub-len (length str))
459 459 (string= sub (substring str 0 sub-len))))))
460 460
461 461 (defun hg-complete-repo (string predicate all)
462 462 "Attempt to complete a repository name.
463 463 We complete on either symbolic names from Mercurial's config or real
464 directory names from the file system. We do not penalise URLs."
464 directory names from the file system. We do not penalize URLs."
465 465 (or (if all
466 466 (all-completions string hg-repo-completion-table predicate)
467 467 (try-completion string hg-repo-completion-table predicate))
468 468 (let* ((str (expand-file-name string))
469 469 (dir (file-name-directory str))
470 470 (file (file-name-nondirectory str)))
471 471 (if all
472 472 (let (completions)
473 473 (dolist (name (delete "./" (file-name-all-completions file dir))
474 474 completions)
475 475 (let ((path (concat dir name)))
476 476 (when (file-directory-p path)
477 477 (setq completions (cons name completions))))))
478 478 (let ((comp (file-name-completion file dir)))
479 479 (if comp
480 480 (hg-abbrev-file-name (concat dir comp))))))))
481 481
482 482 (defun hg-read-repo-name (&optional prompt initial-contents default)
483 483 "Read the location of a repository."
484 484 (save-excursion
485 485 (while hg-prev-buffer
486 486 (set-buffer hg-prev-buffer))
487 487 (let (hg-repo-completion-table)
488 488 (if current-prefix-arg
489 489 (progn
490 490 (dolist (path (hg-config-section "paths" (hg-read-config)))
491 491 (setq hg-repo-completion-table
492 492 (cons (cons (car path) t) hg-repo-completion-table))
493 493 (unless (hg-string-starts-with (hg-feature-cond
494 494 (xemacs directory-sep-char)
495 495 (t ?/))
496 496 (cdr path))
497 497 (setq hg-repo-completion-table
498 498 (cons (cons (cdr path) t) hg-repo-completion-table))))
499 499 (completing-read (format "Repository%s: " (or prompt ""))
500 500 'hg-complete-repo
501 501 nil
502 502 nil
503 503 initial-contents
504 504 'hg-repo-history
505 505 default))
506 506 default))))
507 507
508 508 (defun hg-read-rev (&optional prompt default)
509 509 "Read a revision or tag, offering completions."
510 510 (save-excursion
511 511 (while hg-prev-buffer
512 512 (set-buffer hg-prev-buffer))
513 513 (let ((rev (or default "tip")))
514 514 (if current-prefix-arg
515 515 (let ((revs (split-string
516 516 (hg-chomp
517 517 (hg-run0 "-q" "log" "-l"
518 518 (format "%d" hg-rev-completion-limit)))
519 519 "[\n:]")))
520 520 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
521 521 (setq revs (cons (car (split-string line "\\s-")) revs)))
522 522 (completing-read (format "Revision%s (%s): "
523 523 (or prompt "")
524 524 (or default "tip"))
525 525 (mapcar (lambda (x) (cons x x)) revs)
526 526 nil
527 527 nil
528 528 nil
529 529 'hg-rev-history
530 530 (or default "tip")))
531 531 rev))))
532 532
533 533 (defun hg-parents-for-mode-line (root)
534 534 "Format the parents of the working directory for the mode line."
535 535 (let ((parents (split-string (hg-chomp
536 536 (hg-run0 "--cwd" root "parents" "--template"
537 537 "{rev}\n")) "\n")))
538 538 (mapconcat 'identity parents "+")))
539 539
540 540 (defun hg-buffers-visiting-repo (&optional path)
541 541 "Return a list of buffers visiting the repository containing PATH."
542 542 (let ((root-name (hg-root (or path (buffer-file-name))))
543 543 bufs)
544 544 (save-excursion
545 545 (dolist (buf (buffer-list) bufs)
546 546 (set-buffer buf)
547 547 (let ((name (buffer-file-name)))
548 548 (when (and hg-status name (equal (hg-root name) root-name))
549 549 (setq bufs (cons buf bufs))))))))
550 550
551 551 (defun hg-update-mode-lines (path)
552 552 "Update the mode lines of all buffers visiting the same repository as PATH."
553 553 (let* ((root (hg-root path))
554 554 (parents (hg-parents-for-mode-line root)))
555 555 (save-excursion
556 556 (dolist (info (hg-path-status
557 557 root
558 558 (mapcar
559 559 (function
560 560 (lambda (buf)
561 561 (substring (buffer-file-name buf) (length root))))
562 562 (hg-buffers-visiting-repo root))))
563 563 (let* ((name (car info))
564 564 (status (cdr info))
565 565 (buf (find-buffer-visiting (concat root name))))
566 566 (when buf
567 567 (set-buffer buf)
568 568 (hg-mode-line-internal status parents)))))))
569 569
570 570
571 571 ;;; View mode bits.
572 572
573 573 (defun hg-exit-view-mode (buf)
574 574 "Exit from hg-view-mode.
575 575 We delete the current window if entering hg-view-mode split the
576 576 current frame."
577 577 (when (and (eq buf (current-buffer))
578 578 (> (length (window-list)) 1))
579 579 (delete-window))
580 580 (when (buffer-live-p buf)
581 581 (kill-buffer buf)))
582 582
583 583 (defun hg-view-mode (prev-buffer &optional file-name)
584 584 (goto-char (point-min))
585 585 (set-buffer-modified-p nil)
586 586 (toggle-read-only t)
587 587 (hg-feature-cond (xemacs (view-minor-mode prev-buffer 'hg-exit-view-mode))
588 588 (t (view-mode-enter nil 'hg-exit-view-mode)))
589 589 (setq hg-view-mode t)
590 590 (setq truncate-lines t)
591 591 (when file-name
592 592 (setq hg-view-file-name
593 593 (hg-abbrev-file-name file-name))))
594 594
595 595 (defun hg-file-status (file)
596 596 "Return status of FILE, or nil if FILE does not exist or is unmanaged."
597 597 (let* ((s (hg-run "status" file))
598 598 (exit (car s))
599 599 (output (cdr s)))
600 600 (if (= exit 0)
601 601 (let ((state (and (>= (length output) 2)
602 602 (= (aref output 1) ? )
603 603 (assq (aref output 0) hg-state-alist))))
604 604 (if state
605 605 (cdr state)
606 606 'normal)))))
607 607
608 608 (defun hg-path-status (root paths)
609 609 "Return status of PATHS in repo ROOT as an alist.
610 610 Each entry is a pair (FILE-NAME . STATUS)."
611 611 (let ((s (apply 'hg-run "--cwd" root "status" "-marduc" paths))
612 612 result)
613 613 (dolist (entry (split-string (hg-chomp (cdr s)) "\n") (nreverse result))
614 614 (let (state name)
615 615 (cond ((= (aref entry 1) ? )
616 616 (setq state (assq (aref entry 0) hg-state-alist)
617 617 name (substring entry 2)))
618 618 ((string-match "\\(.*\\): " entry)
619 619 (setq name (match-string 1 entry))))
620 620 (setq result (cons (cons name state) result))))))
621 621
622 622 (defmacro hg-view-output (args &rest body)
623 623 "Execute BODY in a clean buffer, then quickly display that buffer.
624 624 If the buffer contains one line, its contents are displayed in the
625 625 minibuffer. Otherwise, the buffer is displayed in view-mode.
626 626 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
627 627 the name of the buffer to create, and FILE is the name of the file
628 628 being viewed."
629 629 (let ((prev-buf (make-symbol "prev-buf-"))
630 630 (v-b-name (car args))
631 631 (v-m-rest (cdr args)))
632 632 `(let ((view-buf-name ,v-b-name)
633 633 (,prev-buf (current-buffer)))
634 634 (get-buffer-create view-buf-name)
635 635 (kill-buffer view-buf-name)
636 636 (get-buffer-create view-buf-name)
637 637 (set-buffer view-buf-name)
638 638 (save-excursion
639 639 ,@body)
640 640 (case (count-lines (point-min) (point-max))
641 641 ((0)
642 642 (kill-buffer view-buf-name)
643 643 (message "(No output)"))
644 644 ((1)
645 645 (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
646 646 (kill-buffer view-buf-name)
647 647 (message "%s" msg)))
648 648 (t
649 649 (pop-to-buffer view-buf-name)
650 650 (setq hg-prev-buffer ,prev-buf)
651 651 (hg-view-mode ,prev-buf ,@v-m-rest))))))
652 652
653 653 (put 'hg-view-output 'lisp-indent-function 1)
654 654
655 655 ;;; Context save and restore across revert and other operations.
656 656
657 657 (defun hg-position-context (pos)
658 658 "Return information to help find the given position again."
659 659 (let* ((end (min (point-max) (+ pos 98))))
660 660 (list pos
661 661 (buffer-substring (max (point-min) (- pos 2)) end)
662 662 (- end pos))))
663 663
664 664 (defun hg-buffer-context ()
665 665 "Return information to help restore a user's editing context.
666 666 This is useful across reverts and merges, where a context is likely
667 667 to have moved a little, but not really changed."
668 668 (let ((point-context (hg-position-context (point)))
669 669 (mark-context (let ((mark (mark-marker)))
670 670 (and mark
671 671 ;; make sure active mark
672 672 (marker-buffer mark)
673 673 (marker-position mark)
674 674 (hg-position-context mark)))))
675 675 (list point-context mark-context)))
676 676
677 677 (defun hg-find-context (ctx)
678 678 "Attempt to find a context in the given buffer.
679 679 Always returns a valid, hopefully sane, position."
680 680 (let ((pos (nth 0 ctx))
681 681 (str (nth 1 ctx))
682 682 (fixup (nth 2 ctx)))
683 683 (save-excursion
684 684 (goto-char (max (point-min) (- pos 15000)))
685 685 (if (and (not (equal str ""))
686 686 (search-forward str nil t))
687 687 (- (point) fixup)
688 688 (max pos (point-min))))))
689 689
690 690 (defun hg-restore-context (ctx)
691 691 "Attempt to restore the user's editing context."
692 692 (let ((point-context (nth 0 ctx))
693 693 (mark-context (nth 1 ctx)))
694 694 (goto-char (hg-find-context point-context))
695 695 (when mark-context
696 696 (set-mark (hg-find-context mark-context)))))
697 697
698 698
699 699 ;;; Hooks.
700 700
701 701 (defun hg-mode-line-internal (status parents)
702 702 (setq hg-status status
703 703 hg-mode (and status (concat " Hg:"
704 704 parents
705 705 (cdr (assq status
706 706 '((normal . "")
707 707 (removed . "r")
708 708 (added . "a")
709 709 (deleted . "!")
710 710 (modified . "m"))))))))
711 711
712 712 (defun hg-mode-line (&optional force)
713 713 "Update the modeline with the current status of a file.
714 714 An update occurs if optional argument FORCE is non-nil,
715 715 hg-update-modeline is non-nil, or we have not yet checked the state of
716 716 the file."
717 717 (let ((root (hg-root)))
718 718 (when (and root (or force hg-update-modeline (not hg-mode)))
719 719 (let ((status (hg-file-status buffer-file-name))
720 720 (parents (hg-parents-for-mode-line root)))
721 721 (hg-mode-line-internal status parents)
722 722 status))))
723 723
724 724 (defun hg-mode (&optional toggle)
725 725 "Minor mode for Mercurial distributed SCM integration.
726 726
727 727 The Mercurial mode user interface is based on that of VC mode, so if
728 728 you're already familiar with VC, the same keybindings and functions
729 729 will generally work.
730 730
731 731 Below is a list of many common SCM tasks. In the list, `G/L\'
732 732 indicates whether a key binding is global (G) to a repository or
733 733 local (L) to a file. Many commands take a prefix argument.
734 734
735 735 SCM Task G/L Key Binding Command Name
736 736 -------- --- ----------- ------------
737 737 Help overview (what you are reading) G C-c h h hg-help-overview
738 738
739 739 Tell Mercurial to manage a file G C-c h a hg-add
740 740 Commit changes to current file only L C-x v n hg-commit-start
741 741 Undo changes to file since commit L C-x v u hg-revert-buffer
742 742
743 743 Diff file vs last checkin L C-x v = hg-diff
744 744
745 745 View file change history L C-x v l hg-log
746 746 View annotated file L C-x v a hg-annotate
747 747
748 748 Diff repo vs last checkin G C-c h = hg-diff-repo
749 749 View status of files in repo G C-c h s hg-status
750 750 Commit all changes G C-c h c hg-commit-start
751 751
752 752 Undo all changes since last commit G C-c h U hg-revert
753 753 View repo change history G C-c h l hg-log-repo
754 754
755 755 See changes that can be pulled G C-c h , hg-incoming
756 756 Pull changes G C-c h < hg-pull
757 757 Update working directory after pull G C-c h u hg-update
758 758 See changes that can be pushed G C-c h . hg-outgoing
759 759 Push changes G C-c h > hg-push"
760 760 (unless vc-make-backup-files
761 761 (set (make-local-variable 'backup-inhibited) t))
762 762 (run-hooks 'hg-mode-hook))
763 763
764 764 (defun hg-find-file-hook ()
765 765 (ignore-errors
766 766 (when (hg-mode-line)
767 767 (hg-mode))))
768 768
769 769 (add-hook 'find-file-hooks 'hg-find-file-hook)
770 770
771 771 (defun hg-after-save-hook ()
772 772 (ignore-errors
773 773 (let ((old-status hg-status))
774 774 (hg-mode-line)
775 775 (if (and (not old-status) hg-status)
776 776 (hg-mode)))))
777 777
778 778 (add-hook 'after-save-hook 'hg-after-save-hook)
779 779
780 780
781 781 ;;; User interface functions.
782 782
783 783 (defun hg-help-overview ()
784 784 "This is an overview of the Mercurial SCM mode for Emacs.
785 785
786 786 You can find the source code, license (GPLv2+), and credits for this
787 787 code by typing `M-x find-library mercurial RET'."
788 788 (interactive)
789 789 (hg-view-output ("Mercurial Help Overview")
790 790 (insert (documentation 'hg-help-overview))
791 791 (let ((pos (point)))
792 792 (insert (documentation 'hg-mode))
793 793 (goto-char pos)
794 794 (end-of-line 1)
795 795 (delete-region pos (point)))
796 796 (let ((hg-root-dir (hg-root)))
797 797 (if (not hg-root-dir)
798 798 (error "error: %s: directory is not part of a Mercurial repository."
799 799 default-directory)
800 800 (cd hg-root-dir)))))
801 801
802 802 (defun hg-fix-paths ()
803 803 "Fix paths reported by some Mercurial commands."
804 804 (save-excursion
805 805 (goto-char (point-min))
806 806 (while (re-search-forward " \\.\\.." nil t)
807 807 (replace-match " " nil nil))))
808 808
809 809 (defun hg-add (path)
810 810 "Add PATH to the Mercurial repository on the next commit.
811 811 With a prefix argument, prompt for the path to add."
812 812 (interactive (list (hg-read-file-name " to add")))
813 813 (let ((buf (current-buffer))
814 814 (update (equal buffer-file-name path)))
815 815 (hg-view-output (hg-output-buffer-name)
816 816 (apply 'call-process (hg-binary) nil t nil (list "add" path))
817 817 (hg-fix-paths)
818 818 (goto-char (point-min))
819 819 (cd (hg-root path)))
820 820 (when update
821 821 (unless vc-make-backup-files
822 822 (set (make-local-variable 'backup-inhibited) t))
823 823 (with-current-buffer buf
824 824 (hg-mode-line)))))
825 825
826 826 (defun hg-addremove ()
827 827 (interactive)
828 828 (error "not implemented"))
829 829
830 830 (defun hg-annotate ()
831 831 (interactive)
832 832 (error "not implemented"))
833 833
834 834 (defun hg-commit-toggle-file (pos)
835 835 "Toggle whether or not the file at POS will be committed."
836 836 (interactive "d")
837 837 (save-excursion
838 838 (goto-char pos)
839 839 (let (face
840 840 (inhibit-read-only t)
841 841 bol)
842 842 (beginning-of-line)
843 843 (setq bol (+ (point) 4))
844 844 (setq face (get-text-property bol 'face))
845 845 (end-of-line)
846 846 (if (eq face 'bold)
847 847 (progn
848 848 (remove-text-properties bol (point) '(face nil))
849 849 (message "%s will not be committed"
850 850 (buffer-substring bol (point))))
851 851 (add-text-properties bol (point) '(face bold))
852 852 (message "%s will be committed"
853 853 (buffer-substring bol (point)))))))
854 854
855 855 (defun hg-commit-mouse-clicked (event)
856 856 "Toggle whether or not the file at POS will be committed."
857 857 (interactive "@e")
858 858 (hg-commit-toggle-file (hg-event-point event)))
859 859
860 860 (defun hg-commit-kill ()
861 861 "Kill the commit currently being prepared."
862 862 (interactive)
863 863 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
864 864 (let ((buf hg-prev-buffer))
865 865 (kill-buffer nil)
866 866 (switch-to-buffer buf))))
867 867
868 868 (defun hg-commit-finish ()
869 869 "Finish preparing a commit, and perform the actual commit.
870 870 The hook hg-pre-commit-hook is run before anything else is done. If
871 871 the commit message is empty and hg-commit-allow-empty-message is nil,
872 872 an error is raised. If the list of files to commit is empty and
873 873 hg-commit-allow-empty-file-list is nil, an error is raised."
874 874 (interactive)
875 875 (let ((root hg-root))
876 876 (save-excursion
877 877 (run-hooks 'hg-pre-commit-hook)
878 878 (goto-char (point-min))
879 879 (search-forward hg-commit-message-start)
880 880 (let (message files)
881 881 (let ((start (point)))
882 882 (goto-char (point-max))
883 883 (search-backward hg-commit-message-end)
884 884 (setq message (hg-strip (buffer-substring start (point)))))
885 885 (when (and (= (length message) 0)
886 886 (not hg-commit-allow-empty-message))
887 887 (error "Cannot proceed - commit message is empty"))
888 888 (forward-line 1)
889 889 (beginning-of-line)
890 890 (while (< (point) (point-max))
891 891 (let ((pos (+ (point) 4)))
892 892 (end-of-line)
893 893 (when (eq (get-text-property pos 'face) 'bold)
894 894 (end-of-line)
895 895 (setq files (cons (buffer-substring pos (point)) files))))
896 896 (forward-line 1))
897 897 (when (and (= (length files) 0)
898 898 (not hg-commit-allow-empty-file-list))
899 899 (error "Cannot proceed - no files to commit"))
900 900 (setq message (concat message "\n"))
901 901 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
902 902 (let ((buf hg-prev-buffer))
903 903 (kill-buffer nil)
904 904 (switch-to-buffer buf))
905 905 (hg-update-mode-lines root))))
906 906
907 907 (defun hg-commit-mode ()
908 908 "Mode for describing a commit of changes to a Mercurial repository.
909 909 This involves two actions: describing the changes with a commit
910 910 message, and choosing the files to commit.
911 911
912 912 To describe the commit, simply type some text in the designated area.
913 913
914 914 By default, all modified, added and removed files are selected for
915 915 committing. Files that will be committed are displayed in bold face\;
916 916 those that will not are displayed in normal face.
917 917
918 918 To toggle whether a file will be committed, move the cursor over a
919 919 particular file and hit space or return. Alternatively, middle click
920 920 on the file.
921 921
922 922 Key bindings
923 923 ------------
924 924 \\[hg-commit-finish] proceed with commit
925 925 \\[hg-commit-kill] kill commit
926 926
927 927 \\[hg-diff-repo] view diff of pending changes"
928 928 (interactive)
929 929 (use-local-map hg-commit-mode-map)
930 930 (set-syntax-table text-mode-syntax-table)
931 931 (setq local-abbrev-table text-mode-abbrev-table
932 932 major-mode 'hg-commit-mode
933 933 mode-name "Hg-Commit")
934 934 (set-buffer-modified-p nil)
935 935 (setq buffer-undo-list nil)
936 936 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
937 937
938 938 (defun hg-commit-start ()
939 939 "Prepare a commit of changes to the repository containing the current file."
940 940 (interactive)
941 941 (while hg-prev-buffer
942 942 (set-buffer hg-prev-buffer))
943 943 (let ((root (hg-root))
944 944 (prev-buffer (current-buffer))
945 945 modified-files)
946 946 (unless root
947 947 (error "Cannot commit outside a repository!"))
948 948 (hg-sync-buffers root)
949 949 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
950 950 (when (and (= (length modified-files) 0)
951 951 (not hg-commit-allow-empty-file-list))
952 952 (error "No pending changes to commit"))
953 953 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
954 954 (pop-to-buffer (get-buffer-create buf-name))
955 955 (when (= (point-min) (point-max))
956 956 (set (make-local-variable 'hg-root) root)
957 957 (setq hg-prev-buffer prev-buffer)
958 958 (insert "\n")
959 959 (let ((bol (point)))
960 960 (insert hg-commit-message-end)
961 961 (add-text-properties bol (point) '(face bold-italic)))
962 962 (let ((file-area (point)))
963 963 (insert modified-files)
964 964 (goto-char file-area)
965 965 (while (< (point) (point-max))
966 966 (let ((bol (point)))
967 967 (forward-char 1)
968 968 (insert " ")
969 969 (end-of-line)
970 970 (add-text-properties (+ bol 4) (point)
971 971 '(face bold mouse-face highlight)))
972 972 (forward-line 1))
973 973 (goto-char file-area)
974 974 (add-text-properties (point) (point-max)
975 975 `(keymap ,hg-commit-mode-file-map))
976 976 (goto-char (point-min))
977 977 (insert hg-commit-message-start)
978 978 (add-text-properties (point-min) (point) '(face bold-italic))
979 979 (insert "\n\n")
980 980 (forward-line -1)
981 981 (save-excursion
982 982 (goto-char (point-max))
983 983 (search-backward hg-commit-message-end)
984 984 (add-text-properties (match-beginning 0) (point-max)
985 985 '(read-only t))
986 986 (goto-char (point-min))
987 987 (search-forward hg-commit-message-start)
988 988 (add-text-properties (match-beginning 0) (match-end 0)
989 989 '(read-only t)))
990 990 (hg-commit-mode)
991 991 (cd root))))))
992 992
993 993 (defun hg-diff (path &optional rev1 rev2)
994 994 "Show the differences between REV1 and REV2 of PATH.
995 995 When called interactively, the default behaviour is to treat REV1 as
996 996 the \"parent\" revision, REV2 as the current edited version of the file, and
997 997 PATH as the file edited in the current buffer.
998 998 With a prefix argument, prompt for all of these."
999 999 (interactive (list (hg-read-file-name " to diff")
1000 1000 (let ((rev1 (hg-read-rev " to start with" 'parent)))
1001 1001 (and (not (eq rev1 'parent)) rev1))
1002 1002 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1003 1003 (and (not (eq rev2 'working-dir)) rev2))))
1004 1004 (hg-sync-buffers path)
1005 1005 (let ((a-path (hg-abbrev-file-name path))
1006 1006 ;; none revision is specified explicitly
1007 1007 (none (and (not rev1) (not rev2)))
1008 1008 ;; only one revision is specified explicitly
1009 1009 (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
1010 1010 (and (not rev1) rev2)))
1011 1011 diff)
1012 1012 (hg-view-output ((cond
1013 1013 (none
1014 1014 (format "Mercurial: Diff against parent of %s" a-path))
1015 1015 (one
1016 1016 (format "Mercurial: Diff of rev %s of %s" one a-path))
1017 1017 (t
1018 1018 (format "Mercurial: Diff from rev %s to %s of %s"
1019 1019 rev1 rev2 a-path))))
1020 1020 (cond
1021 1021 (none
1022 1022 (call-process (hg-binary) nil t nil "diff" path))
1023 1023 (one
1024 1024 (call-process (hg-binary) nil t nil "diff" "-r" one path))
1025 1025 (t
1026 1026 (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
1027 1027 (diff-mode)
1028 1028 (setq diff (not (= (point-min) (point-max))))
1029 1029 (font-lock-fontify-buffer)
1030 1030 (cd (hg-root path)))
1031 1031 diff))
1032 1032
1033 1033 (defun hg-diff-repo (path &optional rev1 rev2)
1034 1034 "Show the differences between REV1 and REV2 of repository containing PATH.
1035 1035 When called interactively, the default behaviour is to treat REV1 as
1036 1036 the \"parent\" revision, REV2 as the current edited version of the file, and
1037 1037 PATH as the `hg-root' of the current buffer.
1038 1038 With a prefix argument, prompt for all of these."
1039 1039 (interactive (list (hg-read-file-name " to diff")
1040 1040 (let ((rev1 (hg-read-rev " to start with" 'parent)))
1041 1041 (and (not (eq rev1 'parent)) rev1))
1042 1042 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
1043 1043 (and (not (eq rev2 'working-dir)) rev2))))
1044 1044 (hg-diff (hg-root path) rev1 rev2))
1045 1045
1046 1046 (defun hg-forget (path)
1047 1047 "Lose track of PATH, which has been added, but not yet committed.
1048 1048 This will prevent the file from being incorporated into the Mercurial
1049 1049 repository on the next commit.
1050 1050 With a prefix argument, prompt for the path to forget."
1051 1051 (interactive (list (hg-read-file-name " to forget")))
1052 1052 (let ((buf (current-buffer))
1053 1053 (update (equal buffer-file-name path)))
1054 1054 (hg-view-output (hg-output-buffer-name)
1055 1055 (apply 'call-process (hg-binary) nil t nil (list "forget" path))
1056 1056 ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
1057 1057 (hg-fix-paths)
1058 1058 (goto-char (point-min))
1059 1059 (cd (hg-root path)))
1060 1060 (when update
1061 1061 (with-current-buffer buf
1062 1062 (when (local-variable-p 'backup-inhibited)
1063 1063 (kill-local-variable 'backup-inhibited))
1064 1064 (hg-mode-line)))))
1065 1065
1066 1066 (defun hg-incoming (&optional repo)
1067 1067 "Display changesets present in REPO that are not present locally."
1068 1068 (interactive (list (hg-read-repo-name " where changes would come from")))
1069 1069 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
1070 1070 (hg-abbrev-file-name (hg-root))
1071 1071 (hg-abbrev-file-name
1072 1072 (or repo hg-incoming-repository))))
1073 1073 (call-process (hg-binary) nil t nil "incoming"
1074 1074 (or repo hg-incoming-repository))
1075 1075 (hg-log-mode)
1076 1076 (cd (hg-root))))
1077 1077
1078 1078 (defun hg-init ()
1079 1079 (interactive)
1080 1080 (error "not implemented"))
1081 1081
1082 1082 (defun hg-log-mode ()
1083 1083 "Mode for viewing a Mercurial change log."
1084 1084 (goto-char (point-min))
1085 1085 (when (looking-at "^searching for changes.*$")
1086 1086 (delete-region (match-beginning 0) (match-end 0)))
1087 1087 (run-hooks 'hg-log-mode-hook))
1088 1088
1089 1089 (defun hg-log (path &optional rev1 rev2 log-limit)
1090 1090 "Display the revision history of PATH.
1091 1091 History is displayed between REV1 and REV2.
1092 1092 Number of displayed changesets is limited to LOG-LIMIT.
1093 1093 REV1 defaults to the tip, while REV2 defaults to 0.
1094 1094 LOG-LIMIT defaults to `hg-log-limit'.
1095 1095 With a prefix argument, prompt for each parameter."
1096 1096 (interactive (list (hg-read-file-name " to log")
1097 1097 (hg-read-rev " to start with"
1098 1098 "tip")
1099 1099 (hg-read-rev " to end with"
1100 1100 "0")
1101 1101 (hg-read-number "Output limited to: "
1102 1102 hg-log-limit)))
1103 1103 (let ((a-path (hg-abbrev-file-name path))
1104 1104 (r1 (or rev1 "tip"))
1105 1105 (r2 (or rev2 "0"))
1106 1106 (limit (format "%d" (or log-limit hg-log-limit))))
1107 1107 (hg-view-output ((if (equal r1 r2)
1108 1108 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
1109 1109 (format
1110 1110 "Mercurial: at most %s log(s) from rev %s to %s of %s"
1111 1111 limit r1 r2 a-path)))
1112 1112 (eval (list* 'call-process (hg-binary) nil t nil
1113 1113 "log"
1114 1114 "-r" (format "%s:%s" r1 r2)
1115 1115 "-l" limit
1116 1116 (if (> (length path) (length (hg-root path)))
1117 1117 (cons path nil)
1118 1118 nil)))
1119 1119 (hg-log-mode)
1120 1120 (cd (hg-root path)))))
1121 1121
1122 1122 (defun hg-log-repo (path &optional rev1 rev2 log-limit)
1123 1123 "Display the revision history of the repository containing PATH.
1124 1124 History is displayed between REV1 and REV2.
1125 1125 Number of displayed changesets is limited to LOG-LIMIT,
1126 1126 REV1 defaults to the tip, while REV2 defaults to 0.
1127 1127 LOG-LIMIT defaults to `hg-log-limit'.
1128 1128 With a prefix argument, prompt for each parameter."
1129 1129 (interactive (list (hg-read-file-name " to log")
1130 1130 (hg-read-rev " to start with"
1131 1131 "tip")
1132 1132 (hg-read-rev " to end with"
1133 1133 "0")
1134 1134 (hg-read-number "Output limited to: "
1135 1135 hg-log-limit)))
1136 1136 (hg-log (hg-root path) rev1 rev2 log-limit))
1137 1137
1138 1138 (defun hg-outgoing (&optional repo)
1139 1139 "Display changesets present locally that are not present in REPO."
1140 1140 (interactive (list (hg-read-repo-name " where changes would go to" nil
1141 1141 hg-outgoing-repository)))
1142 1142 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
1143 1143 (hg-abbrev-file-name (hg-root))
1144 1144 (hg-abbrev-file-name
1145 1145 (or repo hg-outgoing-repository))))
1146 1146 (call-process (hg-binary) nil t nil "outgoing"
1147 1147 (or repo hg-outgoing-repository))
1148 1148 (hg-log-mode)
1149 1149 (cd (hg-root))))
1150 1150
1151 1151 (defun hg-pull (&optional repo)
1152 1152 "Pull changes from repository REPO.
1153 1153 This does not update the working directory."
1154 1154 (interactive (list (hg-read-repo-name " to pull from")))
1155 1155 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1156 1156 (hg-abbrev-file-name (hg-root))
1157 1157 (hg-abbrev-file-name
1158 1158 (or repo hg-incoming-repository))))
1159 1159 (call-process (hg-binary) nil t nil "pull"
1160 1160 (or repo hg-incoming-repository))
1161 1161 (cd (hg-root))))
1162 1162
1163 1163 (defun hg-push (&optional repo)
1164 1164 "Push changes to repository REPO."
1165 1165 (interactive (list (hg-read-repo-name " to push to")))
1166 1166 (hg-view-output ((format "Mercurial: Push from %s to %s"
1167 1167 (hg-abbrev-file-name (hg-root))
1168 1168 (hg-abbrev-file-name
1169 1169 (or repo hg-outgoing-repository))))
1170 1170 (call-process (hg-binary) nil t nil "push"
1171 1171 (or repo hg-outgoing-repository))
1172 1172 (cd (hg-root))))
1173 1173
1174 1174 (defun hg-revert-buffer-internal ()
1175 1175 (let ((ctx (hg-buffer-context)))
1176 1176 (message "Reverting %s..." buffer-file-name)
1177 1177 (hg-run0 "revert" buffer-file-name)
1178 1178 (revert-buffer t t t)
1179 1179 (hg-restore-context ctx)
1180 1180 (hg-mode-line)
1181 1181 (message "Reverting %s...done" buffer-file-name)))
1182 1182
1183 1183 (defun hg-revert-buffer ()
1184 1184 "Revert current buffer's file back to the latest committed version.
1185 1185 If the file has not changed, nothing happens. Otherwise, this
1186 1186 displays a diff and asks for confirmation before reverting."
1187 1187 (interactive)
1188 1188 (let ((vc-suppress-confirm nil)
1189 1189 (obuf (current-buffer))
1190 1190 diff)
1191 1191 (vc-buffer-sync)
1192 1192 (unwind-protect
1193 1193 (setq diff (hg-diff buffer-file-name))
1194 1194 (when diff
1195 1195 (unless (yes-or-no-p "Discard changes? ")
1196 1196 (error "Revert cancelled")))
1197 1197 (when diff
1198 1198 (let ((buf (current-buffer)))
1199 1199 (delete-window (selected-window))
1200 1200 (kill-buffer buf))))
1201 1201 (set-buffer obuf)
1202 1202 (when diff
1203 1203 (hg-revert-buffer-internal))))
1204 1204
1205 1205 (defun hg-root (&optional path)
1206 1206 "Return the root of the repository that contains the given path.
1207 1207 If the path is outside a repository, return nil.
1208 1208 When called interactively, the root is printed. A prefix argument
1209 1209 prompts for a path to check."
1210 1210 (interactive (list (hg-read-file-name)))
1211 1211 (if (or path (not hg-root))
1212 1212 (let ((root (do ((prev nil dir)
1213 1213 (dir (file-name-directory
1214 1214 (or
1215 1215 path
1216 1216 buffer-file-name
1217 1217 (expand-file-name default-directory)))
1218 1218 (file-name-directory (directory-file-name dir))))
1219 1219 ((equal prev dir))
1220 1220 (when (file-directory-p (concat dir ".hg"))
1221 1221 (return dir)))))
1222 1222 (when (interactive-p)
1223 1223 (if root
1224 1224 (message "The root of this repository is `%s'." root)
1225 1225 (message "The path `%s' is not in a Mercurial repository."
1226 1226 (hg-abbrev-file-name path))))
1227 1227 root)
1228 1228 hg-root))
1229 1229
1230 1230 (defun hg-cwd (&optional path)
1231 1231 "Return the current directory of PATH within the repository."
1232 1232 (do ((stack nil (cons (file-name-nondirectory
1233 1233 (directory-file-name dir))
1234 1234 stack))
1235 1235 (prev nil dir)
1236 1236 (dir (file-name-directory (or path buffer-file-name
1237 1237 (expand-file-name default-directory)))
1238 1238 (file-name-directory (directory-file-name dir))))
1239 1239 ((equal prev dir))
1240 1240 (when (file-directory-p (concat dir ".hg"))
1241 1241 (let ((cwd (mapconcat 'identity stack "/")))
1242 1242 (unless (equal cwd "")
1243 1243 (return (file-name-as-directory cwd)))))))
1244 1244
1245 1245 (defun hg-status (path)
1246 1246 "Print revision control status of a file or directory.
1247 1247 With prefix argument, prompt for the path to give status for.
1248 1248 Names are displayed relative to the repository root."
1249 1249 (interactive (list (hg-read-file-name " for status" (hg-root))))
1250 1250 (let ((root (hg-root)))
1251 1251 (hg-view-output ((format "Mercurial: Status of %s in %s"
1252 1252 (let ((name (substring (expand-file-name path)
1253 1253 (length root))))
1254 1254 (if (> (length name) 0)
1255 1255 name
1256 1256 "*"))
1257 1257 (hg-abbrev-file-name root)))
1258 1258 (apply 'call-process (hg-binary) nil t nil
1259 1259 (list "--cwd" root "status" path))
1260 1260 (cd (hg-root path)))))
1261 1261
1262 1262 (defun hg-undo ()
1263 1263 (interactive)
1264 1264 (error "not implemented"))
1265 1265
1266 1266 (defun hg-update ()
1267 1267 (interactive)
1268 1268 (error "not implemented"))
1269 1269
1270 1270 (defun hg-version-other-window (rev)
1271 1271 "Visit version REV of the current file in another window.
1272 1272 If the current file is named `F', the version is named `F.~REV~'.
1273 1273 If `F.~REV~' already exists, use it instead of checking it out again."
1274 1274 (interactive "sVersion to visit (default is workfile version): ")
1275 1275 (let* ((file buffer-file-name)
1276 1276 (version (if (string-equal rev "")
1277 1277 "tip"
1278 1278 rev))
1279 1279 (automatic-backup (vc-version-backup-file-name file version))
1280 1280 (manual-backup (vc-version-backup-file-name file version 'manual)))
1281 1281 (unless (file-exists-p manual-backup)
1282 1282 (if (file-exists-p automatic-backup)
1283 1283 (rename-file automatic-backup manual-backup nil)
1284 1284 (hg-run0 "-q" "cat" "-r" version "-o" manual-backup file)))
1285 1285 (find-file-other-window manual-backup)))
1286 1286
1287 1287
1288 1288 (provide 'mercurial)
1289 1289
1290 1290
1291 1291 ;;; Local Variables:
1292 1292 ;;; prompt-to-byte-compile: nil
1293 1293 ;;; end:
@@ -1,1703 +1,1703
1 1 " vim600: set foldmethod=marker:
2 2 "
3 3 " Vim plugin to assist in working with HG-controlled files.
4 4 "
5 5 " Last Change: 2006/02/22
6 6 " Version: 1.77
7 7 " Maintainer: Mathieu Clabaut <mathieu.clabaut@gmail.com>
8 8 " License: This file is placed in the public domain.
9 9 " Credits:
10 10 " Bob Hiestand <bob.hiestand@gmail.com> for the fabulous
11 11 " cvscommand.vim from which this script was directly created by
12 12 " means of sed commands and minor tweaks.
13 13 " Note:
14 14 " For Vim7 the use of Bob Hiestand's vcscommand.vim
15 15 " <http://www.vim.org/scripts/script.php?script_id=90>
16 16 " in conjunction with Vladmir Marek's Hg backend
17 17 " <http://www.vim.org/scripts/script.php?script_id=1898>
18 18 " is recommended.
19 19
20 20 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
21 21 "
22 22 " Section: Documentation
23 23 "----------------------------
24 24 "
25 25 " Documentation should be available by ":help hgcommand" command, once the
26 26 " script has been copied in you .vim/plugin directory.
27 27 "
28 28 " You still can read the documentation at the end of this file. Locate it by
29 29 " searching the "hgcommand-contents" string (and set ft=help to have
30 " appropriate syntaxic coloration).
30 " appropriate syntactic coloration).
31 31
32 32 " Section: Plugin header {{{1
33 33
34 34 " loaded_hgcommand is set to 1 when the initialization begins, and 2 when it
35 35 " completes. This allows various actions to only be taken by functions after
36 36 " system initialization.
37 37
38 38 if exists("g:loaded_hgcommand")
39 39 finish
40 40 endif
41 41 let g:loaded_hgcommand = 1
42 42
43 43 " store 'compatible' settings
44 44 let s:save_cpo = &cpo
45 45 set cpo&vim
46 46
47 47 " run checks
48 48 let s:script_name = expand("<sfile>:t:r")
49 49
50 50 function! s:HGCleanupOnFailure(err)
51 51 echohl WarningMsg
52 52 echomsg s:script_name . ":" a:err "Plugin not loaded"
53 53 echohl None
54 54 let g:loaded_hgcommand = "no"
55 55 unlet s:save_cpo s:script_name
56 56 endfunction
57 57
58 58 if v:version < 602
59 59 call <SID>HGCleanupOnFailure("VIM 6.2 or later required.")
60 60 finish
61 61 endif
62 62
63 63 if !exists("*system")
64 64 call <SID>HGCleanupOnFailure("builtin system() function required.")
65 65 finish
66 66 endif
67 67
68 68 let s:script_version = "v0.2"
69 69
70 70 " Section: Event group setup {{{1
71 71
72 72 augroup HGCommand
73 73 augroup END
74 74
75 75 " Section: Plugin initialization {{{1
76 76 silent do HGCommand User HGPluginInit
77 77
78 78 " Section: Script variable initialization {{{1
79 79
80 80 let s:HGCommandEditFileRunning = 0
81 81 unlet! s:vimDiffRestoreCmd
82 82 unlet! s:vimDiffSourceBuffer
83 83 unlet! s:vimDiffBufferCount
84 84 unlet! s:vimDiffScratchList
85 85
86 86 " Section: Utility functions {{{1
87 87
88 88 " Function: s:HGResolveLink() {{{2
89 89 " Fully resolve the given file name to remove shortcuts or symbolic links.
90 90
91 91 function! s:HGResolveLink(fileName)
92 92 let resolved = resolve(a:fileName)
93 93 if resolved != a:fileName
94 94 let resolved = <SID>HGResolveLink(resolved)
95 95 endif
96 96 return resolved
97 97 endfunction
98 98
99 99 " Function: s:HGChangeToCurrentFileDir() {{{2
100 100 " Go to the directory in which the current HG-controlled file is located.
101 101 " If this is a HG command buffer, first switch to the original file.
102 102
103 103 function! s:HGChangeToCurrentFileDir(fileName)
104 104 let oldCwd=getcwd()
105 105 let fileName=<SID>HGResolveLink(a:fileName)
106 106 let newCwd=fnamemodify(fileName, ':h')
107 107 if strlen(newCwd) > 0
108 108 execute 'cd' escape(newCwd, ' ')
109 109 endif
110 110 return oldCwd
111 111 endfunction
112 112
113 113 " Function: <SID>HGGetOption(name, default) {{{2
114 114 " Grab a user-specified option to override the default provided. Options are
115 115 " searched in the window, buffer, then global spaces.
116 116
117 117 function! s:HGGetOption(name, default)
118 118 if exists("s:" . a:name . "Override")
119 119 execute "return s:".a:name."Override"
120 120 elseif exists("w:" . a:name)
121 121 execute "return w:".a:name
122 122 elseif exists("b:" . a:name)
123 123 execute "return b:".a:name
124 124 elseif exists("g:" . a:name)
125 125 execute "return g:".a:name
126 126 else
127 127 return a:default
128 128 endif
129 129 endfunction
130 130
131 131 " Function: s:HGEditFile(name, origBuffNR) {{{2
132 132 " Wrapper around the 'edit' command to provide some helpful error text if the
133 133 " current buffer can't be abandoned. If name is provided, it is used;
134 134 " otherwise, a nameless scratch buffer is used.
135 135 " Returns: 0 if successful, -1 if an error occurs.
136 136
137 137 function! s:HGEditFile(name, origBuffNR)
138 138 "Name parameter will be pasted into expression.
139 139 let name = escape(a:name, ' *?\')
140 140
141 141 let editCommand = <SID>HGGetOption('HGCommandEdit', 'edit')
142 142 if editCommand != 'edit'
143 143 if <SID>HGGetOption('HGCommandSplit', 'horizontal') == 'horizontal'
144 144 if name == ""
145 145 let editCommand = 'rightbelow new'
146 146 else
147 147 let editCommand = 'rightbelow split ' . name
148 148 endif
149 149 else
150 150 if name == ""
151 151 let editCommand = 'vert rightbelow new'
152 152 else
153 153 let editCommand = 'vert rightbelow split ' . name
154 154 endif
155 155 endif
156 156 else
157 157 if name == ""
158 158 let editCommand = 'enew'
159 159 else
160 160 let editCommand = 'edit ' . name
161 161 endif
162 162 endif
163 163
164 164 " Protect against useless buffer set-up
165 165 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
166 166 try
167 167 execute editCommand
168 168 finally
169 169 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
170 170 endtry
171 171
172 172 let b:HGOrigBuffNR=a:origBuffNR
173 173 let b:HGCommandEdit='split'
174 174 endfunction
175 175
176 176 " Function: s:HGCreateCommandBuffer(cmd, cmdName, statusText, filename) {{{2
177 177 " Creates a new scratch buffer and captures the output from execution of the
178 178 " given command. The name of the scratch buffer is returned.
179 179
180 180 function! s:HGCreateCommandBuffer(cmd, cmdName, statusText, origBuffNR)
181 181 let fileName=bufname(a:origBuffNR)
182 182
183 183 let resultBufferName=''
184 184
185 185 if <SID>HGGetOption("HGCommandNameResultBuffers", 0)
186 186 let nameMarker = <SID>HGGetOption("HGCommandNameMarker", '_')
187 187 if strlen(a:statusText) > 0
188 188 let bufName=a:cmdName . ' -- ' . a:statusText
189 189 else
190 190 let bufName=a:cmdName
191 191 endif
192 192 let bufName=fileName . ' ' . nameMarker . bufName . nameMarker
193 193 let counter=0
194 194 let resultBufferName = bufName
195 195 while buflisted(resultBufferName)
196 196 let counter=counter + 1
197 197 let resultBufferName=bufName . ' (' . counter . ')'
198 198 endwhile
199 199 endif
200 200
201 201 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " " . a:cmd
202 202 "echomsg "DBG :".hgCommand
203 203 let hgOut = system(hgCommand)
204 204 " HACK: diff command does not return proper error codes
205 205 if v:shell_error && a:cmdName != 'hgdiff'
206 206 if strlen(hgOut) == 0
207 207 echoerr "HG command failed"
208 208 else
209 209 echoerr "HG command failed: " . hgOut
210 210 endif
211 211 return -1
212 212 endif
213 213 if strlen(hgOut) == 0
214 214 " Handle case of no output. In this case, it is important to check the
215 215 " file status, especially since hg edit/unedit may change the attributes
216 216 " of the file with no visible output.
217 217
218 218 echomsg "No output from HG command"
219 219 checktime
220 220 return -1
221 221 endif
222 222
223 223 if <SID>HGEditFile(resultBufferName, a:origBuffNR) == -1
224 224 return -1
225 225 endif
226 226
227 227 set buftype=nofile
228 228 set noswapfile
229 229 set filetype=
230 230
231 231 if <SID>HGGetOption("HGCommandDeleteOnHide", 0)
232 232 set bufhidden=delete
233 233 endif
234 234
235 235 silent 0put=hgOut
236 236
237 237 " The last command left a blank line at the end of the buffer. If the
238 238 " last line is folded (a side effect of the 'put') then the attempt to
239 239 " remove the blank line will kill the last fold.
240 240 "
241 241 " This could be fixed by explicitly detecting whether the last line is
242 242 " within a fold, but I prefer to simply unfold the result buffer altogether.
243 243
244 244 if has("folding")
245 245 setlocal nofoldenable
246 246 endif
247 247
248 248 $d
249 249 1
250 250
251 251 " Define the environment and execute user-defined hooks.
252 252
253 253 let b:HGSourceFile=fileName
254 254 let b:HGCommand=a:cmdName
255 255 if a:statusText != ""
256 256 let b:HGStatusText=a:statusText
257 257 endif
258 258
259 259 silent do HGCommand User HGBufferCreated
260 260 return bufnr("%")
261 261 endfunction
262 262
263 263 " Function: s:HGBufferCheck(hgBuffer) {{{2
264 264 " Attempts to locate the original file to which HG operations were applied
265 265 " for a given buffer.
266 266
267 267 function! s:HGBufferCheck(hgBuffer)
268 268 let origBuffer = getbufvar(a:hgBuffer, "HGOrigBuffNR")
269 269 if origBuffer
270 270 if bufexists(origBuffer)
271 271 return origBuffer
272 272 else
273 273 " Original buffer no longer exists.
274 274 return -1
275 275 endif
276 276 else
277 277 " No original buffer
278 278 return a:hgBuffer
279 279 endif
280 280 endfunction
281 281
282 282 " Function: s:HGCurrentBufferCheck() {{{2
283 283 " Attempts to locate the original file to which HG operations were applied
284 284 " for the current buffer.
285 285
286 286 function! s:HGCurrentBufferCheck()
287 287 return <SID>HGBufferCheck(bufnr("%"))
288 288 endfunction
289 289
290 290 " Function: s:HGToggleDeleteOnHide() {{{2
291 291 " Toggles on and off the delete-on-hide behavior of HG buffers
292 292
293 293 function! s:HGToggleDeleteOnHide()
294 294 if exists("g:HGCommandDeleteOnHide")
295 295 unlet g:HGCommandDeleteOnHide
296 296 else
297 297 let g:HGCommandDeleteOnHide=1
298 298 endif
299 299 endfunction
300 300
301 301 " Function: s:HGDoCommand(hgcmd, cmdName, statusText) {{{2
302 302 " General skeleton for HG function execution.
303 303 " Returns: name of the new command buffer containing the command results
304 304
305 305 function! s:HGDoCommand(cmd, cmdName, statusText)
306 306 let hgBufferCheck=<SID>HGCurrentBufferCheck()
307 307 if hgBufferCheck == -1
308 308 echo "Original buffer no longer exists, aborting."
309 309 return -1
310 310 endif
311 311
312 312 let fileName=bufname(hgBufferCheck)
313 313 if isdirectory(fileName)
314 314 let fileName=fileName . "/" . getline(".")
315 315 endif
316 316 let realFileName = fnamemodify(<SID>HGResolveLink(fileName), ':t')
317 317 let oldCwd=<SID>HGChangeToCurrentFileDir(fileName)
318 318 try
319 319 " TODO
320 320 "if !filereadable('HG/Root')
321 321 "throw fileName . ' is not a HG-controlled file.'
322 322 "endif
323 323 let fullCmd = a:cmd . ' "' . realFileName . '"'
324 324 "echomsg "DEBUG".fullCmd
325 325 let resultBuffer=<SID>HGCreateCommandBuffer(fullCmd, a:cmdName, a:statusText, hgBufferCheck)
326 326 return resultBuffer
327 327 catch
328 328 echoerr v:exception
329 329 return -1
330 330 finally
331 331 execute 'cd' escape(oldCwd, ' ')
332 332 endtry
333 333 endfunction
334 334
335 335
336 336 " Function: s:HGGetStatusVars(revision, branch, repository) {{{2
337 337 "
338 338 " Obtains a HG revision number and branch name. The 'revisionVar',
339 339 " 'branchVar'and 'repositoryVar' arguments, if non-empty, contain the names of variables to hold
340 340 " the corresponding results.
341 341 "
342 342 " Returns: string to be exec'd that sets the multiple return values.
343 343
344 344 function! s:HGGetStatusVars(revisionVar, branchVar, repositoryVar)
345 345 let hgBufferCheck=<SID>HGCurrentBufferCheck()
346 346 "echomsg "DBG : in HGGetStatusVars"
347 347 if hgBufferCheck == -1
348 348 return ""
349 349 endif
350 350 let fileName=bufname(hgBufferCheck)
351 351 let fileNameWithoutLink=<SID>HGResolveLink(fileName)
352 352 let realFileName = fnamemodify(fileNameWithoutLink, ':t')
353 353 let oldCwd=<SID>HGChangeToCurrentFileDir(realFileName)
354 354 try
355 355 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " root "
356 356 let roottext=system(hgCommand)
357 357 " Suppress ending null char ! Does it work in window ?
358 358 let roottext=substitute(roottext,'^.*/\([^/\n\r]*\)\n\_.*$','\1','')
359 359 if match(getcwd()."/".fileNameWithoutLink, roottext) == -1
360 360 return ""
361 361 endif
362 362 let returnExpression = ""
363 363 if a:repositoryVar != ""
364 364 let returnExpression=returnExpression . " | let " . a:repositoryVar . "='" . roottext . "'"
365 365 endif
366 366 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . realFileName
367 367 let statustext=system(hgCommand)
368 368 if(v:shell_error)
369 369 return ""
370 370 endif
371 371 if match(statustext, '^[?I]') >= 0
372 372 let revision="NEW"
373 373 elseif match(statustext, '^[R]') >= 0
374 374 let revision="REMOVED"
375 375 elseif match(statustext, '^[D]') >= 0
376 376 let revision="DELETED"
377 377 elseif match(statustext, '^[A]') >= 0
378 378 let revision="ADDED"
379 379 else
380 380 " The file is tracked, we can try to get is revision number
381 381 let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " parents "
382 382 let statustext=system(hgCommand)
383 383 if(v:shell_error)
384 384 return ""
385 385 endif
386 386 let revision=substitute(statustext, '^changeset:\s*\(\d\+\):.*\_$\_.*$', '\1', "")
387 387
388 388 if a:branchVar != "" && match(statustext, '^\_.*\_^branch:') >= 0
389 389 let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "")
390 390 let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'"
391 391 endif
392 392 endif
393 393 if (exists('revision'))
394 394 let returnExpression = "let " . a:revisionVar . "='" . revision . "' " . returnExpression
395 395 endif
396 396
397 397 return returnExpression
398 398 finally
399 399 execute 'cd' escape(oldCwd, ' ')
400 400 endtry
401 401 endfunction
402 402
403 403 " Function: s:HGSetupBuffer() {{{2
404 404 " Attempts to set the b:HGBranch, b:HGRevision and b:HGRepository variables.
405 405
406 406 function! s:HGSetupBuffer(...)
407 407 if (exists("b:HGBufferSetup") && b:HGBufferSetup && !exists('a:1'))
408 408 " This buffer is already set up.
409 409 return
410 410 endif
411 411
412 412 if !<SID>HGGetOption("HGCommandEnableBufferSetup", 0)
413 413 \ || @% == ""
414 414 \ || s:HGCommandEditFileRunning > 0
415 415 \ || exists("b:HGOrigBuffNR")
416 416 unlet! b:HGRevision
417 417 unlet! b:HGBranch
418 418 unlet! b:HGRepository
419 419 return
420 420 endif
421 421
422 422 if !filereadable(expand("%"))
423 423 return -1
424 424 endif
425 425
426 426 let revision=""
427 427 let branch=""
428 428 let repository=""
429 429
430 430 exec <SID>HGGetStatusVars('revision', 'branch', 'repository')
431 431 "echomsg "DBG ".revision."#".branch."#".repository
432 432 if revision != ""
433 433 let b:HGRevision=revision
434 434 else
435 435 unlet! b:HGRevision
436 436 endif
437 437 if branch != ""
438 438 let b:HGBranch=branch
439 439 else
440 440 unlet! b:HGBranch
441 441 endif
442 442 if repository != ""
443 443 let b:HGRepository=repository
444 444 else
445 445 unlet! b:HGRepository
446 446 endif
447 447 silent do HGCommand User HGBufferSetup
448 448 let b:HGBufferSetup=1
449 449 endfunction
450 450
451 451 " Function: s:HGMarkOrigBufferForSetup(hgbuffer) {{{2
452 452 " Resets the buffer setup state of the original buffer for a given HG buffer.
453 453 " Returns: The HG buffer number in a passthrough mode.
454 454
455 455 function! s:HGMarkOrigBufferForSetup(hgBuffer)
456 456 checktime
457 457 if a:hgBuffer != -1
458 458 let origBuffer = <SID>HGBufferCheck(a:hgBuffer)
459 459 "This should never not work, but I'm paranoid
460 460 if origBuffer != a:hgBuffer
461 461 call setbufvar(origBuffer, "HGBufferSetup", 0)
462 462 endif
463 463 else
464 464 "We are presumably in the original buffer
465 465 let b:HGBufferSetup = 0
466 466 "We do the setup now as now event will be triggered allowing it later.
467 467 call <SID>HGSetupBuffer()
468 468 endif
469 469 return a:hgBuffer
470 470 endfunction
471 471
472 472 " Function: s:HGOverrideOption(option, [value]) {{{2
473 473 " Provides a temporary override for the given HG option. If no value is
474 474 " passed, the override is disabled.
475 475
476 476 function! s:HGOverrideOption(option, ...)
477 477 if a:0 == 0
478 478 unlet! s:{a:option}Override
479 479 else
480 480 let s:{a:option}Override = a:1
481 481 endif
482 482 endfunction
483 483
484 484 " Function: s:HGWipeoutCommandBuffers() {{{2
485 485 " Clears all current HG buffers of the specified type for a given source.
486 486
487 487 function! s:HGWipeoutCommandBuffers(originalBuffer, hgCommand)
488 488 let buffer = 1
489 489 while buffer <= bufnr('$')
490 490 if getbufvar(buffer, 'HGOrigBuffNR') == a:originalBuffer
491 491 if getbufvar(buffer, 'HGCommand') == a:hgCommand
492 492 execute 'bw' buffer
493 493 endif
494 494 endif
495 495 let buffer = buffer + 1
496 496 endwhile
497 497 endfunction
498 498
499 499 " Function: s:HGInstallDocumentation(full_name, revision) {{{2
500 500 " Install help documentation.
501 501 " Arguments:
502 502 " full_name: Full name of this vim plugin script, including path name.
503 503 " revision: Revision of the vim script. #version# mark in the document file
504 504 " will be replaced with this string with 'v' prefix.
505 505 " Return:
506 506 " 1 if new document installed, 0 otherwise.
507 507 " Note: Cleaned and generalized by guo-peng Wen
508 508 "'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
509 509 " Helper function to make mkdir as portable as possible
510 510 function! s:HGFlexiMkdir(dir)
511 511 if exists("*mkdir") " we can use Vim's own mkdir()
512 512 call mkdir(a:dir)
513 513 elseif !exists("+shellslash")
514 514 call system("mkdir -p '".a:dir."'")
515 515 else " M$
516 516 let l:ssl = &shellslash
517 517 try
518 518 set shellslash
519 519 " no single quotes?
520 520 call system('mkdir "'.a:dir.'"')
521 521 finally
522 522 let &shellslash = l:ssl
523 523 endtry
524 524 endif
525 525 endfunction
526 526
527 527 function! s:HGInstallDocumentation(full_name)
528 528 " Figure out document path based on full name of this script:
529 529 let l:vim_doc_path = fnamemodify(a:full_name, ":h:h") . "/doc"
530 530 if filewritable(l:vim_doc_path) != 2
531 531 echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
532 532 silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
533 533 if filewritable(l:vim_doc_path) != 2
534 534 " Try first item in 'runtimepath':
535 535 let l:vim_doc_path =
536 536 \ substitute(&runtimepath, '^\([^,]*\).*', '\1/doc', 'e')
537 537 if filewritable(l:vim_doc_path) != 2
538 538 echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
539 539 silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
540 540 if filewritable(l:vim_doc_path) != 2
541 541 " Put a warning:
542 542 echomsg "Unable to open documentation directory"
543 543 echomsg " type `:help add-local-help' for more information."
544 544 return 0
545 545 endif
546 546 endif
547 547 endif
548 548 endif
549 549
550 550 " Full name of documentation file:
551 551 let l:doc_file =
552 552 \ l:vim_doc_path . "/" . s:script_name . ".txt"
553 553 " Bail out if document file is still up to date:
554 554 if filereadable(l:doc_file) &&
555 555 \ getftime(a:full_name) < getftime(l:doc_file)
556 556 return 0
557 557 endif
558 558
559 559 " temporary global settings
560 560 let l:lz = &lazyredraw
561 561 let l:hls = &hlsearch
562 562 set lazyredraw nohlsearch
563 563 " Create a new buffer & read in the plugin file (me):
564 564 1 new
565 565 setlocal noswapfile modifiable nomodeline
566 566 if has("folding")
567 567 setlocal nofoldenable
568 568 endif
569 569 silent execute "read" escape(a:full_name, " ")
570 570 let l:doc_buf = bufnr("%")
571 571
572 572 1
573 573 " Delete from first line to a line starts with
574 574 " === START_DOC
575 575 silent 1,/^=\{3,}\s\+START_DOC\C/ delete _
576 576 " Delete from a line starts with
577 577 " === END_DOC
578 578 " to the end of the documents:
579 579 silent /^=\{3,}\s\+END_DOC\C/,$ delete _
580 580
581 581 " Add modeline for help doc: the modeline string is mangled intentionally
582 582 " to avoid it be recognized by VIM:
583 583 call append(line("$"), "")
584 584 call append(line("$"), " v" . "im:tw=78:ts=8:ft=help:norl:")
585 585
586 586 " Replace revision:
587 587 silent execute "normal :1s/#version#/" . s:script_version . "/\<CR>"
588 588 " Save the help document and wipe out buffer:
589 589 silent execute "wq!" escape(l:doc_file, " ") "| bw" l:doc_buf
590 590 " Build help tags:
591 591 silent execute "helptags" l:vim_doc_path
592 592
593 593 let &hlsearch = l:hls
594 594 let &lazyredraw = l:lz
595 595 return 1
596 596 endfunction
597 597
598 598 " Section: Public functions {{{1
599 599
600 600 " Function: HGGetRevision() {{{2
601 601 " Global function for retrieving the current buffer's HG revision number.
602 602 " Returns: Revision number or an empty string if an error occurs.
603 603
604 604 function! HGGetRevision()
605 605 let revision=""
606 606 exec <SID>HGGetStatusVars('revision', '', '')
607 607 return revision
608 608 endfunction
609 609
610 610 " Function: HGDisableBufferSetup() {{{2
611 611 " Global function for deactivating the buffer autovariables.
612 612
613 613 function! HGDisableBufferSetup()
614 614 let g:HGCommandEnableBufferSetup=0
615 615 silent! augroup! HGCommandPlugin
616 616 endfunction
617 617
618 618 " Function: HGEnableBufferSetup() {{{2
619 619 " Global function for activating the buffer autovariables.
620 620
621 621 function! HGEnableBufferSetup()
622 622 let g:HGCommandEnableBufferSetup=1
623 623 augroup HGCommandPlugin
624 624 au!
625 625 au BufEnter * call <SID>HGSetupBuffer()
626 626 au BufWritePost * call <SID>HGSetupBuffer()
627 627 " Force resetting up buffer on external file change (HG update)
628 628 au FileChangedShell * call <SID>HGSetupBuffer(1)
629 629 augroup END
630 630
631 631 " Only auto-load if the plugin is fully loaded. This gives other plugins a
632 632 " chance to run.
633 633 if g:loaded_hgcommand == 2
634 634 call <SID>HGSetupBuffer()
635 635 endif
636 636 endfunction
637 637
638 638 " Function: HGGetStatusLine() {{{2
639 639 " Default (sample) status line entry for HG files. This is only useful if
640 640 " HG-managed buffer mode is on (see the HGCommandEnableBufferSetup variable
641 641 " for how to do this).
642 642
643 643 function! HGGetStatusLine()
644 644 if exists('b:HGSourceFile')
645 645 " This is a result buffer
646 646 let value='[' . b:HGCommand . ' ' . b:HGSourceFile
647 647 if exists('b:HGStatusText')
648 648 let value=value . ' ' . b:HGStatusText
649 649 endif
650 650 let value = value . ']'
651 651 return value
652 652 endif
653 653
654 654 if exists('b:HGRevision')
655 655 \ && b:HGRevision != ''
656 656 \ && exists('b:HGRepository')
657 657 \ && b:HGRepository != ''
658 658 \ && exists('g:HGCommandEnableBufferSetup')
659 659 \ && g:HGCommandEnableBufferSetup
660 660 if !exists('b:HGBranch')
661 661 let l:branch=''
662 662 else
663 663 let l:branch=b:HGBranch
664 664 endif
665 665 return '[HG ' . b:HGRepository . '/' . l:branch .'/' . b:HGRevision . ']'
666 666 else
667 667 return ''
668 668 endif
669 669 endfunction
670 670
671 671 " Section: HG command functions {{{1
672 672
673 673 " Function: s:HGAdd() {{{2
674 674 function! s:HGAdd()
675 675 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('add', 'hgadd', ''))
676 676 endfunction
677 677
678 678 " Function: s:HGAnnotate(...) {{{2
679 679 function! s:HGAnnotate(...)
680 680 if a:0 == 0
681 681 if &filetype == "HGAnnotate"
682 682 " This is a HGAnnotate buffer. Perform annotation of the version
683 683 " indicated by the current line.
684 684 let revision = substitute(getline("."),'\(^[0-9]*\):.*','\1','')
685 685 if <SID>HGGetOption('HGCommandAnnotateParent', 0) != 0 && revision > 0
686 686 let revision = revision - 1
687 687 endif
688 688 else
689 689 let revision=HGGetRevision()
690 690 if revision == ""
691 691 echoerr "Unable to obtain HG version information."
692 692 return -1
693 693 endif
694 694 endif
695 695 else
696 696 let revision=a:1
697 697 endif
698 698
699 699 if revision == "NEW"
700 700 echo "No annotatation available for new file."
701 701 return -1
702 702 endif
703 703
704 704 let resultBuffer=<SID>HGDoCommand('annotate -ndu -r ' . revision, 'hgannotate', revision)
705 705 "echomsg "DBG: ".resultBuffer
706 706 if resultBuffer != -1
707 707 set filetype=HGAnnotate
708 708 endif
709 709
710 710 return resultBuffer
711 711 endfunction
712 712
713 713 " Function: s:HGCommit() {{{2
714 714 function! s:HGCommit(...)
715 715 " Handle the commit message being specified. If a message is supplied, it
716 716 " is used; if bang is supplied, an empty message is used; otherwise, the
717 717 " user is provided a buffer from which to edit the commit message.
718 718 if a:2 != "" || a:1 == "!"
719 719 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('commit -m "' . a:2 . '"', 'hgcommit', ''))
720 720 endif
721 721
722 722 let hgBufferCheck=<SID>HGCurrentBufferCheck()
723 723 if hgBufferCheck == -1
724 724 echo "Original buffer no longer exists, aborting."
725 725 return -1
726 726 endif
727 727
728 728 " Protect against windows' backslashes in paths. They confuse exec'd
729 729 " commands.
730 730
731 731 let shellSlashBak = &shellslash
732 732 try
733 733 set shellslash
734 734
735 735 let messageFileName = tempname()
736 736
737 737 let fileName=bufname(hgBufferCheck)
738 738 let realFilePath=<SID>HGResolveLink(fileName)
739 739 let newCwd=fnamemodify(realFilePath, ':h')
740 740 if strlen(newCwd) == 0
741 741 " Account for autochdir being in effect, which will make this blank, but
742 742 " we know we'll be in the current directory for the original file.
743 743 let newCwd = getcwd()
744 744 endif
745 745
746 746 let realFileName=fnamemodify(realFilePath, ':t')
747 747
748 748 if <SID>HGEditFile(messageFileName, hgBufferCheck) == -1
749 749 return
750 750 endif
751 751
752 752 " Protect against case and backslash issues in Windows.
753 753 let autoPattern = '\c' . messageFileName
754 754
755 " Ensure existance of group
755 " Ensure existence of group
756 756 augroup HGCommit
757 757 augroup END
758 758
759 759 execute 'au HGCommit BufDelete' autoPattern 'call delete("' . messageFileName . '")'
760 760 execute 'au HGCommit BufDelete' autoPattern 'au! HGCommit * ' autoPattern
761 761
762 762 " Create a commit mapping. The mapping must clear all autocommands in case
763 763 " it is invoked when HGCommandCommitOnWrite is active, as well as to not
764 764 " invoke the buffer deletion autocommand.
765 765
766 766 execute 'nnoremap <silent> <buffer> <Plug>HGCommit '.
767 767 \ ':au! HGCommit * ' . autoPattern . '<CR>'.
768 768 \ ':g/^HG:/d<CR>'.
769 769 \ ':update<CR>'.
770 770 \ ':call <SID>HGFinishCommit("' . messageFileName . '",' .
771 771 \ '"' . newCwd . '",' .
772 772 \ '"' . realFileName . '",' .
773 773 \ hgBufferCheck . ')<CR>'
774 774
775 775 silent 0put ='HG: ----------------------------------------------------------------------'
776 776 silent put =\"HG: Enter Log. Lines beginning with `HG:' are removed automatically\"
777 777 silent put ='HG: Type <leader>cc (or your own <Plug>HGCommit mapping)'
778 778
779 779 if <SID>HGGetOption('HGCommandCommitOnWrite', 1) == 1
780 780 execute 'au HGCommit BufWritePre' autoPattern 'g/^HG:/d'
781 781 execute 'au HGCommit BufWritePost' autoPattern 'call <SID>HGFinishCommit("' . messageFileName . '", "' . newCwd . '", "' . realFileName . '", ' . hgBufferCheck . ') | au! * ' autoPattern
782 782 silent put ='HG: or write this buffer'
783 783 endif
784 784
785 785 silent put ='HG: to finish this commit operation'
786 786 silent put ='HG: ----------------------------------------------------------------------'
787 787 $
788 788 let b:HGSourceFile=fileName
789 789 let b:HGCommand='HGCommit'
790 790 set filetype=hg
791 791 finally
792 792 let &shellslash = shellSlashBak
793 793 endtry
794 794
795 795 endfunction
796 796
797 797 " Function: s:HGDiff(...) {{{2
798 798 function! s:HGDiff(...)
799 799 if a:0 == 1
800 800 let revOptions = '-r' . a:1
801 801 let caption = a:1 . ' -> current'
802 802 elseif a:0 == 2
803 803 let revOptions = '-r' . a:1 . ' -r' . a:2
804 804 let caption = a:1 . ' -> ' . a:2
805 805 else
806 806 let revOptions = ''
807 807 let caption = ''
808 808 endif
809 809
810 810 let hgdiffopt=<SID>HGGetOption('HGCommandDiffOpt', 'w')
811 811
812 812 if hgdiffopt == ""
813 813 let diffoptionstring=""
814 814 else
815 815 let diffoptionstring=" -" . hgdiffopt . " "
816 816 endif
817 817
818 818 let resultBuffer = <SID>HGDoCommand('diff ' . diffoptionstring . revOptions , 'hgdiff', caption)
819 819 if resultBuffer != -1
820 820 set filetype=diff
821 821 endif
822 822 return resultBuffer
823 823 endfunction
824 824
825 825
826 826 " Function: s:HGGotoOriginal(["!]) {{{2
827 827 function! s:HGGotoOriginal(...)
828 828 let origBuffNR = <SID>HGCurrentBufferCheck()
829 829 if origBuffNR > 0
830 830 let origWinNR = bufwinnr(origBuffNR)
831 831 if origWinNR == -1
832 832 execute 'buffer' origBuffNR
833 833 else
834 834 execute origWinNR . 'wincmd w'
835 835 endif
836 836 if a:0 == 1
837 837 if a:1 == "!"
838 838 let buffnr = 1
839 839 let buffmaxnr = bufnr("$")
840 840 while buffnr <= buffmaxnr
841 841 if getbufvar(buffnr, "HGOrigBuffNR") == origBuffNR
842 842 execute "bw" buffnr
843 843 endif
844 844 let buffnr = buffnr + 1
845 845 endwhile
846 846 endif
847 847 endif
848 848 endif
849 849 endfunction
850 850
851 851 " Function: s:HGFinishCommit(messageFile, targetDir, targetFile) {{{2
852 852 function! s:HGFinishCommit(messageFile, targetDir, targetFile, origBuffNR)
853 853 if filereadable(a:messageFile)
854 854 let oldCwd=getcwd()
855 855 if strlen(a:targetDir) > 0
856 856 execute 'cd' escape(a:targetDir, ' ')
857 857 endif
858 858 let resultBuffer=<SID>HGCreateCommandBuffer('commit -l "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR)
859 859 execute 'cd' escape(oldCwd, ' ')
860 860 execute 'bw' escape(a:messageFile, ' *?\')
861 861 silent execute 'call delete("' . a:messageFile . '")'
862 862 return <SID>HGMarkOrigBufferForSetup(resultBuffer)
863 863 else
864 864 echoerr "Can't read message file; no commit is possible."
865 865 return -1
866 866 endif
867 867 endfunction
868 868
869 869 " Function: s:HGLog() {{{2
870 870 function! s:HGLog(...)
871 871 if a:0 == 0
872 872 let versionOption = ""
873 873 let caption = ''
874 874 else
875 875 let versionOption=" -r" . a:1
876 876 let caption = a:1
877 877 endif
878 878
879 879 let resultBuffer=<SID>HGDoCommand('log' . versionOption, 'hglog', caption)
880 880 if resultBuffer != ""
881 881 set filetype=rcslog
882 882 endif
883 883 return resultBuffer
884 884 endfunction
885 885
886 886 " Function: s:HGRevert() {{{2
887 887 function! s:HGRevert()
888 888 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('revert', 'hgrevert', ''))
889 889 endfunction
890 890
891 891 " Function: s:HGReview(...) {{{2
892 892 function! s:HGReview(...)
893 893 if a:0 == 0
894 894 let versiontag=""
895 895 if <SID>HGGetOption('HGCommandInteractive', 0)
896 896 let versiontag=input('Revision: ')
897 897 endif
898 898 if versiontag == ""
899 899 let versiontag="(current)"
900 900 let versionOption=""
901 901 else
902 902 let versionOption=" -r " . versiontag . " "
903 903 endif
904 904 else
905 905 let versiontag=a:1
906 906 let versionOption=" -r " . versiontag . " "
907 907 endif
908 908
909 909 let resultBuffer = <SID>HGDoCommand('cat' . versionOption, 'hgreview', versiontag)
910 910 if resultBuffer > 0
911 911 let &filetype=getbufvar(b:HGOrigBuffNR, '&filetype')
912 912 endif
913 913
914 914 return resultBuffer
915 915 endfunction
916 916
917 917 " Function: s:HGStatus() {{{2
918 918 function! s:HGStatus()
919 919 return <SID>HGDoCommand('status', 'hgstatus', '')
920 920 endfunction
921 921
922 922
923 923 " Function: s:HGUpdate() {{{2
924 924 function! s:HGUpdate()
925 925 return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('update', 'update', ''))
926 926 endfunction
927 927
928 928 " Function: s:HGVimDiff(...) {{{2
929 929 function! s:HGVimDiff(...)
930 930 let originalBuffer = <SID>HGCurrentBufferCheck()
931 931 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
932 932 try
933 933 " If there's already a VimDiff'ed window, restore it.
934 934 " There may only be one HGVimDiff original window at a time.
935 935
936 936 if exists("s:vimDiffSourceBuffer") && s:vimDiffSourceBuffer != originalBuffer
937 937 " Clear the existing vimdiff setup by removing the result buffers.
938 938 call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
939 939 endif
940 940
941 941 " Split and diff
942 942 if(a:0 == 2)
943 943 " Reset the vimdiff system, as 2 explicit versions were provided.
944 944 if exists('s:vimDiffSourceBuffer')
945 945 call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
946 946 endif
947 947 let resultBuffer = <SID>HGReview(a:1)
948 948 if resultBuffer < 0
949 949 echomsg "Can't open HG revision " . a:1
950 950 return resultBuffer
951 951 endif
952 952 let b:HGCommand = 'vimdiff'
953 953 diffthis
954 954 let s:vimDiffBufferCount = 1
955 955 let s:vimDiffScratchList = '{'. resultBuffer . '}'
956 956 " If no split method is defined, cheat, and set it to vertical.
957 957 try
958 958 call <SID>HGOverrideOption('HGCommandSplit', <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
959 959 let resultBuffer=<SID>HGReview(a:2)
960 960 finally
961 961 call <SID>HGOverrideOption('HGCommandSplit')
962 962 endtry
963 963 if resultBuffer < 0
964 964 echomsg "Can't open HG revision " . a:1
965 965 return resultBuffer
966 966 endif
967 967 let b:HGCommand = 'vimdiff'
968 968 diffthis
969 969 let s:vimDiffBufferCount = 2
970 970 let s:vimDiffScratchList = s:vimDiffScratchList . '{'. resultBuffer . '}'
971 971 else
972 972 " Add new buffer
973 973 try
974 974 " Force splitting behavior, otherwise why use vimdiff?
975 975 call <SID>HGOverrideOption("HGCommandEdit", "split")
976 976 call <SID>HGOverrideOption("HGCommandSplit", <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
977 977 if(a:0 == 0)
978 978 let resultBuffer=<SID>HGReview()
979 979 else
980 980 let resultBuffer=<SID>HGReview(a:1)
981 981 endif
982 982 finally
983 983 call <SID>HGOverrideOption("HGCommandEdit")
984 984 call <SID>HGOverrideOption("HGCommandSplit")
985 985 endtry
986 986 if resultBuffer < 0
987 987 echomsg "Can't open current HG revision"
988 988 return resultBuffer
989 989 endif
990 990 let b:HGCommand = 'vimdiff'
991 991 diffthis
992 992
993 993 if !exists('s:vimDiffBufferCount')
994 994 " New instance of vimdiff.
995 995 let s:vimDiffBufferCount = 2
996 996 let s:vimDiffScratchList = '{' . resultBuffer . '}'
997 997
998 998 " This could have been invoked on a HG result buffer, not the
999 999 " original buffer.
1000 1000 wincmd W
1001 1001 execute 'buffer' originalBuffer
1002 1002 " Store info for later original buffer restore
1003 1003 let s:vimDiffRestoreCmd =
1004 1004 \ "call setbufvar(".originalBuffer.", \"&diff\", ".getbufvar(originalBuffer, '&diff').")"
1005 1005 \ . "|call setbufvar(".originalBuffer.", \"&foldcolumn\", ".getbufvar(originalBuffer, '&foldcolumn').")"
1006 1006 \ . "|call setbufvar(".originalBuffer.", \"&foldenable\", ".getbufvar(originalBuffer, '&foldenable').")"
1007 1007 \ . "|call setbufvar(".originalBuffer.", \"&foldmethod\", '".getbufvar(originalBuffer, '&foldmethod')."')"
1008 1008 \ . "|call setbufvar(".originalBuffer.", \"&scrollbind\", ".getbufvar(originalBuffer, '&scrollbind').")"
1009 1009 \ . "|call setbufvar(".originalBuffer.", \"&wrap\", ".getbufvar(originalBuffer, '&wrap').")"
1010 1010 \ . "|if &foldmethod=='manual'|execute 'normal! zE'|endif"
1011 1011 diffthis
1012 1012 wincmd w
1013 1013 else
1014 1014 " Adding a window to an existing vimdiff
1015 1015 let s:vimDiffBufferCount = s:vimDiffBufferCount + 1
1016 1016 let s:vimDiffScratchList = s:vimDiffScratchList . '{' . resultBuffer . '}'
1017 1017 endif
1018 1018 endif
1019 1019
1020 1020 let s:vimDiffSourceBuffer = originalBuffer
1021 1021
1022 1022 " Avoid executing the modeline in the current buffer after the autocommand.
1023 1023
1024 1024 let currentBuffer = bufnr('%')
1025 1025 let saveModeline = getbufvar(currentBuffer, '&modeline')
1026 1026 try
1027 1027 call setbufvar(currentBuffer, '&modeline', 0)
1028 1028 silent do HGCommand User HGVimDiffFinish
1029 1029 finally
1030 1030 call setbufvar(currentBuffer, '&modeline', saveModeline)
1031 1031 endtry
1032 1032 return resultBuffer
1033 1033 finally
1034 1034 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
1035 1035 endtry
1036 1036 endfunction
1037 1037
1038 1038 " Section: Command definitions {{{1
1039 1039 " Section: Primary commands {{{2
1040 1040 com! HGAdd call <SID>HGAdd()
1041 1041 com! -nargs=? HGAnnotate call <SID>HGAnnotate(<f-args>)
1042 1042 com! -bang -nargs=? HGCommit call <SID>HGCommit(<q-bang>, <q-args>)
1043 1043 com! -nargs=* HGDiff call <SID>HGDiff(<f-args>)
1044 1044 com! -bang HGGotoOriginal call <SID>HGGotoOriginal(<q-bang>)
1045 1045 com! -nargs=? HGLog call <SID>HGLog(<f-args>)
1046 1046 com! HGRevert call <SID>HGRevert()
1047 1047 com! -nargs=? HGReview call <SID>HGReview(<f-args>)
1048 1048 com! HGStatus call <SID>HGStatus()
1049 1049 com! HGUpdate call <SID>HGUpdate()
1050 1050 com! -nargs=* HGVimDiff call <SID>HGVimDiff(<f-args>)
1051 1051
1052 1052 " Section: HG buffer management commands {{{2
1053 1053 com! HGDisableBufferSetup call HGDisableBufferSetup()
1054 1054 com! HGEnableBufferSetup call HGEnableBufferSetup()
1055 1055
1056 1056 " Allow reloading hgcommand.vim
1057 1057 com! HGReload unlet! g:loaded_hgcommand | runtime plugin/hgcommand.vim
1058 1058
1059 1059 " Section: Plugin command mappings {{{1
1060 1060 nnoremap <silent> <Plug>HGAdd :HGAdd<CR>
1061 1061 nnoremap <silent> <Plug>HGAnnotate :HGAnnotate<CR>
1062 1062 nnoremap <silent> <Plug>HGCommit :HGCommit<CR>
1063 1063 nnoremap <silent> <Plug>HGDiff :HGDiff<CR>
1064 1064 nnoremap <silent> <Plug>HGGotoOriginal :HGGotoOriginal<CR>
1065 1065 nnoremap <silent> <Plug>HGClearAndGotoOriginal :HGGotoOriginal!<CR>
1066 1066 nnoremap <silent> <Plug>HGLog :HGLog<CR>
1067 1067 nnoremap <silent> <Plug>HGRevert :HGRevert<CR>
1068 1068 nnoremap <silent> <Plug>HGReview :HGReview<CR>
1069 1069 nnoremap <silent> <Plug>HGStatus :HGStatus<CR>
1070 1070 nnoremap <silent> <Plug>HGUpdate :HGUpdate<CR>
1071 1071 nnoremap <silent> <Plug>HGVimDiff :HGVimDiff<CR>
1072 1072
1073 1073 " Section: Default mappings {{{1
1074 1074 if !hasmapto('<Plug>HGAdd')
1075 1075 nmap <unique> <Leader>hga <Plug>HGAdd
1076 1076 endif
1077 1077 if !hasmapto('<Plug>HGAnnotate')
1078 1078 nmap <unique> <Leader>hgn <Plug>HGAnnotate
1079 1079 endif
1080 1080 if !hasmapto('<Plug>HGClearAndGotoOriginal')
1081 1081 nmap <unique> <Leader>hgG <Plug>HGClearAndGotoOriginal
1082 1082 endif
1083 1083 if !hasmapto('<Plug>HGCommit')
1084 1084 nmap <unique> <Leader>hgc <Plug>HGCommit
1085 1085 endif
1086 1086 if !hasmapto('<Plug>HGDiff')
1087 1087 nmap <unique> <Leader>hgd <Plug>HGDiff
1088 1088 endif
1089 1089 if !hasmapto('<Plug>HGGotoOriginal')
1090 1090 nmap <unique> <Leader>hgg <Plug>HGGotoOriginal
1091 1091 endif
1092 1092 if !hasmapto('<Plug>HGLog')
1093 1093 nmap <unique> <Leader>hgl <Plug>HGLog
1094 1094 endif
1095 1095 if !hasmapto('<Plug>HGRevert')
1096 1096 nmap <unique> <Leader>hgq <Plug>HGRevert
1097 1097 endif
1098 1098 if !hasmapto('<Plug>HGReview')
1099 1099 nmap <unique> <Leader>hgr <Plug>HGReview
1100 1100 endif
1101 1101 if !hasmapto('<Plug>HGStatus')
1102 1102 nmap <unique> <Leader>hgs <Plug>HGStatus
1103 1103 endif
1104 1104 if !hasmapto('<Plug>HGUpdate')
1105 1105 nmap <unique> <Leader>hgu <Plug>HGUpdate
1106 1106 endif
1107 1107 if !hasmapto('<Plug>HGVimDiff')
1108 1108 nmap <unique> <Leader>hgv <Plug>HGVimDiff
1109 1109 endif
1110 1110
1111 1111 " Section: Menu items {{{1
1112 1112 silent! aunmenu Plugin.HG
1113 1113 amenu <silent> &Plugin.HG.&Add <Plug>HGAdd
1114 1114 amenu <silent> &Plugin.HG.A&nnotate <Plug>HGAnnotate
1115 1115 amenu <silent> &Plugin.HG.&Commit <Plug>HGCommit
1116 1116 amenu <silent> &Plugin.HG.&Diff <Plug>HGDiff
1117 1117 amenu <silent> &Plugin.HG.&Log <Plug>HGLog
1118 1118 amenu <silent> &Plugin.HG.Revert <Plug>HGRevert
1119 1119 amenu <silent> &Plugin.HG.&Review <Plug>HGReview
1120 1120 amenu <silent> &Plugin.HG.&Status <Plug>HGStatus
1121 1121 amenu <silent> &Plugin.HG.&Update <Plug>HGUpdate
1122 1122 amenu <silent> &Plugin.HG.&VimDiff <Plug>HGVimDiff
1123 1123
1124 1124 " Section: Autocommands to restore vimdiff state {{{1
1125 1125 function! s:HGVimDiffRestore(vimDiffBuff)
1126 1126 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
1127 1127 try
1128 1128 if exists("s:vimDiffSourceBuffer")
1129 1129 if a:vimDiffBuff == s:vimDiffSourceBuffer
1130 1130 " Original file is being removed.
1131 1131 unlet! s:vimDiffSourceBuffer
1132 1132 unlet! s:vimDiffBufferCount
1133 1133 unlet! s:vimDiffRestoreCmd
1134 1134 unlet! s:vimDiffScratchList
1135 1135 elseif match(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}') >= 0
1136 1136 let s:vimDiffScratchList = substitute(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}', '', '')
1137 1137 let s:vimDiffBufferCount = s:vimDiffBufferCount - 1
1138 1138 if s:vimDiffBufferCount == 1 && exists('s:vimDiffRestoreCmd')
1139 1139 " All scratch buffers are gone, reset the original.
1140 1140 " Only restore if the source buffer is still in Diff mode
1141 1141
1142 1142 let sourceWinNR=bufwinnr(s:vimDiffSourceBuffer)
1143 1143 if sourceWinNR != -1
1144 1144 " The buffer is visible in at least one window
1145 1145 let currentWinNR = winnr()
1146 1146 while winbufnr(sourceWinNR) != -1
1147 1147 if winbufnr(sourceWinNR) == s:vimDiffSourceBuffer
1148 1148 execute sourceWinNR . 'wincmd w'
1149 1149 if getwinvar('', "&diff")
1150 1150 execute s:vimDiffRestoreCmd
1151 1151 endif
1152 1152 endif
1153 1153 let sourceWinNR = sourceWinNR + 1
1154 1154 endwhile
1155 1155 execute currentWinNR . 'wincmd w'
1156 1156 else
1157 1157 " The buffer is hidden. It must be visible in order to set the
1158 1158 " diff option.
1159 1159 let currentBufNR = bufnr('')
1160 1160 execute "hide buffer" s:vimDiffSourceBuffer
1161 1161 if getwinvar('', "&diff")
1162 1162 execute s:vimDiffRestoreCmd
1163 1163 endif
1164 1164 execute "hide buffer" currentBufNR
1165 1165 endif
1166 1166
1167 1167 unlet s:vimDiffRestoreCmd
1168 1168 unlet s:vimDiffSourceBuffer
1169 1169 unlet s:vimDiffBufferCount
1170 1170 unlet s:vimDiffScratchList
1171 1171 elseif s:vimDiffBufferCount == 0
1172 1172 " All buffers are gone.
1173 1173 unlet s:vimDiffSourceBuffer
1174 1174 unlet s:vimDiffBufferCount
1175 1175 unlet s:vimDiffScratchList
1176 1176 endif
1177 1177 endif
1178 1178 endif
1179 1179 finally
1180 1180 let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
1181 1181 endtry
1182 1182 endfunction
1183 1183
1184 1184 augroup HGVimDiffRestore
1185 1185 au!
1186 1186 au BufUnload * call <SID>HGVimDiffRestore(expand("<abuf>"))
1187 1187 augroup END
1188 1188
1189 1189 " Section: Optional activation of buffer management {{{1
1190 1190
1191 1191 if s:HGGetOption('HGCommandEnableBufferSetup', 1)
1192 1192 call HGEnableBufferSetup()
1193 1193 endif
1194 1194
1195 1195 " Section: Doc installation {{{1
1196 1196
1197 1197 if <SID>HGInstallDocumentation(expand("<sfile>:p"))
1198 1198 echomsg s:script_name s:script_version . ": updated documentation"
1199 1199 endif
1200 1200
1201 1201 " Section: Plugin completion {{{1
1202 1202
1203 1203 " delete one-time vars and functions
1204 1204 delfunction <SID>HGInstallDocumentation
1205 1205 delfunction <SID>HGFlexiMkdir
1206 1206 delfunction <SID>HGCleanupOnFailure
1207 1207 unlet s:script_version s:script_name
1208 1208
1209 1209 let g:loaded_hgcommand=2
1210 1210 silent do HGCommand User HGPluginFinish
1211 1211
1212 1212 let &cpo = s:save_cpo
1213 1213 unlet s:save_cpo
1214 1214 " vim:se expandtab sts=2 sw=2:
1215 1215 finish
1216 1216
1217 1217 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1218 1218 " Section: Documentation content {{{1
1219 1219 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1220 1220 === START_DOC
1221 1221 *hgcommand.txt* Mercurial vim integration #version#
1222 1222
1223 1223
1224 1224 HGCOMMAND REFERENCE MANUAL~
1225 1225
1226 1226
1227 1227 Author: Mathieu Clabaut <mathieu.clabaut@gmail.com>
1228 1228 Credits: Bob Hiestand <bob.hiestand@gmail.com>
1229 1229 Mercurial: http://mercurial.selenic.com/
1230 1230 Mercurial (noted Hg) is a fast, lightweight Source Control Management
1231 1231 system designed for efficient handling of very large distributed projects.
1232 1232
1233 1233 ==============================================================================
1234 1234 1. Contents *hgcommand-contents*
1235 1235
1236 1236 Installation : |hgcommand-install|
1237 1237 HGCommand Intro : |hgcommand|
1238 1238 HGCommand Manual : |hgcommand-manual|
1239 1239 Customization : |hgcommand-customize|
1240 1240 Bugs : |hgcommand-bugs|
1241 1241
1242 1242 ==============================================================================
1243 1243 2. HGCommand Installation *hgcommand-install*
1244 1244
1245 1245 In order to install the plugin, place the hgcommand.vim file into a plugin'
1246 1246 directory in your runtime path (please see |add-global-plugin| and
1247 1247 |'runtimepath'|.
1248 1248
1249 1249 HGCommand may be customized by setting variables, creating maps, and
1250 1250 specifying event handlers. Please see |hgcommand-customize| for more
1251 1251 details.
1252 1252
1253 1253 *hgcommand-auto-help*
1254 1254 The help file is automagically generated when the |hgcommand| script is
1255 1255 loaded for the first time.
1256 1256
1257 1257 ==============================================================================
1258 1258
1259 1259 3. HGCommand Intro *hgcommand*
1260 1260 *hgcommand-intro*
1261 1261
1262 1262 The HGCommand plugin provides global ex commands for manipulating
1263 1263 HG-controlled source files. In general, each command operates on the
1264 1264 current buffer and accomplishes a separate hg function, such as update,
1265 1265 commit, log, and others (please see |hgcommand-commands| for a list of all
1266 1266 available commands). The results of each operation are displayed in a
1267 1267 scratch buffer. Several buffer variables are defined for those scratch
1268 1268 buffers (please see |hgcommand-buffer-variables|).
1269 1269
1270 1270 The notion of "current file" means either the current buffer, or, in the
1271 1271 case of a directory buffer, the file on the current line within the buffer.
1272 1272
1273 1273 For convenience, any HGCommand invoked on a HGCommand scratch buffer acts
1274 1274 as though it was invoked on the original file and splits the screen so that
1275 1275 the output appears in a new window.
1276 1276
1277 1277 Many of the commands accept revisions as arguments. By default, most
1278 1278 operate on the most recent revision on the current branch if no revision is
1279 1279 specified (though see |HGCommandInteractive| to prompt instead).
1280 1280
1281 1281 Each HGCommand is mapped to a key sequence starting with the <Leader>
1282 1282 keystroke. The default mappings may be overridden by supplying different
1283 1283 mappings before the plugin is loaded, such as in the vimrc, in the standard
1284 1284 fashion for plugin mappings. For examples, please see
1285 1285 |hgcommand-mappings-override|.
1286 1286
1287 1287 The HGCommand plugin may be configured in several ways. For more details,
1288 1288 please see |hgcommand-customize|.
1289 1289
1290 1290 ==============================================================================
1291 1291 4. HGCommand Manual *hgcommand-manual*
1292 1292
1293 1293 4.1 HGCommand commands *hgcommand-commands*
1294 1294
1295 1295 HGCommand defines the following commands:
1296 1296
1297 1297 |:HGAdd|
1298 1298 |:HGAnnotate|
1299 1299 |:HGCommit|
1300 1300 |:HGDiff|
1301 1301 |:HGGotoOriginal|
1302 1302 |:HGLog|
1303 1303 |:HGRevert|
1304 1304 |:HGReview|
1305 1305 |:HGStatus|
1306 1306 |:HGUpdate|
1307 1307 |:HGVimDiff|
1308 1308
1309 1309 :HGAdd *:HGAdd*
1310 1310
1311 1311 This command performs "hg add" on the current file. Please note, this does
1312 1312 not commit the newly-added file.
1313 1313
1314 1314 :HGAnnotate *:HGAnnotate*
1315 1315
1316 1316 This command performs "hg annotate" on the current file. If an argument is
1317 1317 given, the argument is used as a revision number to display. If not given
1318 1318 an argument, it uses the most recent version of the file on the current
1319 1319 branch. Additionally, if the current buffer is a HGAnnotate buffer
1320 1320 already, the version number on the current line is used.
1321 1321
1322 1322 If the |HGCommandAnnotateParent| variable is set to a non-zero value, the
1323 1323 version previous to the one on the current line is used instead. This
1324 1324 allows one to navigate back to examine the previous version of a line.
1325 1325
1326 1326 The filetype of the HGCommand scratch buffer is set to 'HGAnnotate', to
1327 1327 take advantage of the bundled syntax file.
1328 1328
1329 1329
1330 1330 :HGCommit[!] *:HGCommit*
1331 1331
1332 1332 If called with arguments, this performs "hg commit" using the arguments as
1333 1333 the log message.
1334 1334
1335 1335 If '!' is used with no arguments, an empty log message is committed.
1336 1336
1337 1337 If called with no arguments, this is a two-step command. The first step
1338 1338 opens a buffer to accept a log message. When that buffer is written, it is
1339 1339 automatically closed and the file is committed using the information from
1340 1340 that log message. The commit can be abandoned if the log message buffer is
1341 1341 deleted or wiped before being written.
1342 1342
1343 1343 Alternatively, the mapping that is used to invoke :HGCommit (by default
1344 1344 <Leader>hgc) can be used in the log message buffer to immediately commit.
1345 1345 This is useful if the |HGCommandCommitOnWrite| variable is set to 0 to
1346 1346 disable the normal commit-on-write behavior.
1347 1347
1348 1348 :HGDiff *:HGDiff*
1349 1349
1350 1350 With no arguments, this performs "hg diff" on the current file against the
1351 1351 current repository version.
1352 1352
1353 1353 With one argument, "hg diff" is performed on the current file against the
1354 1354 specified revision.
1355 1355
1356 1356 With two arguments, hg diff is performed between the specified revisions of
1357 1357 the current file.
1358 1358
1359 1359 This command uses the 'HGCommandDiffOpt' variable to specify diff options.
1360 1360 If that variable does not exist, then 'wbBc' is assumed. If you wish to
1361 1361 have no options, then set it to the empty string.
1362 1362
1363 1363
1364 1364 :HGGotoOriginal *:HGGotoOriginal*
1365 1365
1366 1366 This command returns the current window to the source buffer, if the
1367 1367 current buffer is a HG command output buffer.
1368 1368
1369 1369 :HGGotoOriginal!
1370 1370
1371 1371 Like ":HGGotoOriginal" but also executes :bufwipeout on all HG command
1372 1372 output buffers for the source buffer.
1373 1373
1374 1374 :HGLog *:HGLog*
1375 1375
1376 1376 Performs "hg log" on the current file.
1377 1377
1378 1378 If an argument is given, it is passed as an argument to the "-r" option of
1379 1379 "hg log".
1380 1380
1381 1381 :HGRevert *:HGRevert*
1382 1382
1383 1383 Replaces the current file with the most recent version from the repository
1384 1384 in order to wipe out any undesired changes.
1385 1385
1386 1386 :HGReview *:HGReview*
1387 1387
1388 1388 Retrieves a particular version of the current file. If no argument is
1389 1389 given, the most recent version of the file on the current branch is
1390 1390 retrieved. Otherwise, the specified version is retrieved.
1391 1391
1392 1392 :HGStatus *:HGStatus*
1393 1393
1394 1394 Performs "hg status" on the current file.
1395 1395
1396 1396 :HGUpdate *:HGUpdate*
1397 1397
1398 1398 Performs "hg update" on the current file. This intentionally does not
1399 1399 automatically reload the current buffer, though vim should prompt the user
1400 1400 to do so if the underlying file is altered by this command.
1401 1401
1402 1402 :HGVimDiff *:HGVimDiff*
1403 1403
1404 1404 With no arguments, this prompts the user for a revision and then uses
1405 1405 vimdiff to display the differences between the current file and the
1406 1406 specified revision. If no revision is specified, the most recent version
1407 1407 of the file on the current branch is used.
1408 1408
1409 1409 With one argument, that argument is used as the revision as above. With
1410 1410 two arguments, the differences between the two revisions is displayed using
1411 1411 vimdiff.
1412 1412
1413 1413 With either zero or one argument, the original buffer is used to perform
1414 1414 the vimdiff. When the other buffer is closed, the original buffer will be
1415 1415 returned to normal mode.
1416 1416
1417 1417 Once vimdiff mode is started using the above methods, additional vimdiff
1418 1418 buffers may be added by passing a single version argument to the command.
1419 1419 There may be up to 4 vimdiff buffers total.
1420 1420
1421 1421 Using the 2-argument form of the command resets the vimdiff to only those 2
1422 1422 versions. Additionally, invoking the command on a different file will
1423 1423 close the previous vimdiff buffers.
1424 1424
1425 1425
1426 1426 4.2 Mappings *hgcommand-mappings*
1427 1427
1428 1428 By default, a mapping is defined for each command. These mappings execute
1429 1429 the default (no-argument) form of each command.
1430 1430
1431 1431 <Leader>hga HGAdd
1432 1432 <Leader>hgn HGAnnotate
1433 1433 <Leader>hgc HGCommit
1434 1434 <Leader>hgd HGDiff
1435 1435 <Leader>hgg HGGotoOriginal
1436 1436 <Leader>hgG HGGotoOriginal!
1437 1437 <Leader>hgl HGLog
1438 1438 <Leader>hgr HGReview
1439 1439 <Leader>hgs HGStatus
1440 1440 <Leader>hgu HGUpdate
1441 1441 <Leader>hgv HGVimDiff
1442 1442
1443 1443 *hgcommand-mappings-override*
1444 1444
1445 1445 The default mappings can be overridden by user-provided instead by mapping
1446 1446 to <Plug>CommandName. This is especially useful when these mappings
1447 1447 collide with other existing mappings (vim will warn of this during plugin
1448 1448 initialization, but will not clobber the existing mappings).
1449 1449
1450 1450 For instance, to override the default mapping for :HGAdd to set it to
1451 1451 '\add', add the following to the vimrc: >
1452 1452
1453 1453 nmap \add <Plug>HGAdd
1454 1454 <
1455 1455 4.3 Automatic buffer variables *hgcommand-buffer-variables*
1456 1456
1457 1457 Several buffer variables are defined in each HGCommand result buffer.
1458 1458 These may be useful for additional customization in callbacks defined in
1459 1459 the event handlers (please see |hgcommand-events|).
1460 1460
1461 1461 The following variables are automatically defined:
1462 1462
1463 1463 b:hgOrigBuffNR *b:hgOrigBuffNR*
1464 1464
1465 1465 This variable is set to the buffer number of the source file.
1466 1466
1467 1467 b:hgcmd *b:hgcmd*
1468 1468
1469 1469 This variable is set to the name of the hg command that created the result
1470 1470 buffer.
1471 1471 ==============================================================================
1472 1472
1473 1473 5. Configuration and customization *hgcommand-customize*
1474 1474 *hgcommand-config*
1475 1475
1476 1476 The HGCommand plugin can be configured in two ways: by setting
1477 1477 configuration variables (see |hgcommand-options|) or by defining HGCommand
1478 1478 event handlers (see |hgcommand-events|). Additionally, the HGCommand
1479 1479 plugin provides several option for naming the HG result buffers (see
1480 1480 |hgcommand-naming|) and supported a customized status line (see
1481 1481 |hgcommand-statusline| and |hgcommand-buffer-management|).
1482 1482
1483 1483 5.1 HGCommand configuration variables *hgcommand-options*
1484 1484
1485 1485 Several variables affect the plugin's behavior. These variables are
1486 1486 checked at time of execution, and may be defined at the window, buffer, or
1487 1487 global level and are checked in that order of precedence.
1488 1488
1489 1489
1490 1490 The following variables are available:
1491 1491
1492 1492 |HGCommandAnnotateParent|
1493 1493 |HGCommandCommitOnWrite|
1494 1494 |HGCommandHGExec|
1495 1495 |HGCommandDeleteOnHide|
1496 1496 |HGCommandDiffOpt|
1497 1497 |HGCommandDiffSplit|
1498 1498 |HGCommandEdit|
1499 1499 |HGCommandEnableBufferSetup|
1500 1500 |HGCommandInteractive|
1501 1501 |HGCommandNameMarker|
1502 1502 |HGCommandNameResultBuffers|
1503 1503 |HGCommandSplit|
1504 1504
1505 1505 HGCommandAnnotateParent *HGCommandAnnotateParent*
1506 1506
1507 1507 This variable, if set to a non-zero value, causes the zero-argument form of
1508 1508 HGAnnotate when invoked on a HGAnnotate buffer to go to the version
1509 1509 previous to that displayed on the current line. If not set, it defaults to
1510 1510 0.
1511 1511
1512 1512 HGCommandCommitOnWrite *HGCommandCommitOnWrite*
1513 1513
1514 1514 This variable, if set to a non-zero value, causes the pending hg commit to
1515 1515 take place immediately as soon as the log message buffer is written. If
1516 1516 set to zero, only the HGCommit mapping will cause the pending commit to
1517 1517 occur. If not set, it defaults to 1.
1518 1518
1519 1519 HGCommandHGExec *HGCommandHGExec*
1520 1520
1521 1521 This variable controls the executable used for all HG commands. If not
1522 1522 set, it defaults to "hg".
1523 1523
1524 1524 HGCommandDeleteOnHide *HGCommandDeleteOnHide*
1525 1525
1526 1526 This variable, if set to a non-zero value, causes the temporary HG result
1527 1527 buffers to automatically delete themselves when hidden.
1528 1528
1529 1529 HGCommandDiffOpt *HGCommandDiffOpt*
1530 1530
1531 1531 This variable, if set, determines the options passed to the diff command of
1532 1532 HG. If not set, it defaults to 'w'.
1533 1533
1534 1534 HGCommandDiffSplit *HGCommandDiffSplit*
1535 1535
1536 1536 This variable overrides the |HGCommandSplit| variable, but only for buffers
1537 1537 created with |:HGVimDiff|.
1538 1538
1539 1539 HGCommandEdit *HGCommandEdit*
1540 1540
1541 1541 This variable controls whether the original buffer is replaced ('edit') or
1542 1542 split ('split'). If not set, it defaults to 'edit'.
1543 1543
1544 1544 HGCommandEnableBufferSetup *HGCommandEnableBufferSetup*
1545 1545
1546 1546 This variable, if set to a non-zero value, activates HG buffer management
1547 1547 mode see (|hgcommand-buffer-management|). This mode means that three
1548 1548 buffer variables, 'HGRepository', 'HGRevision' and 'HGBranch', are set if
1549 1549 the file is HG-controlled. This is useful for displaying version
1550 1550 information in the status bar.
1551 1551
1552 1552 HGCommandInteractive *HGCommandInteractive*
1553 1553
1554 1554 This variable, if set to a non-zero value, causes appropriate commands (for
1555 1555 the moment, only |:HGReview|) to query the user for a revision to use
1556 1556 instead of the current revision if none is specified.
1557 1557
1558 1558 HGCommandNameMarker *HGCommandNameMarker*
1559 1559
1560 1560 This variable, if set, configures the special attention-getting characters
1561 1561 that appear on either side of the hg buffer type in the buffer name. This
1562 1562 has no effect unless |HGCommandNameResultBuffers| is set to a true value.
1563 1563 If not set, it defaults to '_'.
1564 1564
1565 1565 HGCommandNameResultBuffers *HGCommandNameResultBuffers*
1566 1566
1567 1567 This variable, if set to a true value, causes the hg result buffers to be
1568 1568 named in the old way ('<source file name> _<hg command>_'). If not set or
1569 1569 set to a false value, the result buffer is nameless.
1570 1570
1571 1571 HGCommandSplit *HGCommandSplit*
1572 1572
1573 1573 This variable controls the orientation of the various window splits that
1574 1574 may occur (such as with HGVimDiff, when using a HG command on a HG command
1575 1575 buffer, or when the |HGCommandEdit| variable is set to 'split'. If set to
1576 1576 'horizontal', the resulting windows will be on stacked on top of one
1577 1577 another. If set to 'vertical', the resulting windows will be side-by-side.
1578 1578 If not set, it defaults to 'horizontal' for all but HGVimDiff windows.
1579 1579
1580 1580 5.2 HGCommand events *hgcommand-events*
1581 1581
1582 1582 For additional customization, HGCommand can trigger user-defined events.
1583 1583 Event handlers are provided by defining User event autocommands (see
1584 1584 |autocommand|, |User|) in the HGCommand group with patterns matching the
1585 1585 event name.
1586 1586
1587 1587 For instance, the following could be added to the vimrc to provide a 'q'
1588 1588 mapping to quit a HGCommand scratch buffer: >
1589 1589
1590 1590 augroup HGCommand
1591 1591 au HGCommand User HGBufferCreated silent! nmap <unique> <buffer> q:
1592 1592 bwipeout<cr>
1593 1593 augroup END
1594 1594 <
1595 1595
1596 1596 The following hooks are available:
1597 1597
1598 1598 HGBufferCreated This event is fired just after a hg command result
1599 1599 buffer is created and filled with the result of a hg
1600 1600 command. It is executed within the context of the HG
1601 1601 command buffer. The HGCommand buffer variables may be
1602 1602 useful for handlers of this event (please see
1603 1603 |hgcommand-buffer-variables|).
1604 1604
1605 1605 HGBufferSetup This event is fired just after HG buffer setup occurs,
1606 1606 if enabled.
1607 1607
1608 1608 HGPluginInit This event is fired when the HGCommand plugin first
1609 1609 loads.
1610 1610
1611 1611 HGPluginFinish This event is fired just after the HGCommand plugin
1612 1612 loads.
1613 1613
1614 1614 HGVimDiffFinish This event is fired just after the HGVimDiff command
1615 1615 executes to allow customization of, for instance,
1616 1616 window placement and focus.
1617 1617
1618 1618 5.3 HGCommand buffer naming *hgcommand-naming*
1619 1619
1620 1620 By default, the buffers containing the result of HG commands are nameless
1621 1621 scratch buffers. It is intended that buffer variables of those buffers be
1622 1622 used to customize the statusline option so that the user may fully control
1623 1623 the display of result buffers.
1624 1624
1625 1625 If the old-style naming is desired, please enable the
1626 1626 |HGCommandNameResultBuffers| variable. Then, each result buffer will
1627 1627 receive a unique name that includes the source file name, the HG command,
1628 1628 and any extra data (such as revision numbers) that were part of the
1629 1629 command.
1630 1630
1631 1631 5.4 HGCommand status line support *hgcommand-statusline*
1632 1632
1633 1633 It is intended that the user will customize the |'statusline'| option to
1634 1634 include HG result buffer attributes. A sample function that may be used in
1635 1635 the |'statusline'| option is provided by the plugin, HGGetStatusLine(). In
1636 1636 order to use that function in the status line, do something like the
1637 1637 following: >
1638 1638
1639 1639 set statusline=%<%f\ %{HGGetStatusLine()}\ %h%m%r%=%l,%c%V\ %P
1640 1640 <
1641 1641 of which %{HGGetStatusLine()} is the relevant portion.
1642 1642
1643 1643 The sample HGGetStatusLine() function handles both HG result buffers and
1644 1644 HG-managed files if HGCommand buffer management is enabled (please see
1645 1645 |hgcommand-buffer-management|).
1646 1646
1647 1647 5.5 HGCommand buffer management *hgcommand-buffer-management*
1648 1648
1649 1649 The HGCommand plugin can operate in buffer management mode, which means
1650 1650 that it attempts to set two buffer variables ('HGRevision' and 'HGBranch')
1651 1651 upon entry into a buffer. This is rather slow because it means that 'hg
1652 1652 status' will be invoked at each entry into a buffer (during the |BufEnter|
1653 1653 autocommand).
1654 1654
1655 1655 This mode is enabled by default. In order to disable it, set the
1656 1656 |HGCommandEnableBufferSetup| variable to a false (zero) value. Enabling
1657 1657 this mode simply provides the buffer variables mentioned above. The user
1658 1658 must explicitly include those in the |'statusline'| option if they are to
1659 1659 appear in the status line (but see |hgcommand-statusline| for a simple way
1660 1660 to do that).
1661 1661
1662 1662 ==============================================================================
1663 1663 9. Tips *hgcommand-tips*
1664 1664
1665 1665 9.1 Split window annotation, by Michael Anderson >
1666 1666
1667 1667 :nmap <Leader>hgN :vs<CR><C-w>h<Leader>hgn:vertical res 40<CR>
1668 1668 \ggdddd:set scb<CR>:set nowrap<CR><C-w>lgg:set scb<CR>
1669 1669 \:set nowrap<CR>
1670 1670 <
1671 1671
1672 1672 This splits the buffer vertically, puts an annotation on the left (minus
1673 1673 the header) with the width set to 40. An editable/normal copy is placed on
1674 1674 the right. The two versions are scroll locked so they move as one. and
1675 1675 wrapping is turned off so that the lines line up correctly. The advantages
1676 1676 are...
1677 1677
1678 1678 1) You get a versioning on the right.
1679 1679 2) You can still edit your own code.
1680 1680 3) Your own code still has syntax highlighting.
1681 1681
1682 1682 ==============================================================================
1683 1683
1684 1684 8. Known bugs *hgcommand-bugs*
1685 1685
1686 1686 Please let me know if you run across any.
1687 1687
1688 1688 HGVimDiff, when using the original (real) source buffer as one of the diff
1689 1689 buffers, uses some hacks to try to restore the state of the original buffer
1690 1690 when the scratch buffer containing the other version is destroyed. There
1691 1691 may still be bugs in here, depending on many configuration details.
1692 1692
1693 1693 ==============================================================================
1694 1694
1695 1695 9. TODO *hgcommand-todo*
1696 1696
1697 1697 Integrate symlink tracking once HG will support them.
1698 1698 ==============================================================================
1699 1699 === END_DOC
1700 1700 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1701 1701 " v im:tw=78:ts=8:ft=help:norl:
1702 1702 " vim600: set foldmethod=marker tabstop=8 shiftwidth=2 softtabstop=2 smartindent smarttab :
1703 1703 "fileencoding=iso-8859-15
@@ -1,868 +1,868
1 1 " VIM plugin for doing single, multi-patch or diff code reviews {{{
2 2 " Home: http://www.vim.org/scripts/script.php?script_id=1563
3 3
4 4 " Version : 0.2.2 "{{{
5 5 " Author : Manpreet Singh < junkblocker@yahoo.com >
6 6 " Copyright : 2006-2010 by Manpreet Singh
7 7 " License : This file is placed in the public domain.
8 8 " No warranties express or implied. Use at your own risk.
9 9 "
10 10 " Changelog :
11 11 "
12 12 " 0.2.2 - Security fixes by removing custom tempfile creation
13 13 " - Removed need for DiffReviewCleanup/PatchReviewCleanup
14 14 " - Better command execution error detection and display
15 15 " - Improved diff view and folding by ignoring modelines
16 16 " - Improved tab labels display
17 17 "
18 18 " 0.2.1 - Minor temp directory autodetection logic and cleanup
19 19 "
20 " 0.2 - Removed the need for filterdiff by implemeting it in pure vim script
20 " 0.2 - Removed the need for filterdiff by implementing it in pure vim script
21 21 " - Added DiffReview command for reverse (changed repository to
22 22 " pristine state) reviews.
23 23 " (PatchReview does pristine repository to patch review)
24 24 " - DiffReview does automatic detection and generation of diffs for
25 25 " various Source Control systems
26 26 " - Skip load if VIM 7.0 or higher unavailable
27 27 "
28 28 " 0.1 - First released
29 29 "}}}
30 30
31 31 " Documentation: "{{{
32 32 " ===========================================================================
33 33 " This plugin allows single or multiple, patch or diff based code reviews to
34 34 " be easily done in VIM. VIM has :diffpatch command to do single file reviews
35 35 " but a) can not handle patch files containing multiple patches or b) do
36 36 " automated diff generation for various version control systems. This plugin
37 37 " attempts to provide those functionalities. It opens each changed / added or
38 38 " removed file diff in new tabs.
39 39 "
40 40 " Installing:
41 41 "
42 42 " For a quick start, unzip patchreview.zip into your ~/.vim directory and
43 43 " restart Vim.
44 44 "
45 45 " Details:
46 46 "
47 47 " Requirements:
48 48 "
49 49 " 1) VIM 7.0 or higher built with +diff option.
50 50 "
51 51 " 2) A gnu compatible patch command installed. This is the standard patch
52 52 " command on Linux, Mac OS X, *BSD, Cygwin or /usr/bin/gpatch on newer
53 53 " Solaris.
54 54 "
55 55 " 3) Optional (but recommended for speed)
56 56 "
57 57 " Install patchutils ( http://cyberelk.net/tim/patchutils/ ) for your
58 58 " OS. For windows it is available from Cygwin
59 59 "
60 60 " http://www.cygwin.com
61 61 "
62 62 " or GnuWin32
63 63 "
64 64 " http://gnuwin32.sourceforge.net/
65 65 "
66 66 " Install:
67 67 "
68 68 " 1) Extract the zip in your $HOME/.vim or $VIM/vimfiles directory and
69 69 " restart vim. The directory location relevant to your platform can be
70 70 " seen by running :help add-global-plugin in vim.
71 71 "
72 72 " 2) Restart vim.
73 73 "
74 74 " Configuration:
75 75 "
76 76 " Optionally, specify the locations to these filterdiff and patch commands
77 77 " and location of a temporary directory to use in your .vimrc.
78 78 "
79 79 " let g:patchreview_patch = '/path/to/gnu/patch'
80 80 "
81 81 " " If you are using filterdiff
82 82 " let g:patchreview_filterdiff = '/path/to/filterdiff'
83 83 "
84 84 "
85 85 " Usage:
86 86 "
87 87 " Please see :help patchreview or :help diffreview for details.
88 88 "
89 89 ""}}}
90 90
91 91 " Enabled only during development
92 92 " unlet! g:loaded_patchreview " DEBUG
93 93 " unlet! g:patchreview_patch " DEBUG
94 94 " unlet! g:patchreview_filterdiff " DEBUG
95 95 " let g:patchreview_patch = 'patch' " DEBUG
96 96
97 97 if v:version < 700
98 98 finish
99 99 endif
100 100 if ! has('diff')
101 101 call confirm('patchreview.vim plugin needs (G)VIM built with +diff support to work.')
102 102 finish
103 103 endif
104 104
105 105 " load only once
106 106 if (! exists('g:patchreview_debug') && exists('g:loaded_patchreview')) || &compatible
107 107 finish
108 108 endif
109 109 let g:loaded_patchreview="0.2.2"
110 110
111 111 let s:msgbufname = '-PatchReviewMessages-'
112 112
113 113 function! <SID>Debug(str) "{{{
114 114 if exists('g:patchreview_debug')
115 115 Pecho 'DEBUG: ' . a:str
116 116 endif
117 117 endfunction
118 118 command! -nargs=+ -complete=expression Debug call s:Debug(<args>)
119 119 "}}}
120 120
121 121 function! <SID>PR_wipeMsgBuf() "{{{
122 122 let winnum = bufwinnr(s:msgbufname)
123 123 if winnum != -1 " If the window is already open, jump to it
124 124 let cur_winnr = winnr()
125 125 if winnr() != winnum
126 126 exe winnum . 'wincmd w'
127 127 exe 'bw'
128 128 exe cur_winnr . 'wincmd w'
129 129 endif
130 130 endif
131 131 endfunction
132 132 "}}}
133 133
134 134 function! <SID>Pecho(...) "{{{
135 135 " Usage: Pecho(msg, [return_to_original_window_flag])
136 136 " default return_to_original_window_flag = 0
137 137 "
138 138 let cur_winnr = winnr()
139 139 let winnum = bufwinnr(s:msgbufname)
140 140 if winnum != -1 " If the window is already open, jump to it
141 141 if winnr() != winnum
142 142 exe winnum . 'wincmd w'
143 143 endif
144 144 else
145 145 let bufnum = bufnr(s:msgbufname)
146 146 if bufnum == -1
147 147 let wcmd = s:msgbufname
148 148 else
149 149 let wcmd = '+buffer' . bufnum
150 150 endif
151 151 exe 'silent! botright 5split ' . wcmd
152 152 endif
153 153 setlocal modifiable
154 154 setlocal buftype=nofile
155 155 setlocal bufhidden=delete
156 156 setlocal noswapfile
157 157 setlocal nowrap
158 158 setlocal nobuflisted
159 159 if a:0 != 0
160 160 silent! $put =a:1
161 161 endif
162 162 exe ':$'
163 163 setlocal nomodifiable
164 164 if a:0 > 1 && a:2
165 165 exe cur_winnr . 'wincmd w'
166 166 endif
167 167 endfunction
168 168
169 169 command! -nargs=+ -complete=expression Pecho call s:Pecho(<args>)
170 170 "}}}
171 171
172 172 function! <SID>PR_checkBinary(BinaryName) "{{{
173 173 " Verify that BinaryName is specified or available
174 174 if ! exists('g:patchreview_' . a:BinaryName)
175 175 if executable(a:BinaryName)
176 176 let g:patchreview_{a:BinaryName} = a:BinaryName
177 177 return 1
178 178 else
179 179 Pecho 'g:patchreview_' . a:BinaryName . ' is not defined and ' . a:BinaryName . ' command could not be found on path.'
180 180 Pecho 'Please define it in your .vimrc.'
181 181 return 0
182 182 endif
183 183 elseif ! executable(g:patchreview_{a:BinaryName})
184 184 Pecho 'Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a:BinaryName} . '] is not executable.'
185 185 return 0
186 186 else
187 187 return 1
188 188 endif
189 189 endfunction
190 190 "}}}
191 191
192 192 function! <SID>ExtractDiffsNative(...) "{{{
193 193 " Sets g:patches = {'reason':'', 'patch':[
194 194 " {
195 195 " 'filename': filepath
196 196 " 'type' : '+' | '-' | '!'
197 197 " 'content' : patch text for this file
198 198 " },
199 199 " ...
200 200 " ]}
201 201 let g:patches = {'reason' : '', 'patch' : []}
202 202 " TODO : User pointers into lines list rather then use collect
203 203 if a:0 == 0
204 204 let g:patches['reason'] = "ExtractDiffsNative expects at least a patchfile argument"
205 205 return
206 206 endif
207 207 let patchfile = expand(a:1, ':p')
208 208 if a:0 > 1
209 209 let patch = a:2
210 210 endif
211 211 if ! filereadable(patchfile)
212 212 let g:patches['reason'] = "File " . patchfile . " is not readable"
213 213 return
214 214 endif
215 215 unlet! filterdiffcmd
216 216 let filterdiffcmd = '' . g:patchreview_filterdiff . ' --list -s ' . patchfile
217 217 let fileslist = split(system(filterdiffcmd), '[\r\n]')
218 218 for filewithchangetype in fileslist
219 219 if filewithchangetype !~ '^[!+-] '
220 220 Pecho '*** Skipping review generation due to unknown change for [' . filewithchangetype . ']'
221 221 continue
222 222 endif
223 223
224 224 unlet! this_patch
225 225 let this_patch = {}
226 226
227 227 unlet! relpath
228 228 let relpath = substitute(filewithchangetype, '^. ', '', '')
229 229
230 230 let this_patch['filename'] = relpath
231 231
232 232 if filewithchangetype =~ '^! '
233 233 let this_patch['type'] = '!'
234 234 elseif filewithchangetype =~ '^+ '
235 235 let this_patch['type'] = '+'
236 236 elseif filewithchangetype =~ '^- '
237 237 let this_patch['type'] = '-'
238 238 endif
239 239
240 240 unlet! filterdiffcmd
241 241 let filterdiffcmd = '' . g:patchreview_filterdiff . ' -i ' . relpath . ' ' . patchfile
242 242 let this_patch['content'] = split(system(filterdiffcmd), '[\n\r]')
243 243 let g:patches['patch'] += [this_patch]
244 244 Debug "Patch collected for " . relpath
245 245 endfor
246 246 endfunction
247 247 "}}}
248 248
249 249 function! <SID>ExtractDiffsPureVim(...) "{{{
250 250 " Sets g:patches = {'reason':'', 'patch':[
251 251 " {
252 252 " 'filename': filepath
253 253 " 'type' : '+' | '-' | '!'
254 254 " 'content' : patch text for this file
255 255 " },
256 256 " ...
257 257 " ]}
258 258 let g:patches = {'reason' : '', 'patch' : []}
259 259 " TODO : User pointers into lines list rather then use collect
260 260 if a:0 == 0
261 261 let g:patches['reason'] = "ExtractDiffsPureVim expects at least a patchfile argument"
262 262 return
263 263 endif
264 264 let patchfile = expand(a:1, ':p')
265 265 if a:0 > 1
266 266 let patch = a:2
267 267 endif
268 268 if ! filereadable(patchfile)
269 269 let g:patches['reason'] = "File " . patchfile . " is not readable"
270 270 return
271 271 endif
272 272 call s:PR_wipeMsgBuf()
273 273 let collect = []
274 274 let linum = 0
275 275 let lines = readfile(patchfile)
276 276 let linescount = len(lines)
277 277 State 'START'
278 278 while linum < linescount
279 279 let line = lines[linum]
280 280 let linum += 1
281 281 if State() == 'START'
282 282 let mat = matchlist(line, '^--- \([^\t]\+\).*$')
283 283 if ! empty(mat) && mat[1] != ''
284 284 State 'MAYBE_UNIFIED_DIFF'
285 285 let p_first_file = mat[1]
286 286 let collect = [line]
287 287 Debug line . State()
288 288 continue
289 289 endif
290 290 let mat = matchlist(line, '^\*\*\* \([^\t]\+\).*$')
291 291 if ! empty(mat) && mat[1] != ''
292 292 State 'MAYBE_CONTEXT_DIFF'
293 293 let p_first_file = mat[1]
294 294 let collect = [line]
295 295 Debug line . State()
296 296 continue
297 297 endif
298 298 continue
299 299 elseif State() == 'MAYBE_CONTEXT_DIFF'
300 300 let mat = matchlist(line, '^--- \([^\t]\+\).*$')
301 301 if empty(mat) || mat[1] == ''
302 302 State 'START'
303 303 let linum -= 1
304 304 continue
305 305 Debug 'Back to square one ' . line()
306 306 endif
307 307 let p_second_file = mat[1]
308 308 if p_first_file == '/dev/null'
309 309 if p_second_file == '/dev/null'
310 310 let g:patches['reason'] = "Malformed diff found at line " . linum
311 311 return
312 312 endif
313 313 let p_type = '+'
314 314 let filepath = p_second_file
315 315 else
316 316 if p_second_file == '/dev/null'
317 317 let p_type = '-'
318 318 let filepath = p_first_file
319 319 else
320 320 let p_type = '!'
321 321 let filepath = p_first_file
322 322 endif
323 323 endif
324 324 State 'EXPECT_15_STARS'
325 325 let collect += [line]
326 326 Debug line . State()
327 327 elseif State() == 'EXPECT_15_STARS'
328 328 if line !~ '^*\{15}$'
329 329 State 'START'
330 330 let linum -= 1
331 331 Debug line . State()
332 332 continue
333 333 endif
334 334 State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
335 335 let collect += [line]
336 336 Debug line . State()
337 337 elseif State() == 'EXPECT_CONTEXT_CHUNK_HEADER_1'
338 338 let mat = matchlist(line, '^\*\*\* \(\d\+,\)\?\(\d\+\) \*\*\*\*$')
339 339 if empty(mat) || mat[1] == ''
340 340 State 'START'
341 341 let linum -= 1
342 342 Debug line . State()
343 343 continue
344 344 endif
345 345 let collect += [line]
346 346 State 'SKIP_CONTEXT_STUFF_1'
347 347 Debug line . State()
348 348 continue
349 349 elseif State() == 'SKIP_CONTEXT_STUFF_1'
350 350 if line !~ '^[ !+].*$'
351 351 let mat = matchlist(line, '^--- \(\d\+\),\(\d\+\) ----$')
352 352 if ! empty(mat) && mat[1] != '' && mat[2] != ''
353 353 let goal_count = mat[2] - mat[1] + 1
354 354 let c_count = 0
355 355 State 'READ_CONTEXT_CHUNK'
356 356 let collect += [line]
357 357 Debug line . State() . " Goal count set to " . goal_count
358 358 continue
359 359 endif
360 360 State 'START'
361 361 let linum -= 1
362 362 Debug line . State()
363 363 continue
364 364 endif
365 365 let collect += [line]
366 366 continue
367 367 elseif State() == 'READ_CONTEXT_CHUNK'
368 368 let c_count += 1
369 369 if c_count == goal_count
370 370 let collect += [line]
371 371 State 'BACKSLASH_OR_CRANGE_EOF'
372 372 continue
373 373 else " goal not met yet
374 374 let mat = matchlist(line, '^\([\\!+ ]\).*$')
375 375 if empty(mat) || mat[1] == ''
376 376 let linum -= 1
377 377 State 'START'
378 378 Debug line . State()
379 379 continue
380 380 endif
381 381 let collect += [line]
382 382 continue
383 383 endif
384 384 elseif State() == 'BACKSLASH_OR_CRANGE_EOF'
385 385 if line =~ '^\\ No newline.*$' " XXX: Can we go to another chunk from here??
386 386 let collect += [line]
387 387 let this_patch = {}
388 388 let this_patch['filename'] = filepath
389 389 let this_patch['type'] = p_type
390 390 let this_patch['content'] = collect
391 391 let g:patches['patch'] += [this_patch]
392 392 Debug "Patch collected for " . filepath
393 393 State 'START'
394 394 continue
395 395 endif
396 396 if line =~ '^\*\{15}$'
397 397 let collect += [line]
398 398 State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
399 399 Debug line . State()
400 400 continue
401 401 endif
402 402 let this_patch = {}
403 403 let this_patch['filename'] = filepath
404 404 let this_patch['type'] = p_type
405 405 let this_patch['content'] = collect
406 406 let g:patches['patch'] += [this_patch]
407 407 let linum -= 1
408 408 State 'START'
409 409 Debug "Patch collected for " . filepath
410 410 Debug line . State()
411 411 continue
412 412 elseif State() == 'MAYBE_UNIFIED_DIFF'
413 413 let mat = matchlist(line, '^+++ \([^\t]\+\).*$')
414 414 if empty(mat) || mat[1] == ''
415 415 State 'START'
416 416 let linum -= 1
417 417 Debug line . State()
418 418 continue
419 419 endif
420 420 let p_second_file = mat[1]
421 421 if p_first_file == '/dev/null'
422 422 if p_second_file == '/dev/null'
423 423 let g:patches['reason'] = "Malformed diff found at line " . linum
424 424 return
425 425 endif
426 426 let p_type = '+'
427 427 let filepath = p_second_file
428 428 else
429 429 if p_second_file == '/dev/null'
430 430 let p_type = '-'
431 431 let filepath = p_first_file
432 432 else
433 433 let p_type = '!'
434 434 let filepath = p_first_file
435 435 endif
436 436 endif
437 437 State 'EXPECT_UNIFIED_RANGE_CHUNK'
438 438 let collect += [line]
439 439 Debug line . State()
440 440 continue
441 441 elseif State() == 'EXPECT_UNIFIED_RANGE_CHUNK'
442 442 let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
443 443 if ! empty(mat)
444 444 let old_goal_count = mat[2]
445 445 let new_goal_count = mat[4]
446 446 let o_count = 0
447 447 let n_count = 0
448 448 Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
449 449 State 'READ_UNIFIED_CHUNK'
450 450 let collect += [line]
451 451 Debug line . State()
452 452 continue
453 453 endif
454 454 State 'START'
455 455 Debug line . State()
456 456 continue
457 457 elseif State() == 'READ_UNIFIED_CHUNK'
458 458 if o_count == old_goal_count && n_count == new_goal_count
459 459 if line =~ '^\\.*$' " XXX: Can we go to another chunk from here??
460 460 let collect += [line]
461 461 let this_patch = {}
462 462 let this_patch['filename'] = filepath
463 463 let this_patch['type'] = p_type
464 464 let this_patch['content'] = collect
465 465 let g:patches['patch'] += [this_patch]
466 466 Debug "Patch collected for " . filepath
467 467 State 'START'
468 468 continue
469 469 endif
470 470 let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
471 471 if ! empty(mat)
472 472 let old_goal_count = mat[2]
473 473 let new_goal_count = mat[4]
474 474 let o_count = 0
475 475 let n_count = 0
476 476 Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
477 477 let collect += [line]
478 478 Debug line . State()
479 479 continue
480 480 endif
481 481 let this_patch = {}
482 482 let this_patch['filename'] = filepath
483 483 let this_patch['type'] = p_type
484 484 let this_patch['content'] = collect
485 485 let g:patches['patch'] += [this_patch]
486 486 Debug "Patch collected for " . filepath
487 487 let linum -= 1
488 488 State 'START'
489 489 Debug line . State()
490 490 continue
491 491 else " goal not met yet
492 492 let mat = matchlist(line, '^\([\\+ -]\).*$')
493 493 if empty(mat) || mat[1] == ''
494 494 let linum -= 1
495 495 State 'START'
496 496 continue
497 497 endif
498 498 let chr = mat[1]
499 499 if chr == '+'
500 500 let n_count += 1
501 501 endif
502 502 if chr == ' '
503 503 let o_count += 1
504 504 let n_count += 1
505 505 endif
506 506 if chr == '-'
507 507 let o_count += 1
508 508 endif
509 509 let collect += [line]
510 510 Debug line . State()
511 511 continue
512 512 endif
513 513 else
514 514 let g:patches['reason'] = "Internal error: Do not use the plugin anymore and if possible please send the diff or patch file you tried it with to Manpreet Singh <junkblocker@yahoo.com>"
515 515 return
516 516 endif
517 517 endwhile
518 518 "Pecho State()
519 519 if (State() == 'READ_CONTEXT_CHUNK' && c_count == goal_count) || (State() == 'READ_UNIFIED_CHUNK' && n_count == new_goal_count && o_count == old_goal_count)
520 520 let this_patch = {}
521 521 let this_patch['filename'] = filepath
522 522 let this_patch['type'] = p_type
523 523 let this_patch['content'] = collect
524 524 let g:patches['patch'] += [this_patch]
525 525 Debug "Patch collected for " . filepath
526 526 endif
527 527 return
528 528 endfunction
529 529 "}}}
530 530
531 531 function! State(...) " For easy manipulation of diff extraction state "{{{
532 532 if a:0 != 0
533 533 let s:STATE = a:1
534 534 else
535 535 if ! exists('s:STATE')
536 536 let s:STATE = 'START'
537 537 endif
538 538 return s:STATE
539 539 endif
540 540 endfunction
541 541 com! -nargs=+ -complete=expression State call State(<args>)
542 542 "}}}
543 543
544 544 function! <SID>PatchReview(...) "{{{
545 545 let s:save_shortmess = &shortmess
546 546 let s:save_aw = &autowrite
547 547 let s:save_awa = &autowriteall
548 548 set shortmess=aW
549 549 call s:PR_wipeMsgBuf()
550 550 let s:reviewmode = 'patch'
551 551 call s:_GenericReview(a:000)
552 552 let &autowriteall = s:save_awa
553 553 let &autowrite = s:save_aw
554 554 let &shortmess = s:save_shortmess
555 555 endfunction
556 556 "}}}
557 557
558 558 function! <SID>_GenericReview(argslist) "{{{
559 559 " diff mode:
560 560 " arg1 = patchfile
561 561 " arg2 = strip count
562 562 " patch mode:
563 563 " arg1 = patchfile
564 564 " arg2 = strip count
565 565 " arg3 = directory
566 566
567 567 " VIM 7+ required
568 568 if version < 700
569 569 Pecho 'This plugin needs VIM 7 or higher'
570 570 return
571 571 endif
572 572
573 573 " +diff required
574 574 if ! has('diff')
575 575 Pecho 'This plugin needs VIM built with +diff feature.'
576 576 return
577 577 endif
578 578
579 579
580 580 if s:reviewmode == 'diff'
581 581 let patch_R_option = ' -t -R '
582 582 elseif s:reviewmode == 'patch'
583 583 let patch_R_option = ''
584 584 else
585 585 Pecho 'Fatal internal error in patchreview.vim plugin'
586 586 return
587 587 endif
588 588
589 589 " Check passed arguments
590 590 if len(a:argslist) == 0
591 591 Pecho 'PatchReview command needs at least one argument specifying a patchfile path.'
592 592 return
593 593 endif
594 594 let StripCount = 0
595 595 if len(a:argslist) >= 1 && ((s:reviewmode == 'patch' && len(a:argslist) <= 3) || (s:reviewmode == 'diff' && len(a:argslist) == 2))
596 596 let PatchFilePath = expand(a:argslist[0], ':p')
597 597 if ! filereadable(PatchFilePath)
598 598 Pecho 'File [' . PatchFilePath . '] is not accessible.'
599 599 return
600 600 endif
601 601 if len(a:argslist) >= 2 && s:reviewmode == 'patch'
602 602 let s:SrcDirectory = expand(a:argslist[1], ':p')
603 603 if ! isdirectory(s:SrcDirectory)
604 604 Pecho '[' . s:SrcDirectory . '] is not a directory'
605 605 return
606 606 endif
607 607 try
608 608 " Command line has already escaped the path
609 609 exe 'cd ' . s:SrcDirectory
610 610 catch /^.*E344.*/
611 611 Pecho 'Could not change to directory [' . s:SrcDirectory . ']'
612 612 return
613 613 endtry
614 614 endif
615 615 if s:reviewmode == 'diff'
616 616 " passed in by default
617 617 let StripCount = eval(a:argslist[1])
618 618 elseif s:reviewmode == 'patch'
619 619 let StripCount = 1
620 620 " optional strip count
621 621 if len(a:argslist) == 3
622 622 let StripCount = eval(a:argslist[2])
623 623 endif
624 624 endif
625 625 else
626 626 if s:reviewmode == 'patch'
627 627 Pecho 'PatchReview command needs at most three arguments: patchfile path, optional source directory path and optional strip count.'
628 628 elseif s:reviewmode == 'diff'
629 629 Pecho 'DiffReview command accepts no arguments.'
630 630 endif
631 631 return
632 632 endif
633 633
634 634 " Verify that patch command and temporary directory are available or specified
635 635 if ! s:PR_checkBinary('patch')
636 636 return
637 637 endif
638 638
639 639 " Requirements met, now execute
640 640 let PatchFilePath = fnamemodify(PatchFilePath, ':p')
641 641 if s:reviewmode == 'patch'
642 642 Pecho 'Patch file : ' . PatchFilePath
643 643 endif
644 644 Pecho 'Source directory: ' . getcwd()
645 645 Pecho '------------------'
646 646 if s:PR_checkBinary('filterdiff')
647 647 Debug "Using filterdiff"
648 648 call s:ExtractDiffsNative(PatchFilePath)
649 649 else
650 650 Debug "Using own diff extraction (slower)"
651 651 call s:ExtractDiffsPureVim(PatchFilePath)
652 652 endif
653 653 for patch in g:patches['patch']
654 654 if patch.type !~ '^[!+-]$'
655 655 Pecho '*** Skipping review generation due to unknown change [' . patch.type . ']', 1
656 656 continue
657 657 endif
658 658 unlet! relpath
659 659 let relpath = patch.filename
660 660 " XXX: svn diff and hg diff produce different kind of outputs, one requires
661 661 " XXX: stripping but the other doesn't. We need to take care of that
662 662 let stripmore = StripCount
663 663 let StrippedRelativeFilePath = relpath
664 664 while stripmore > 0
665 665 " strip one
666 666 let StrippedRelativeFilePath = substitute(StrippedRelativeFilePath, '^[^\\\/]\+[^\\\/]*[\\\/]' , '' , '')
667 667 let stripmore -= 1
668 668 endwhile
669 669 if patch.type == '!'
670 670 if s:reviewmode == 'patch'
671 671 let msgtype = 'Patch modifies file: '
672 672 elseif s:reviewmode == 'diff'
673 673 let msgtype = 'File has changes: '
674 674 endif
675 675 elseif patch.type == '+'
676 676 if s:reviewmode == 'patch'
677 677 let msgtype = 'Patch adds file : '
678 678 elseif s:reviewmode == 'diff'
679 679 let msgtype = 'New file : '
680 680 endif
681 681 elseif patch.type == '-'
682 682 if s:reviewmode == 'patch'
683 683 let msgtype = 'Patch removes file : '
684 684 elseif s:reviewmode == 'diff'
685 685 let msgtype = 'Removed file : '
686 686 endif
687 687 endif
688 688 let bufnum = bufnr(relpath)
689 689 if buflisted(bufnum) && getbufvar(bufnum, '&mod')
690 690 Pecho 'Old buffer for file [' . relpath . '] exists in modified state. Skipping review.', 1
691 691 continue
692 692 endif
693 693 let tmpname = tempname()
694 694
695 695 " write patch for patch.filename into tmpname
696 696 call writefile(patch.content, tmpname)
697 697 if patch.type == '+' && s:reviewmode == 'patch'
698 698 let inputfile = ''
699 699 let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
700 700 elseif patch.type == '+' && s:reviewmode == 'diff'
701 701 let inputfile = ''
702 702 unlet! patchcmd
703 703 else
704 704 let inputfile = expand(StrippedRelativeFilePath, ':p')
705 705 let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
706 706 endif
707 707 if exists('patchcmd')
708 708 let v:errmsg = ''
709 709 Debug patchcmd
710 710 silent exe patchcmd
711 711 if v:errmsg != '' || v:shell_error
712 712 Pecho 'ERROR: Could not execute patch command.'
713 713 Pecho 'ERROR: ' . patchcmd
714 714 Pecho 'ERROR: ' . v:errmsg
715 715 Pecho 'ERROR: Diff skipped.'
716 716 continue
717 717 endif
718 718 endif
719 719 call delete(tmpname)
720 720 let s:origtabpagenr = tabpagenr()
721 721 silent! exe 'tabedit ' . StrippedRelativeFilePath
722 722 if exists('patchcmd')
723 723 " modelines in loaded files mess with diff comparision
724 724 let s:keep_modeline=&modeline
725 725 let &modeline=0
726 726 silent! exe 'vert diffsplit ' . tmpname . '.file'
727 727 setlocal buftype=nofile
728 728 setlocal noswapfile
729 729 setlocal syntax=none
730 730 setlocal bufhidden=delete
731 731 setlocal nobuflisted
732 732 setlocal modifiable
733 733 setlocal nowrap
734 734 " Remove buffer name
735 735 silent! 0f
736 736 " Switch to original to get a nice tab title
737 737 silent! wincmd p
738 738 let &modeline=s:keep_modeline
739 739 else
740 740 silent! exe 'vnew'
741 741 endif
742 742 if filereadable(tmpname . '.file.rej')
743 743 silent! exe 'topleft 5split ' . tmpname . '.file.rej'
744 744 Pecho msgtype . '*** REJECTED *** ' . relpath, 1
745 745 else
746 746 Pecho msgtype . ' ' . relpath, 1
747 747 endif
748 748 silent! exe 'tabn ' . s:origtabpagenr
749 749 endfor
750 750 Pecho '-----'
751 751 Pecho 'Done.'
752 752
753 753 endfunction
754 754 "}}}
755 755
756 756 function! <SID>DiffReview(...) "{{{
757 757 let s:save_shortmess = &shortmess
758 758 set shortmess=aW
759 759 call s:PR_wipeMsgBuf()
760 760
761 761 let vcsdict = {
762 762 \'Mercurial' : {'dir' : '.hg', 'binary' : 'hg', 'diffargs' : 'diff' , 'strip' : 1},
763 763 \'Bazaar-NG' : {'dir' : '.bzr', 'binary' : 'bzr', 'diffargs' : 'diff' , 'strip' : 0},
764 764 \'monotone' : {'dir' : '_MTN', 'binary' : 'mtn', 'diffargs' : 'diff --unified', 'strip' : 0},
765 765 \'Subversion' : {'dir' : '.svn', 'binary' : 'svn', 'diffargs' : 'diff' , 'strip' : 0},
766 766 \'cvs' : {'dir' : 'CVS', 'binary' : 'cvs', 'diffargs' : '-q diff -u' , 'strip' : 0},
767 767 \}
768 768
769 769 unlet! s:theDiffCmd
770 770 unlet! l:vcs
771 771 if ! exists('g:patchreview_diffcmd')
772 772 for key in keys(vcsdict)
773 773 if isdirectory(vcsdict[key]['dir'])
774 774 if ! s:PR_checkBinary(vcsdict[key]['binary'])
775 775 Pecho 'Current directory looks like a ' . vcsdict[key] . ' repository but ' . vcsdist[key]['binary'] . ' command was not found on path.'
776 776 let &shortmess = s:save_shortmess
777 777 return
778 778 else
779 779 let s:theDiffCmd = vcsdict[key]['binary'] . ' ' . vcsdict[key]['diffargs']
780 780 let strip = vcsdict[key]['strip']
781 781
782 782 Pecho 'Using [' . s:theDiffCmd . '] to generate diffs for this ' . key . ' review.'
783 783 let &shortmess = s:save_shortmess
784 784 let l:vcs = vcsdict[key]['binary']
785 785 break
786 786 endif
787 787 else
788 788 continue
789 789 endif
790 790 endfor
791 791 else
792 792 let s:theDiffCmd = g:patchreview_diffcmd
793 793 let strip = 0
794 794 endif
795 795 if ! exists('s:theDiffCmd')
796 796 Pecho 'Please define g:patchreview_diffcmd and make sure you are in a VCS controlled top directory.'
797 797 let &shortmess = s:save_shortmess
798 798 return
799 799 endif
800 800
801 801 let outfile = tempname()
802 802 let cmd = s:theDiffCmd . ' > "' . outfile . '"'
803 803 let v:errmsg = ''
804 804 let cout = system(cmd)
805 805 if v:errmsg == '' && exists('l:vcs') && l:vcs == 'cvs' && v:shell_error == 1
806 806 " Ignoring CVS non-error
807 807 elseif v:errmsg != '' || v:shell_error
808 808 Pecho v:errmsg
809 809 Pecho 'Could not execute [' . s:theDiffCmd . ']'
810 810 Pecho 'Error code: ' . v:shell_error
811 811 Pecho cout
812 812 Pecho 'Diff review aborted.'
813 813 let &shortmess = s:save_shortmess
814 814 return
815 815 endif
816 816 let s:reviewmode = 'diff'
817 817 call s:_GenericReview([outfile, strip])
818 818 let &shortmess = s:save_shortmess
819 819 endfunction
820 820 "}}}
821 821
822 822 " End user commands "{{{
823 823 "============================================================================
824 824 " :PatchReview
825 825 command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
826 826
827 827 " :DiffReview
828 828 command! -nargs=0 DiffReview call s:DiffReview()
829 829 "}}}
830 830
831 831 " Development "{{{
832 832 if exists('g:patchreview_debug')
833 833 " Tests
834 834 function! <SID>PRExtractTestNative(...)
835 835 "let patchfiles = glob(expand(a:1) . '/?*')
836 836 "for fname in split(patchfiles)
837 837 call s:PR_wipeMsgBuf()
838 838 let fname = a:1
839 839 call s:ExtractDiffsNative(fname)
840 840 for patch in g:patches['patch']
841 841 for line in patch.content
842 842 Pecho line
843 843 endfor
844 844 endfor
845 845 "endfor
846 846 endfunction
847 847
848 848 function! <SID>PRExtractTestVim(...)
849 849 "let patchfiles = glob(expand(a:1) . '/?*')
850 850 "for fname in split(patchfiles)
851 851 call s:PR_wipeMsgBuf()
852 852 let fname = a:1
853 853 call s:ExtractDiffsPureVim(fname)
854 854 for patch in g:patches['patch']
855 855 for line in patch.content
856 856 Pecho line
857 857 endfor
858 858 endfor
859 859 "endfor
860 860 endfunction
861 861
862 862 command! -nargs=+ -complete=file PRTestVim call s:PRExtractTestVim(<f-args>)
863 863 command! -nargs=+ -complete=file PRTestNative call s:PRExtractTestNative(<f-args>)
864 864 endif
865 865 "}}}
866 866
867 867 " modeline
868 868 " vim: set et fdl=0 fdm=marker fenc=latin ff=unix ft=vim sw=2 sts=0 ts=2 textwidth=78 nowrap :
@@ -1,1110 +1,1110
1 1 # -*- coding: utf-8 -*-
2 2 # $Id: manpage.py 6110 2009-08-31 14:40:33Z grubert $
3 3 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
4 4 # Copyright: This module is put into the public domain.
5 5
6 6 """
7 7 Simple man page writer for reStructuredText.
8 8
9 9 Man pages (short for "manual pages") contain system documentation on unix-like
10 10 systems. The pages are grouped in numbered sections:
11 11
12 12 1 executable programs and shell commands
13 13 2 system calls
14 14 3 library functions
15 15 4 special files
16 16 5 file formats
17 17 6 games
18 18 7 miscellaneous
19 19 8 system administration
20 20
21 21 Man pages are written *troff*, a text file formatting system.
22 22
23 23 See http://www.tldp.org/HOWTO/Man-Page for a start.
24 24
25 25 Man pages have no subsection only parts.
26 26 Standard parts
27 27
28 28 NAME ,
29 29 SYNOPSIS ,
30 30 DESCRIPTION ,
31 31 OPTIONS ,
32 32 FILES ,
33 33 SEE ALSO ,
34 34 BUGS ,
35 35
36 36 and
37 37
38 38 AUTHOR .
39 39
40 40 A unix-like system keeps an index of the DESCRIPTIONs, which is accesable
41 41 by the command whatis or apropos.
42 42
43 43 """
44 44
45 45 __docformat__ = 'reStructuredText'
46 46
47 47 import re
48 48
49 49 from docutils import nodes, writers, languages
50 50 try:
51 51 import roman
52 52 except ImportError:
53 53 from docutils.utils import roman
54 54 import inspect
55 55
56 56 FIELD_LIST_INDENT = 7
57 57 DEFINITION_LIST_INDENT = 7
58 58 OPTION_LIST_INDENT = 7
59 59 BLOCKQOUTE_INDENT = 3.5
60 60
61 61 # Define two macros so man/roff can calculate the
62 62 # indent/unindent margins by itself
63 63 MACRO_DEF = (r""".
64 64 .nr rst2man-indent-level 0
65 65 .
66 66 .de1 rstReportMargin
67 67 \\$1 \\n[an-margin]
68 68 level \\n[rst2man-indent-level]
69 69 level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
70 70 -
71 71 \\n[rst2man-indent0]
72 72 \\n[rst2man-indent1]
73 73 \\n[rst2man-indent2]
74 74 ..
75 75 .de1 INDENT
76 76 .\" .rstReportMargin pre:
77 77 . RS \\$1
78 78 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
79 79 . nr rst2man-indent-level +1
80 80 .\" .rstReportMargin post:
81 81 ..
82 82 .de UNINDENT
83 83 . RE
84 84 .\" indent \\n[an-margin]
85 85 .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
86 86 .nr rst2man-indent-level -1
87 87 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
88 88 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
89 89 ..
90 90 """)
91 91
92 92 class Writer(writers.Writer):
93 93
94 94 supported = ('manpage')
95 95 """Formats this writer supports."""
96 96
97 97 output = None
98 98 """Final translated form of `document`."""
99 99
100 100 def __init__(self):
101 101 writers.Writer.__init__(self)
102 102 self.translator_class = Translator
103 103
104 104 def translate(self):
105 105 visitor = self.translator_class(self.document)
106 106 self.document.walkabout(visitor)
107 107 self.output = visitor.astext()
108 108
109 109
110 110 class Table(object):
111 111 def __init__(self):
112 112 self._rows = []
113 113 self._options = ['center']
114 114 self._tab_char = '\t'
115 115 self._coldefs = []
116 116 def new_row(self):
117 117 self._rows.append([])
118 118 def append_separator(self, separator):
119 119 """Append the separator for table head."""
120 120 self._rows.append([separator])
121 121 def append_cell(self, cell_lines):
122 122 """cell_lines is an array of lines"""
123 123 start = 0
124 124 if len(cell_lines) > 0 and cell_lines[0] == '.sp\n':
125 125 start = 1
126 126 self._rows[-1].append(cell_lines[start:])
127 127 if len(self._coldefs) < len(self._rows[-1]):
128 128 self._coldefs.append('l')
129 129 def _minimize_cell(self, cell_lines):
130 130 """Remove leading and trailing blank and ``.sp`` lines"""
131 131 while (cell_lines and cell_lines[0] in ('\n', '.sp\n')):
132 132 del cell_lines[0]
133 133 while (cell_lines and cell_lines[-1] in ('\n', '.sp\n')):
134 134 del cell_lines[-1]
135 135 def as_list(self):
136 136 text = ['.TS\n']
137 137 text.append(' '.join(self._options) + ';\n')
138 138 text.append('|%s|.\n' % ('|'.join(self._coldefs)))
139 139 for row in self._rows:
140 140 # row = array of cells. cell = array of lines.
141 141 text.append('_\n') # line above
142 142 text.append('T{\n')
143 143 for i in range(len(row)):
144 144 cell = row[i]
145 145 self._minimize_cell(cell)
146 146 text.extend(cell)
147 147 if not text[-1].endswith('\n'):
148 148 text[-1] += '\n'
149 149 if i < len(row)-1:
150 150 text.append('T}'+self._tab_char+'T{\n')
151 151 else:
152 152 text.append('T}\n')
153 153 text.append('_\n')
154 154 text.append('.TE\n')
155 155 return text
156 156
157 157 class Translator(nodes.NodeVisitor):
158 158 """"""
159 159
160 160 words_and_spaces = re.compile(r'\S+| +|\n')
161 document_start = """Man page generated from reStructeredText."""
161 document_start = """Man page generated from reStructuredText."""
162 162
163 163 def __init__(self, document):
164 164 nodes.NodeVisitor.__init__(self, document)
165 165 self.settings = settings = document.settings
166 166 lcode = settings.language_code
167 167 arglen = len(inspect.getargspec(languages.get_language)[0])
168 168 if arglen == 2:
169 169 self.language = languages.get_language(lcode,
170 170 self.document.reporter)
171 171 else:
172 172 self.language = languages.get_language(lcode)
173 173 self.head = []
174 174 self.body = []
175 175 self.foot = []
176 176 self.section_level = 0
177 177 self.context = []
178 178 self.topic_class = ''
179 179 self.colspecs = []
180 180 self.compact_p = 1
181 181 self.compact_simple = None
182 182 # the list style "*" bullet or "#" numbered
183 183 self._list_char = []
184 184 # writing the header .TH and .SH NAME is postboned after
185 185 # docinfo.
186 186 self._docinfo = {
187 187 "title" : "", "title_upper": "",
188 188 "subtitle" : "",
189 189 "manual_section" : "", "manual_group" : "",
190 190 "author" : [],
191 191 "date" : "",
192 192 "copyright" : "",
193 193 "version" : "",
194 194 }
195 195 self._docinfo_keys = [] # a list to keep the sequence as in source.
196 196 self._docinfo_names = {} # to get name from text not normalized.
197 197 self._in_docinfo = None
198 198 self._active_table = None
199 199 self._in_literal = False
200 200 self.header_written = 0
201 201 self._line_block = 0
202 202 self.authors = []
203 203 self.section_level = 0
204 204 self._indent = [0]
205 205 # central definition of simple processing rules
206 206 # what to output on : visit, depart
207 207 # Do not use paragraph requests ``.PP`` because these set indentation.
208 208 # use ``.sp``. Remove superfluous ``.sp`` in ``astext``.
209 209 #
210 210 # Fonts are put on a stack, the top one is used.
211 211 # ``.ft P`` or ``\\fP`` pop from stack.
212 212 # ``B`` bold, ``I`` italic, ``R`` roman should be available.
213 213 # Hopefully ``C`` courier too.
214 214 self.defs = {
215 215 'indent' : ('.INDENT %.1f\n', '.UNINDENT\n'),
216 216 'definition_list_item' : ('.TP', ''),
217 217 'field_name' : ('.TP\n.B ', '\n'),
218 218 'literal' : ('\\fB', '\\fP'),
219 219 'literal_block' : ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'),
220 220
221 221 'option_list_item' : ('.TP\n', ''),
222 222
223 223 'reference' : (r'\%', r'\:'),
224 224 'emphasis': ('\\fI', '\\fP'),
225 225 'strong' : ('\\fB', '\\fP'),
226 226 'term' : ('\n.B ', '\n'),
227 227 'title_reference' : ('\\fI', '\\fP'),
228 228
229 229 'topic-title' : ('.SS ',),
230 230 'sidebar-title' : ('.SS ',),
231 231
232 232 'problematic' : ('\n.nf\n', '\n.fi\n'),
233 233 }
234 234 # NOTE don't specify the newline before a dot-command, but ensure
235 235 # it is there.
236 236
237 237 def comment_begin(self, text):
238 238 """Return commented version of the passed text WITHOUT end of
239 239 line/comment."""
240 240 prefix = '.\\" '
241 241 out_text = ''.join(
242 242 [(prefix + in_line + '\n')
243 243 for in_line in text.split('\n')])
244 244 return out_text
245 245
246 246 def comment(self, text):
247 247 """Return commented version of the passed text."""
248 248 return self.comment_begin(text)+'.\n'
249 249
250 250 def ensure_eol(self):
251 251 """Ensure the last line in body is terminated by new line."""
252 252 if self.body[-1][-1] != '\n':
253 253 self.body.append('\n')
254 254
255 255 def astext(self):
256 256 """Return the final formatted document as a string."""
257 257 if not self.header_written:
258 258 # ensure we get a ".TH" as viewers require it.
259 259 self.head.append(self.header())
260 260 # filter body
261 261 for i in xrange(len(self.body)-1, 0, -1):
262 262 # remove superfluous vertical gaps.
263 263 if self.body[i] == '.sp\n':
264 264 if self.body[i - 1][:4] in ('.BI ','.IP '):
265 265 self.body[i] = '.\n'
266 266 elif (self.body[i - 1][:3] == '.B ' and
267 267 self.body[i - 2][:4] == '.TP\n'):
268 268 self.body[i] = '.\n'
269 269 elif (self.body[i - 1] == '\n' and
270 270 self.body[i - 2][0] != '.' and
271 271 (self.body[i - 3][:7] == '.TP\n.B '
272 272 or self.body[i - 3][:4] == '\n.B ')
273 273 ):
274 274 self.body[i] = '.\n'
275 275 return ''.join(self.head + self.body + self.foot)
276 276
277 277 def deunicode(self, text):
278 278 text = text.replace(u'\xa0', '\\ ')
279 279 text = text.replace(u'\u2020', '\\(dg')
280 280 return text
281 281
282 282 def visit_Text(self, node):
283 283 text = node.astext()
284 284 text = text.replace('\\','\\e')
285 285 replace_pairs = [
286 286 (u'-', ur'\-'),
287 287 (u'\'', ur'\(aq'),
288 288 (u'´', ur'\''),
289 289 (u'`', ur'\(ga'),
290 290 ]
291 291 for (in_char, out_markup) in replace_pairs:
292 292 text = text.replace(in_char, out_markup)
293 293 # unicode
294 294 text = self.deunicode(text)
295 295 if self._in_literal:
296 296 # prevent interpretation of "." at line start
297 297 if text[0] == '.':
298 298 text = '\\&' + text
299 299 text = text.replace('\n.', '\n\\&.')
300 300 self.body.append(text)
301 301
302 302 def depart_Text(self, node):
303 303 pass
304 304
305 305 def list_start(self, node):
306 306 class enum_char(object):
307 307 enum_style = {
308 308 'bullet' : '\\(bu',
309 309 'emdash' : '\\(em',
310 310 }
311 311
312 312 def __init__(self, style):
313 313 self._style = style
314 314 if 'start' in node:
315 315 self._cnt = node['start'] - 1
316 316 else:
317 317 self._cnt = 0
318 318 self._indent = 2
319 319 if style == 'arabic':
320 320 # indentation depends on number of childrens
321 321 # and start value.
322 322 self._indent = len(str(len(node.children)))
323 323 self._indent += len(str(self._cnt)) + 1
324 324 elif style == 'loweralpha':
325 325 self._cnt += ord('a') - 1
326 326 self._indent = 3
327 327 elif style == 'upperalpha':
328 328 self._cnt += ord('A') - 1
329 329 self._indent = 3
330 330 elif style.endswith('roman'):
331 331 self._indent = 5
332 332
333 333 def next(self):
334 334 if self._style == 'bullet':
335 335 return self.enum_style[self._style]
336 336 elif self._style == 'emdash':
337 337 return self.enum_style[self._style]
338 338 self._cnt += 1
339 339 # TODO add prefix postfix
340 340 if self._style == 'arabic':
341 341 return "%d." % self._cnt
342 342 elif self._style in ('loweralpha', 'upperalpha'):
343 343 return "%c." % self._cnt
344 344 elif self._style.endswith('roman'):
345 345 res = roman.toRoman(self._cnt) + '.'
346 346 if self._style.startswith('upper'):
347 347 return res.upper()
348 348 return res.lower()
349 349 else:
350 350 return "%d." % self._cnt
351 351 def get_width(self):
352 352 return self._indent
353 353 def __repr__(self):
354 354 return 'enum_style-%s' % list(self._style)
355 355
356 356 if 'enumtype' in node:
357 357 self._list_char.append(enum_char(node['enumtype']))
358 358 else:
359 359 self._list_char.append(enum_char('bullet'))
360 360 if len(self._list_char) > 1:
361 361 # indent nested lists
362 362 self.indent(self._list_char[-2].get_width())
363 363 else:
364 364 self.indent(self._list_char[-1].get_width())
365 365
366 366 def list_end(self):
367 367 self.dedent()
368 368 self._list_char.pop()
369 369
370 370 def header(self):
371 371 tmpl = (".TH %(title_upper)s %(manual_section)s"
372 372 " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
373 373 ".SH NAME\n"
374 374 "%(title)s \- %(subtitle)s\n")
375 375 return tmpl % self._docinfo
376 376
377 377 def append_header(self):
378 378 """append header with .TH and .SH NAME"""
379 379 # NOTE before everything
380 380 # .TH title_upper section date source manual
381 381 if self.header_written:
382 382 return
383 383 self.body.append(self.header())
384 384 self.body.append(MACRO_DEF)
385 385 self.header_written = 1
386 386
387 387 def visit_address(self, node):
388 388 self.visit_docinfo_item(node, 'address')
389 389
390 390 def depart_address(self, node):
391 391 pass
392 392
393 393 def visit_admonition(self, node, name=None):
394 394 if name:
395 395 self.body.append('.IP %s\n' %
396 396 self.language.labels.get(name, name))
397 397
398 398 def depart_admonition(self, node):
399 399 self.body.append('.RE\n')
400 400
401 401 def visit_attention(self, node):
402 402 self.visit_admonition(node, 'attention')
403 403
404 404 depart_attention = depart_admonition
405 405
406 406 def visit_docinfo_item(self, node, name):
407 407 if name == 'author':
408 408 self._docinfo[name].append(node.astext())
409 409 else:
410 410 self._docinfo[name] = node.astext()
411 411 self._docinfo_keys.append(name)
412 412 raise nodes.SkipNode
413 413
414 414 def depart_docinfo_item(self, node):
415 415 pass
416 416
417 417 def visit_author(self, node):
418 418 self.visit_docinfo_item(node, 'author')
419 419
420 420 depart_author = depart_docinfo_item
421 421
422 422 def visit_authors(self, node):
423 423 # _author is called anyway.
424 424 pass
425 425
426 426 def depart_authors(self, node):
427 427 pass
428 428
429 429 def visit_block_quote(self, node):
430 430 # BUG/HACK: indent alway uses the _last_ indention,
431 431 # thus we need two of them.
432 432 self.indent(BLOCKQOUTE_INDENT)
433 433 self.indent(0)
434 434
435 435 def depart_block_quote(self, node):
436 436 self.dedent()
437 437 self.dedent()
438 438
439 439 def visit_bullet_list(self, node):
440 440 self.list_start(node)
441 441
442 442 def depart_bullet_list(self, node):
443 443 self.list_end()
444 444
445 445 def visit_caption(self, node):
446 446 pass
447 447
448 448 def depart_caption(self, node):
449 449 pass
450 450
451 451 def visit_caution(self, node):
452 452 self.visit_admonition(node, 'caution')
453 453
454 454 depart_caution = depart_admonition
455 455
456 456 def visit_citation(self, node):
457 457 num, text = node.astext().split(None, 1)
458 458 num = num.strip()
459 459 self.body.append('.IP [%s] 5\n' % num)
460 460
461 461 def depart_citation(self, node):
462 462 pass
463 463
464 464 def visit_citation_reference(self, node):
465 465 self.body.append('['+node.astext()+']')
466 466 raise nodes.SkipNode
467 467
468 468 def visit_classifier(self, node):
469 469 pass
470 470
471 471 def depart_classifier(self, node):
472 472 pass
473 473
474 474 def visit_colspec(self, node):
475 475 self.colspecs.append(node)
476 476
477 477 def depart_colspec(self, node):
478 478 pass
479 479
480 480 def write_colspecs(self):
481 481 self.body.append("%s.\n" % ('L '*len(self.colspecs)))
482 482
483 483 def visit_comment(self, node,
484 484 sub=re.compile('-(?=-)').sub):
485 485 self.body.append(self.comment(node.astext()))
486 486 raise nodes.SkipNode
487 487
488 488 def visit_contact(self, node):
489 489 self.visit_docinfo_item(node, 'contact')
490 490
491 491 depart_contact = depart_docinfo_item
492 492
493 493 def visit_container(self, node):
494 494 pass
495 495
496 496 def depart_container(self, node):
497 497 pass
498 498
499 499 def visit_compound(self, node):
500 500 pass
501 501
502 502 def depart_compound(self, node):
503 503 pass
504 504
505 505 def visit_copyright(self, node):
506 506 self.visit_docinfo_item(node, 'copyright')
507 507
508 508 def visit_danger(self, node):
509 509 self.visit_admonition(node, 'danger')
510 510
511 511 depart_danger = depart_admonition
512 512
513 513 def visit_date(self, node):
514 514 self.visit_docinfo_item(node, 'date')
515 515
516 516 def visit_decoration(self, node):
517 517 pass
518 518
519 519 def depart_decoration(self, node):
520 520 pass
521 521
522 522 def visit_definition(self, node):
523 523 pass
524 524
525 525 def depart_definition(self, node):
526 526 pass
527 527
528 528 def visit_definition_list(self, node):
529 529 self.indent(DEFINITION_LIST_INDENT)
530 530
531 531 def depart_definition_list(self, node):
532 532 self.dedent()
533 533
534 534 def visit_definition_list_item(self, node):
535 535 self.body.append(self.defs['definition_list_item'][0])
536 536
537 537 def depart_definition_list_item(self, node):
538 538 self.body.append(self.defs['definition_list_item'][1])
539 539
540 540 def visit_description(self, node):
541 541 pass
542 542
543 543 def depart_description(self, node):
544 544 pass
545 545
546 546 def visit_docinfo(self, node):
547 547 self._in_docinfo = 1
548 548
549 549 def depart_docinfo(self, node):
550 550 self._in_docinfo = None
551 551 # NOTE nothing should be written before this
552 552 self.append_header()
553 553
554 554 def visit_doctest_block(self, node):
555 555 self.body.append(self.defs['literal_block'][0])
556 556 self._in_literal = True
557 557
558 558 def depart_doctest_block(self, node):
559 559 self._in_literal = False
560 560 self.body.append(self.defs['literal_block'][1])
561 561
562 562 def visit_document(self, node):
563 563 # no blank line between comment and header.
564 564 self.body.append(self.comment(self.document_start).rstrip()+'\n')
565 565 # writing header is postboned
566 566 self.header_written = 0
567 567
568 568 def depart_document(self, node):
569 569 if self._docinfo['author']:
570 570 self.body.append('.SH AUTHOR\n%s\n'
571 571 % ', '.join(self._docinfo['author']))
572 572 skip = ('author', 'copyright', 'date',
573 573 'manual_group', 'manual_section',
574 574 'subtitle',
575 575 'title', 'title_upper', 'version')
576 576 for name in self._docinfo_keys:
577 577 if name == 'address':
578 578 self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % (
579 579 self.language.labels.get(name, name),
580 580 self.defs['indent'][0] % 0,
581 581 self.defs['indent'][0] % BLOCKQOUTE_INDENT,
582 582 self._docinfo[name],
583 583 self.defs['indent'][1],
584 584 self.defs['indent'][1]))
585 585 elif name not in skip:
586 586 if name in self._docinfo_names:
587 587 label = self._docinfo_names[name]
588 588 else:
589 589 label = self.language.labels.get(name, name)
590 590 self.body.append("\n%s: %s\n" % (label, self._docinfo[name]))
591 591 if self._docinfo['copyright']:
592 592 self.body.append('.SH COPYRIGHT\n%s\n'
593 593 % self._docinfo['copyright'])
594 594 self.body.append(self.comment(
595 595 'Generated by docutils manpage writer.\n'))
596 596
597 597 def visit_emphasis(self, node):
598 598 self.body.append(self.defs['emphasis'][0])
599 599
600 600 def depart_emphasis(self, node):
601 601 self.body.append(self.defs['emphasis'][1])
602 602
603 603 def visit_entry(self, node):
604 604 # a cell in a table row
605 605 if 'morerows' in node:
606 606 self.document.reporter.warning('"table row spanning" not supported',
607 607 base_node=node)
608 608 if 'morecols' in node:
609 609 self.document.reporter.warning(
610 610 '"table cell spanning" not supported', base_node=node)
611 611 self.context.append(len(self.body))
612 612
613 613 def depart_entry(self, node):
614 614 start = self.context.pop()
615 615 self._active_table.append_cell(self.body[start:])
616 616 del self.body[start:]
617 617
618 618 def visit_enumerated_list(self, node):
619 619 self.list_start(node)
620 620
621 621 def depart_enumerated_list(self, node):
622 622 self.list_end()
623 623
624 624 def visit_error(self, node):
625 625 self.visit_admonition(node, 'error')
626 626
627 627 depart_error = depart_admonition
628 628
629 629 def visit_field(self, node):
630 630 pass
631 631
632 632 def depart_field(self, node):
633 633 pass
634 634
635 635 def visit_field_body(self, node):
636 636 if self._in_docinfo:
637 637 name_normalized = self._field_name.lower().replace(" ","_")
638 638 self._docinfo_names[name_normalized] = self._field_name
639 639 self.visit_docinfo_item(node, name_normalized)
640 640 raise nodes.SkipNode
641 641
642 642 def depart_field_body(self, node):
643 643 pass
644 644
645 645 def visit_field_list(self, node):
646 646 self.indent(FIELD_LIST_INDENT)
647 647
648 648 def depart_field_list(self, node):
649 649 self.dedent()
650 650
651 651 def visit_field_name(self, node):
652 652 if self._in_docinfo:
653 653 self._field_name = node.astext()
654 654 raise nodes.SkipNode
655 655 else:
656 656 self.body.append(self.defs['field_name'][0])
657 657
658 658 def depart_field_name(self, node):
659 659 self.body.append(self.defs['field_name'][1])
660 660
661 661 def visit_figure(self, node):
662 662 self.indent(2.5)
663 663 self.indent(0)
664 664
665 665 def depart_figure(self, node):
666 666 self.dedent()
667 667 self.dedent()
668 668
669 669 def visit_footer(self, node):
670 670 self.document.reporter.warning('"footer" not supported',
671 671 base_node=node)
672 672
673 673 def depart_footer(self, node):
674 674 pass
675 675
676 676 def visit_footnote(self, node):
677 677 num, text = node.astext().split(None, 1)
678 678 num = num.strip()
679 679 self.body.append('.IP [%s] 5\n' % self.deunicode(num))
680 680
681 681 def depart_footnote(self, node):
682 682 pass
683 683
684 684 def footnote_backrefs(self, node):
685 685 self.document.reporter.warning('"footnote_backrefs" not supported',
686 686 base_node=node)
687 687
688 688 def visit_footnote_reference(self, node):
689 689 self.body.append('['+self.deunicode(node.astext())+']')
690 690 raise nodes.SkipNode
691 691
692 692 def depart_footnote_reference(self, node):
693 693 pass
694 694
695 695 def visit_generated(self, node):
696 696 pass
697 697
698 698 def depart_generated(self, node):
699 699 pass
700 700
701 701 def visit_header(self, node):
702 702 raise NotImplementedError, node.astext()
703 703
704 704 def depart_header(self, node):
705 705 pass
706 706
707 707 def visit_hint(self, node):
708 708 self.visit_admonition(node, 'hint')
709 709
710 710 depart_hint = depart_admonition
711 711
712 712 def visit_subscript(self, node):
713 713 self.body.append('\\s-2\\d')
714 714
715 715 def depart_subscript(self, node):
716 716 self.body.append('\\u\\s0')
717 717
718 718 def visit_superscript(self, node):
719 719 self.body.append('\\s-2\\u')
720 720
721 721 def depart_superscript(self, node):
722 722 self.body.append('\\d\\s0')
723 723
724 724 def visit_attribution(self, node):
725 725 self.body.append('\\(em ')
726 726
727 727 def depart_attribution(self, node):
728 728 self.body.append('\n')
729 729
730 730 def visit_image(self, node):
731 731 self.document.reporter.warning('"image" not supported',
732 732 base_node=node)
733 733 text = []
734 734 if 'alt' in node.attributes:
735 735 text.append(node.attributes['alt'])
736 736 if 'uri' in node.attributes:
737 737 text.append(node.attributes['uri'])
738 738 self.body.append('[image: %s]\n' % ('/'.join(text)))
739 739 raise nodes.SkipNode
740 740
741 741 def visit_important(self, node):
742 742 self.visit_admonition(node, 'important')
743 743
744 744 depart_important = depart_admonition
745 745
746 746 def visit_label(self, node):
747 747 # footnote and citation
748 748 if (isinstance(node.parent, nodes.footnote)
749 749 or isinstance(node.parent, nodes.citation)):
750 750 raise nodes.SkipNode
751 751 self.document.reporter.warning('"unsupported "label"',
752 752 base_node=node)
753 753 self.body.append('[')
754 754
755 755 def depart_label(self, node):
756 756 self.body.append(']\n')
757 757
758 758 def visit_legend(self, node):
759 759 pass
760 760
761 761 def depart_legend(self, node):
762 762 pass
763 763
764 764 # WHAT should we use .INDENT, .UNINDENT ?
765 765 def visit_line_block(self, node):
766 766 self._line_block += 1
767 767 if self._line_block == 1:
768 768 self.body.append('.sp\n')
769 769 self.body.append('.nf\n')
770 770 else:
771 771 self.body.append('.in +2\n')
772 772
773 773 def depart_line_block(self, node):
774 774 self._line_block -= 1
775 775 if self._line_block == 0:
776 776 self.body.append('.fi\n')
777 777 self.body.append('.sp\n')
778 778 else:
779 779 self.body.append('.in -2\n')
780 780
781 781 def visit_line(self, node):
782 782 pass
783 783
784 784 def depart_line(self, node):
785 785 self.body.append('\n')
786 786
787 787 def visit_list_item(self, node):
788 788 # man 7 man argues to use ".IP" instead of ".TP"
789 789 self.body.append('.IP %s %d\n' % (
790 790 self._list_char[-1].next(),
791 791 self._list_char[-1].get_width(),))
792 792
793 793 def depart_list_item(self, node):
794 794 pass
795 795
796 796 def visit_literal(self, node):
797 797 self.body.append(self.defs['literal'][0])
798 798
799 799 def depart_literal(self, node):
800 800 self.body.append(self.defs['literal'][1])
801 801
802 802 def visit_literal_block(self, node):
803 803 self.body.append(self.defs['literal_block'][0])
804 804 self._in_literal = True
805 805
806 806 def depart_literal_block(self, node):
807 807 self._in_literal = False
808 808 self.body.append(self.defs['literal_block'][1])
809 809
810 810 def visit_meta(self, node):
811 811 raise NotImplementedError, node.astext()
812 812
813 813 def depart_meta(self, node):
814 814 pass
815 815
816 816 def visit_note(self, node):
817 817 self.visit_admonition(node, 'note')
818 818
819 819 depart_note = depart_admonition
820 820
821 821 def indent(self, by=0.5):
822 822 # if we are in a section ".SH" there already is a .RS
823 823 step = self._indent[-1]
824 824 self._indent.append(by)
825 825 self.body.append(self.defs['indent'][0] % step)
826 826
827 827 def dedent(self):
828 828 self._indent.pop()
829 829 self.body.append(self.defs['indent'][1])
830 830
831 831 def visit_option_list(self, node):
832 832 self.indent(OPTION_LIST_INDENT)
833 833
834 834 def depart_option_list(self, node):
835 835 self.dedent()
836 836
837 837 def visit_option_list_item(self, node):
838 838 # one item of the list
839 839 self.body.append(self.defs['option_list_item'][0])
840 840
841 841 def depart_option_list_item(self, node):
842 842 self.body.append(self.defs['option_list_item'][1])
843 843
844 844 def visit_option_group(self, node):
845 845 # as one option could have several forms it is a group
846 846 # options without parameter bold only, .B, -v
847 847 # options with parameter bold italic, .BI, -f file
848 848 #
849 849 # we do not know if .B or .BI
850 850 self.context.append('.B') # blind guess
851 851 self.context.append(len(self.body)) # to be able to insert later
852 852 self.context.append(0) # option counter
853 853
854 854 def depart_option_group(self, node):
855 855 self.context.pop() # the counter
856 856 start_position = self.context.pop()
857 857 text = self.body[start_position:]
858 858 del self.body[start_position:]
859 859 self.body.append('%s%s\n' % (self.context.pop(), ''.join(text)))
860 860
861 861 def visit_option(self, node):
862 862 # each form of the option will be presented separately
863 863 if self.context[-1] > 0:
864 864 self.body.append(', ')
865 865 if self.context[-3] == '.BI':
866 866 self.body.append('\\')
867 867 self.body.append(' ')
868 868
869 869 def depart_option(self, node):
870 870 self.context[-1] += 1
871 871
872 872 def visit_option_string(self, node):
873 873 # do not know if .B or .BI
874 874 pass
875 875
876 876 def depart_option_string(self, node):
877 877 pass
878 878
879 879 def visit_option_argument(self, node):
880 880 self.context[-3] = '.BI' # bold/italic alternate
881 881 if node['delimiter'] != ' ':
882 882 self.body.append('\\fB%s ' % node['delimiter'])
883 883 elif self.body[len(self.body)-1].endswith('='):
884 884 # a blank only means no blank in output, just changing font
885 885 self.body.append(' ')
886 886 else:
887 887 # blank backslash blank, switch font then a blank
888 888 self.body.append(' \\ ')
889 889
890 890 def depart_option_argument(self, node):
891 891 pass
892 892
893 893 def visit_organization(self, node):
894 894 self.visit_docinfo_item(node, 'organization')
895 895
896 896 def depart_organization(self, node):
897 897 pass
898 898
899 899 def visit_paragraph(self, node):
900 900 # ``.PP`` : Start standard indented paragraph.
901 901 # ``.LP`` : Start block paragraph, all except the first.
902 902 # ``.P [type]`` : Start paragraph type.
903 # NOTE dont use paragraph starts because they reset indentation.
903 # NOTE don't use paragraph starts because they reset indentation.
904 904 # ``.sp`` is only vertical space
905 905 self.ensure_eol()
906 906 self.body.append('.sp\n')
907 907
908 908 def depart_paragraph(self, node):
909 909 self.body.append('\n')
910 910
911 911 def visit_problematic(self, node):
912 912 self.body.append(self.defs['problematic'][0])
913 913
914 914 def depart_problematic(self, node):
915 915 self.body.append(self.defs['problematic'][1])
916 916
917 917 def visit_raw(self, node):
918 918 if node.get('format') == 'manpage':
919 919 self.body.append(node.astext() + "\n")
920 920 # Keep non-manpage raw text out of output:
921 921 raise nodes.SkipNode
922 922
923 923 def visit_reference(self, node):
924 924 """E.g. link or email address."""
925 925 self.body.append(self.defs['reference'][0])
926 926
927 927 def depart_reference(self, node):
928 928 self.body.append(self.defs['reference'][1])
929 929
930 930 def visit_revision(self, node):
931 931 self.visit_docinfo_item(node, 'revision')
932 932
933 933 depart_revision = depart_docinfo_item
934 934
935 935 def visit_row(self, node):
936 936 self._active_table.new_row()
937 937
938 938 def depart_row(self, node):
939 939 pass
940 940
941 941 def visit_section(self, node):
942 942 self.section_level += 1
943 943
944 944 def depart_section(self, node):
945 945 self.section_level -= 1
946 946
947 947 def visit_status(self, node):
948 948 self.visit_docinfo_item(node, 'status')
949 949
950 950 depart_status = depart_docinfo_item
951 951
952 952 def visit_strong(self, node):
953 953 self.body.append(self.defs['strong'][0])
954 954
955 955 def depart_strong(self, node):
956 956 self.body.append(self.defs['strong'][1])
957 957
958 958 def visit_substitution_definition(self, node):
959 959 """Internal only."""
960 960 raise nodes.SkipNode
961 961
962 962 def visit_substitution_reference(self, node):
963 963 self.document.reporter.warning('"substitution_reference" not supported',
964 964 base_node=node)
965 965
966 966 def visit_subtitle(self, node):
967 967 if isinstance(node.parent, nodes.sidebar):
968 968 self.body.append(self.defs['strong'][0])
969 969 elif isinstance(node.parent, nodes.document):
970 970 self.visit_docinfo_item(node, 'subtitle')
971 971 elif isinstance(node.parent, nodes.section):
972 972 self.body.append(self.defs['strong'][0])
973 973
974 974 def depart_subtitle(self, node):
975 975 # document subtitle calls SkipNode
976 976 self.body.append(self.defs['strong'][1]+'\n.PP\n')
977 977
978 978 def visit_system_message(self, node):
979 979 # TODO add report_level
980 980 #if node['level'] < self.document.reporter['writer'].report_level:
981 981 # Level is too low to display:
982 982 # raise nodes.SkipNode
983 983 attr = {}
984 984 backref_text = ''
985 985 if node.hasattr('id'):
986 986 attr['name'] = node['id']
987 987 if node.hasattr('line'):
988 988 line = ', line %s' % node['line']
989 989 else:
990 990 line = ''
991 991 self.body.append('.IP "System Message: %s/%s (%s:%s)"\n'
992 992 % (node['type'], node['level'], node['source'], line))
993 993
994 994 def depart_system_message(self, node):
995 995 pass
996 996
997 997 def visit_table(self, node):
998 998 self._active_table = Table()
999 999
1000 1000 def depart_table(self, node):
1001 1001 self.ensure_eol()
1002 1002 self.body.extend(self._active_table.as_list())
1003 1003 self._active_table = None
1004 1004
1005 1005 def visit_target(self, node):
1006 1006 # targets are in-document hyper targets, without any use for man-pages.
1007 1007 raise nodes.SkipNode
1008 1008
1009 1009 def visit_tbody(self, node):
1010 1010 pass
1011 1011
1012 1012 def depart_tbody(self, node):
1013 1013 pass
1014 1014
1015 1015 def visit_term(self, node):
1016 1016 self.body.append(self.defs['term'][0])
1017 1017
1018 1018 def depart_term(self, node):
1019 1019 self.body.append(self.defs['term'][1])
1020 1020
1021 1021 def visit_tgroup(self, node):
1022 1022 pass
1023 1023
1024 1024 def depart_tgroup(self, node):
1025 1025 pass
1026 1026
1027 1027 def visit_thead(self, node):
1028 1028 # MAYBE double line '='
1029 1029 pass
1030 1030
1031 1031 def depart_thead(self, node):
1032 1032 # MAYBE double line '='
1033 1033 pass
1034 1034
1035 1035 def visit_tip(self, node):
1036 1036 self.visit_admonition(node, 'tip')
1037 1037
1038 1038 depart_tip = depart_admonition
1039 1039
1040 1040 def visit_title(self, node):
1041 1041 if isinstance(node.parent, nodes.topic):
1042 1042 self.body.append(self.defs['topic-title'][0])
1043 1043 elif isinstance(node.parent, nodes.sidebar):
1044 1044 self.body.append(self.defs['sidebar-title'][0])
1045 1045 elif isinstance(node.parent, nodes.admonition):
1046 1046 self.body.append('.IP "')
1047 1047 elif self.section_level == 0:
1048 1048 self._docinfo['title'] = node.astext()
1049 1049 # document title for .TH
1050 1050 self._docinfo['title_upper'] = node.astext().upper()
1051 1051 raise nodes.SkipNode
1052 1052 elif self.section_level == 1:
1053 1053 self.body.append('.SH ')
1054 1054 for n in node.traverse(nodes.Text):
1055 1055 n.parent.replace(n, nodes.Text(n.astext().upper()))
1056 1056 else:
1057 1057 self.body.append('.SS ')
1058 1058
1059 1059 def depart_title(self, node):
1060 1060 if isinstance(node.parent, nodes.admonition):
1061 1061 self.body.append('"')
1062 1062 self.body.append('\n')
1063 1063
1064 1064 def visit_title_reference(self, node):
1065 1065 """inline citation reference"""
1066 1066 self.body.append(self.defs['title_reference'][0])
1067 1067
1068 1068 def depart_title_reference(self, node):
1069 1069 self.body.append(self.defs['title_reference'][1])
1070 1070
1071 1071 def visit_topic(self, node):
1072 1072 pass
1073 1073
1074 1074 def depart_topic(self, node):
1075 1075 pass
1076 1076
1077 1077 def visit_sidebar(self, node):
1078 1078 pass
1079 1079
1080 1080 def depart_sidebar(self, node):
1081 1081 pass
1082 1082
1083 1083 def visit_rubric(self, node):
1084 1084 pass
1085 1085
1086 1086 def depart_rubric(self, node):
1087 1087 pass
1088 1088
1089 1089 def visit_transition(self, node):
1090 1090 # .PP Begin a new paragraph and reset prevailing indent.
1091 1091 # .sp N leaves N lines of blank space.
1092 1092 # .ce centers the next line
1093 1093 self.body.append('\n.sp\n.ce\n----\n')
1094 1094
1095 1095 def depart_transition(self, node):
1096 1096 self.body.append('\n.ce 0\n.sp\n')
1097 1097
1098 1098 def visit_version(self, node):
1099 1099 self.visit_docinfo_item(node, 'version')
1100 1100
1101 1101 def visit_warning(self, node):
1102 1102 self.visit_admonition(node, 'warning')
1103 1103
1104 1104 depart_warning = depart_admonition
1105 1105
1106 1106 def unimplemented_visit(self, node):
1107 1107 raise NotImplementedError('visiting unimplemented node type: %s'
1108 1108 % node.__class__.__name__)
1109 1109
1110 1110 # vim: set fileencoding=utf-8 et ts=4 ai :
@@ -1,915 +1,915
1 1 # bugzilla.py - bugzilla integration for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 # Copyright 2011-2 Jim Hague <jim.hague@acm.org>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''hooks for integrating with the Bugzilla bug tracker
10 10
11 11 This hook extension adds comments on bugs in Bugzilla when changesets
12 12 that refer to bugs by Bugzilla ID are seen. The comment is formatted using
13 13 the Mercurial template mechanism.
14 14
15 15 The bug references can optionally include an update for Bugzilla of the
16 16 hours spent working on the bug. Bugs can also be marked fixed.
17 17
18 18 Three basic modes of access to Bugzilla are provided:
19 19
20 20 1. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later.
21 21
22 22 2. Check data via the Bugzilla XMLRPC interface and submit bug change
23 23 via email to Bugzilla email interface. Requires Bugzilla 3.4 or later.
24 24
25 25 3. Writing directly to the Bugzilla database. Only Bugzilla installations
26 26 using MySQL are supported. Requires Python MySQLdb.
27 27
28 28 Writing directly to the database is susceptible to schema changes, and
29 29 relies on a Bugzilla contrib script to send out bug change
30 30 notification emails. This script runs as the user running Mercurial,
31 31 must be run on the host with the Bugzilla install, and requires
32 32 permission to read Bugzilla configuration details and the necessary
33 33 MySQL user and password to have full access rights to the Bugzilla
34 34 database. For these reasons this access mode is now considered
35 35 deprecated, and will not be updated for new Bugzilla versions going
36 36 forward. Only adding comments is supported in this access mode.
37 37
38 38 Access via XMLRPC needs a Bugzilla username and password to be specified
39 39 in the configuration. Comments are added under that username. Since the
40 40 configuration must be readable by all Mercurial users, it is recommended
41 41 that the rights of that user are restricted in Bugzilla to the minimum
42 42 necessary to add comments. Marking bugs fixed requires Bugzilla 4.0 and later.
43 43
44 44 Access via XMLRPC/email uses XMLRPC to query Bugzilla, but sends
45 45 email to the Bugzilla email interface to submit comments to bugs.
46 46 The From: address in the email is set to the email address of the Mercurial
47 47 user, so the comment appears to come from the Mercurial user. In the event
48 that the Mercurial user email is not recognised by Bugzilla as a Bugzilla
48 that the Mercurial user email is not recognized by Bugzilla as a Bugzilla
49 49 user, the email associated with the Bugzilla username used to log into
50 50 Bugzilla is used instead as the source of the comment. Marking bugs fixed
51 51 works on all supported Bugzilla versions.
52 52
53 53 Configuration items common to all access modes:
54 54
55 55 bugzilla.version
56 This access type to use. Values recognised are:
56 The access type to use. Values recognized are:
57 57
58 58 :``xmlrpc``: Bugzilla XMLRPC interface.
59 59 :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces.
60 60 :``3.0``: MySQL access, Bugzilla 3.0 and later.
61 61 :``2.18``: MySQL access, Bugzilla 2.18 and up to but not
62 62 including 3.0.
63 63 :``2.16``: MySQL access, Bugzilla 2.16 and up to but not
64 64 including 2.18.
65 65
66 66 bugzilla.regexp
67 67 Regular expression to match bug IDs for update in changeset commit message.
68 68 It must contain one "()" named group ``<ids>`` containing the bug
69 69 IDs separated by non-digit characters. It may also contain
70 70 a named group ``<hours>`` with a floating-point number giving the
71 71 hours worked on the bug. If no named groups are present, the first
72 72 "()" group is assumed to contain the bug IDs, and work time is not
73 73 updated. The default expression matches ``Bug 1234``, ``Bug no. 1234``,
74 74 ``Bug number 1234``, ``Bugs 1234,5678``, ``Bug 1234 and 5678`` and
75 75 variations thereof, followed by an hours number prefixed by ``h`` or
76 76 ``hours``, e.g. ``hours 1.5``. Matching is case insensitive.
77 77
78 78 bugzilla.fixregexp
79 79 Regular expression to match bug IDs for marking fixed in changeset
80 80 commit message. This must contain a "()" named group ``<ids>` containing
81 81 the bug IDs separated by non-digit characters. It may also contain
82 82 a named group ``<hours>`` with a floating-point number giving the
83 83 hours worked on the bug. If no named groups are present, the first
84 84 "()" group is assumed to contain the bug IDs, and work time is not
85 85 updated. The default expression matches ``Fixes 1234``, ``Fixes bug 1234``,
86 86 ``Fixes bugs 1234,5678``, ``Fixes 1234 and 5678`` and
87 87 variations thereof, followed by an hours number prefixed by ``h`` or
88 88 ``hours``, e.g. ``hours 1.5``. Matching is case insensitive.
89 89
90 90 bugzilla.fixstatus
91 91 The status to set a bug to when marking fixed. Default ``RESOLVED``.
92 92
93 93 bugzilla.fixresolution
94 94 The resolution to set a bug to when marking fixed. Default ``FIXED``.
95 95
96 96 bugzilla.style
97 97 The style file to use when formatting comments.
98 98
99 99 bugzilla.template
100 100 Template to use when formatting comments. Overrides style if
101 101 specified. In addition to the usual Mercurial keywords, the
102 102 extension specifies:
103 103
104 104 :``{bug}``: The Bugzilla bug ID.
105 105 :``{root}``: The full pathname of the Mercurial repository.
106 106 :``{webroot}``: Stripped pathname of the Mercurial repository.
107 107 :``{hgweb}``: Base URL for browsing Mercurial repositories.
108 108
109 109 Default ``changeset {node|short} in repo {root} refers to bug
110 110 {bug}.\\ndetails:\\n\\t{desc|tabindent}``
111 111
112 112 bugzilla.strip
113 113 The number of path separator characters to strip from the front of
114 114 the Mercurial repository path (``{root}`` in templates) to produce
115 115 ``{webroot}``. For example, a repository with ``{root}``
116 116 ``/var/local/my-project`` with a strip of 2 gives a value for
117 117 ``{webroot}`` of ``my-project``. Default 0.
118 118
119 119 web.baseurl
120 120 Base URL for browsing Mercurial repositories. Referenced from
121 121 templates as ``{hgweb}``.
122 122
123 123 Configuration items common to XMLRPC+email and MySQL access modes:
124 124
125 125 bugzilla.usermap
126 126 Path of file containing Mercurial committer email to Bugzilla user email
127 127 mappings. If specified, the file should contain one mapping per
128 128 line::
129 129
130 130 committer = Bugzilla user
131 131
132 132 See also the ``[usermap]`` section.
133 133
134 134 The ``[usermap]`` section is used to specify mappings of Mercurial
135 135 committer email to Bugzilla user email. See also ``bugzilla.usermap``.
136 136 Contains entries of the form ``committer = Bugzilla user``.
137 137
138 138 XMLRPC access mode configuration:
139 139
140 140 bugzilla.bzurl
141 141 The base URL for the Bugzilla installation.
142 142 Default ``http://localhost/bugzilla``.
143 143
144 144 bugzilla.user
145 145 The username to use to log into Bugzilla via XMLRPC. Default
146 146 ``bugs``.
147 147
148 148 bugzilla.password
149 149 The password for Bugzilla login.
150 150
151 151 XMLRPC+email access mode uses the XMLRPC access mode configuration items,
152 152 and also:
153 153
154 154 bugzilla.bzemail
155 155 The Bugzilla email address.
156 156
157 157 In addition, the Mercurial email settings must be configured. See the
158 158 documentation in hgrc(5), sections ``[email]`` and ``[smtp]``.
159 159
160 160 MySQL access mode configuration:
161 161
162 162 bugzilla.host
163 163 Hostname of the MySQL server holding the Bugzilla database.
164 164 Default ``localhost``.
165 165
166 166 bugzilla.db
167 167 Name of the Bugzilla database in MySQL. Default ``bugs``.
168 168
169 169 bugzilla.user
170 170 Username to use to access MySQL server. Default ``bugs``.
171 171
172 172 bugzilla.password
173 173 Password to use to access MySQL server.
174 174
175 175 bugzilla.timeout
176 176 Database connection timeout (seconds). Default 5.
177 177
178 178 bugzilla.bzuser
179 179 Fallback Bugzilla user name to record comments with, if changeset
180 180 committer cannot be found as a Bugzilla user.
181 181
182 182 bugzilla.bzdir
183 183 Bugzilla install directory. Used by default notify. Default
184 184 ``/var/www/html/bugzilla``.
185 185
186 186 bugzilla.notify
187 187 The command to run to get Bugzilla to send bug change notification
188 188 emails. Substitutes from a map with 3 keys, ``bzdir``, ``id`` (bug
189 189 id) and ``user`` (committer bugzilla email). Default depends on
190 190 version; from 2.18 it is "cd %(bzdir)s && perl -T
191 191 contrib/sendbugmail.pl %(id)s %(user)s".
192 192
193 193 Activating the extension::
194 194
195 195 [extensions]
196 196 bugzilla =
197 197
198 198 [hooks]
199 199 # run bugzilla hook on every change pulled or pushed in here
200 200 incoming.bugzilla = python:hgext.bugzilla.hook
201 201
202 202 Example configurations:
203 203
204 204 XMLRPC example configuration. This uses the Bugzilla at
205 205 ``http://my-project.org/bugzilla``, logging in as user
206 206 ``bugmail@my-project.org`` with password ``plugh``. It is used with a
207 207 collection of Mercurial repositories in ``/var/local/hg/repos/``,
208 208 with a web interface at ``http://my-project.org/hg``. ::
209 209
210 210 [bugzilla]
211 211 bzurl=http://my-project.org/bugzilla
212 212 user=bugmail@my-project.org
213 213 password=plugh
214 214 version=xmlrpc
215 215 template=Changeset {node|short} in {root|basename}.
216 216 {hgweb}/{webroot}/rev/{node|short}\\n
217 217 {desc}\\n
218 218 strip=5
219 219
220 220 [web]
221 221 baseurl=http://my-project.org/hg
222 222
223 223 XMLRPC+email example configuration. This uses the Bugzilla at
224 224 ``http://my-project.org/bugzilla``, logging in as user
225 225 ``bugmail@my-project.org`` with password ``plugh``. It is used with a
226 226 collection of Mercurial repositories in ``/var/local/hg/repos/``,
227 227 with a web interface at ``http://my-project.org/hg``. Bug comments
228 228 are sent to the Bugzilla email address
229 229 ``bugzilla@my-project.org``. ::
230 230
231 231 [bugzilla]
232 232 bzurl=http://my-project.org/bugzilla
233 233 user=bugmail@my-project.org
234 234 password=plugh
235 235 version=xmlrpc
236 236 bzemail=bugzilla@my-project.org
237 237 template=Changeset {node|short} in {root|basename}.
238 238 {hgweb}/{webroot}/rev/{node|short}\\n
239 239 {desc}\\n
240 240 strip=5
241 241
242 242 [web]
243 243 baseurl=http://my-project.org/hg
244 244
245 245 [usermap]
246 246 user@emaildomain.com=user.name@bugzilladomain.com
247 247
248 248 MySQL example configuration. This has a local Bugzilla 3.2 installation
249 249 in ``/opt/bugzilla-3.2``. The MySQL database is on ``localhost``,
250 250 the Bugzilla database name is ``bugs`` and MySQL is
251 251 accessed with MySQL username ``bugs`` password ``XYZZY``. It is used
252 252 with a collection of Mercurial repositories in ``/var/local/hg/repos/``,
253 253 with a web interface at ``http://my-project.org/hg``. ::
254 254
255 255 [bugzilla]
256 256 host=localhost
257 257 password=XYZZY
258 258 version=3.0
259 259 bzuser=unknown@domain.com
260 260 bzdir=/opt/bugzilla-3.2
261 261 template=Changeset {node|short} in {root|basename}.
262 262 {hgweb}/{webroot}/rev/{node|short}\\n
263 263 {desc}\\n
264 264 strip=5
265 265
266 266 [web]
267 267 baseurl=http://my-project.org/hg
268 268
269 269 [usermap]
270 270 user@emaildomain.com=user.name@bugzilladomain.com
271 271
272 272 All the above add a comment to the Bugzilla bug record of the form::
273 273
274 274 Changeset 3b16791d6642 in repository-name.
275 275 http://my-project.org/hg/repository-name/rev/3b16791d6642
276 276
277 277 Changeset commit comment. Bug 1234.
278 278 '''
279 279
280 280 from mercurial.i18n import _
281 281 from mercurial.node import short
282 282 from mercurial import cmdutil, mail, templater, util
283 283 import re, time, urlparse, xmlrpclib
284 284
285 285 testedwith = 'internal'
286 286
287 287 class bzaccess(object):
288 288 '''Base class for access to Bugzilla.'''
289 289
290 290 def __init__(self, ui):
291 291 self.ui = ui
292 292 usermap = self.ui.config('bugzilla', 'usermap')
293 293 if usermap:
294 294 self.ui.readconfig(usermap, sections=['usermap'])
295 295
296 296 def map_committer(self, user):
297 297 '''map name of committer to Bugzilla user name.'''
298 298 for committer, bzuser in self.ui.configitems('usermap'):
299 299 if committer.lower() == user.lower():
300 300 return bzuser
301 301 return user
302 302
303 303 # Methods to be implemented by access classes.
304 304 #
305 305 # 'bugs' is a dict keyed on bug id, where values are a dict holding
306 # updates to bug state. Recognised dict keys are:
306 # updates to bug state. Recognized dict keys are:
307 307 #
308 308 # 'hours': Value, float containing work hours to be updated.
309 309 # 'fix': If key present, bug is to be marked fixed. Value ignored.
310 310
311 311 def filter_real_bug_ids(self, bugs):
312 312 '''remove bug IDs that do not exist in Bugzilla from bugs.'''
313 313 pass
314 314
315 315 def filter_cset_known_bug_ids(self, node, bugs):
316 316 '''remove bug IDs where node occurs in comment text from bugs.'''
317 317 pass
318 318
319 319 def updatebug(self, bugid, newstate, text, committer):
320 320 '''update the specified bug. Add comment text and set new states.
321 321
322 322 If possible add the comment as being from the committer of
323 323 the changeset. Otherwise use the default Bugzilla user.
324 324 '''
325 325 pass
326 326
327 327 def notify(self, bugs, committer):
328 328 '''Force sending of Bugzilla notification emails.
329 329
330 330 Only required if the access method does not trigger notification
331 331 emails automatically.
332 332 '''
333 333 pass
334 334
335 335 # Bugzilla via direct access to MySQL database.
336 336 class bzmysql(bzaccess):
337 337 '''Support for direct MySQL access to Bugzilla.
338 338
339 339 The earliest Bugzilla version this is tested with is version 2.16.
340 340
341 341 If your Bugzilla is version 3.4 or above, you are strongly
342 342 recommended to use the XMLRPC access method instead.
343 343 '''
344 344
345 345 @staticmethod
346 346 def sql_buglist(ids):
347 347 '''return SQL-friendly list of bug ids'''
348 348 return '(' + ','.join(map(str, ids)) + ')'
349 349
350 350 _MySQLdb = None
351 351
352 352 def __init__(self, ui):
353 353 try:
354 354 import MySQLdb as mysql
355 355 bzmysql._MySQLdb = mysql
356 356 except ImportError, err:
357 357 raise util.Abort(_('python mysql support not available: %s') % err)
358 358
359 359 bzaccess.__init__(self, ui)
360 360
361 361 host = self.ui.config('bugzilla', 'host', 'localhost')
362 362 user = self.ui.config('bugzilla', 'user', 'bugs')
363 363 passwd = self.ui.config('bugzilla', 'password')
364 364 db = self.ui.config('bugzilla', 'db', 'bugs')
365 365 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
366 366 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
367 367 (host, db, user, '*' * len(passwd)))
368 368 self.conn = bzmysql._MySQLdb.connect(host=host,
369 369 user=user, passwd=passwd,
370 370 db=db,
371 371 connect_timeout=timeout)
372 372 self.cursor = self.conn.cursor()
373 373 self.longdesc_id = self.get_longdesc_id()
374 374 self.user_ids = {}
375 375 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
376 376
377 377 def run(self, *args, **kwargs):
378 378 '''run a query.'''
379 379 self.ui.note(_('query: %s %s\n') % (args, kwargs))
380 380 try:
381 381 self.cursor.execute(*args, **kwargs)
382 382 except bzmysql._MySQLdb.MySQLError:
383 383 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
384 384 raise
385 385
386 386 def get_longdesc_id(self):
387 387 '''get identity of longdesc field'''
388 388 self.run('select fieldid from fielddefs where name = "longdesc"')
389 389 ids = self.cursor.fetchall()
390 390 if len(ids) != 1:
391 391 raise util.Abort(_('unknown database schema'))
392 392 return ids[0][0]
393 393
394 394 def filter_real_bug_ids(self, bugs):
395 395 '''filter not-existing bugs from set.'''
396 396 self.run('select bug_id from bugs where bug_id in %s' %
397 397 bzmysql.sql_buglist(bugs.keys()))
398 398 existing = [id for (id,) in self.cursor.fetchall()]
399 399 for id in bugs.keys():
400 400 if id not in existing:
401 401 self.ui.status(_('bug %d does not exist\n') % id)
402 402 del bugs[id]
403 403
404 404 def filter_cset_known_bug_ids(self, node, bugs):
405 405 '''filter bug ids that already refer to this changeset from set.'''
406 406 self.run('''select bug_id from longdescs where
407 407 bug_id in %s and thetext like "%%%s%%"''' %
408 408 (bzmysql.sql_buglist(bugs.keys()), short(node)))
409 409 for (id,) in self.cursor.fetchall():
410 410 self.ui.status(_('bug %d already knows about changeset %s\n') %
411 411 (id, short(node)))
412 412 del bugs[id]
413 413
414 414 def notify(self, bugs, committer):
415 415 '''tell bugzilla to send mail.'''
416 416 self.ui.status(_('telling bugzilla to send mail:\n'))
417 417 (user, userid) = self.get_bugzilla_user(committer)
418 418 for id in bugs.keys():
419 419 self.ui.status(_(' bug %s\n') % id)
420 420 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
421 421 bzdir = self.ui.config('bugzilla', 'bzdir',
422 422 '/var/www/html/bugzilla')
423 423 try:
424 424 # Backwards-compatible with old notify string, which
425 425 # took one string. This will throw with a new format
426 426 # string.
427 427 cmd = cmdfmt % id
428 428 except TypeError:
429 429 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
430 430 self.ui.note(_('running notify command %s\n') % cmd)
431 431 fp = util.popen('(%s) 2>&1' % cmd)
432 432 out = fp.read()
433 433 ret = fp.close()
434 434 if ret:
435 435 self.ui.warn(out)
436 436 raise util.Abort(_('bugzilla notify command %s') %
437 437 util.explainexit(ret)[0])
438 438 self.ui.status(_('done\n'))
439 439
440 440 def get_user_id(self, user):
441 441 '''look up numeric bugzilla user id.'''
442 442 try:
443 443 return self.user_ids[user]
444 444 except KeyError:
445 445 try:
446 446 userid = int(user)
447 447 except ValueError:
448 448 self.ui.note(_('looking up user %s\n') % user)
449 449 self.run('''select userid from profiles
450 450 where login_name like %s''', user)
451 451 all = self.cursor.fetchall()
452 452 if len(all) != 1:
453 453 raise KeyError(user)
454 454 userid = int(all[0][0])
455 455 self.user_ids[user] = userid
456 456 return userid
457 457
458 458 def get_bugzilla_user(self, committer):
459 459 '''See if committer is a registered bugzilla user. Return
460 460 bugzilla username and userid if so. If not, return default
461 461 bugzilla username and userid.'''
462 462 user = self.map_committer(committer)
463 463 try:
464 464 userid = self.get_user_id(user)
465 465 except KeyError:
466 466 try:
467 467 defaultuser = self.ui.config('bugzilla', 'bzuser')
468 468 if not defaultuser:
469 469 raise util.Abort(_('cannot find bugzilla user id for %s') %
470 470 user)
471 471 userid = self.get_user_id(defaultuser)
472 472 user = defaultuser
473 473 except KeyError:
474 474 raise util.Abort(_('cannot find bugzilla user id for %s or %s')
475 475 % (user, defaultuser))
476 476 return (user, userid)
477 477
478 478 def updatebug(self, bugid, newstate, text, committer):
479 479 '''update bug state with comment text.
480 480
481 481 Try adding comment as committer of changeset, otherwise as
482 482 default bugzilla user.'''
483 483 if len(newstate) > 0:
484 484 self.ui.warn(_("Bugzilla/MySQL cannot update bug state\n"))
485 485
486 486 (user, userid) = self.get_bugzilla_user(committer)
487 487 now = time.strftime('%Y-%m-%d %H:%M:%S')
488 488 self.run('''insert into longdescs
489 489 (bug_id, who, bug_when, thetext)
490 490 values (%s, %s, %s, %s)''',
491 491 (bugid, userid, now, text))
492 492 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
493 493 values (%s, %s, %s, %s)''',
494 494 (bugid, userid, now, self.longdesc_id))
495 495 self.conn.commit()
496 496
497 497 class bzmysql_2_18(bzmysql):
498 498 '''support for bugzilla 2.18 series.'''
499 499
500 500 def __init__(self, ui):
501 501 bzmysql.__init__(self, ui)
502 502 self.default_notify = \
503 503 "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
504 504
505 505 class bzmysql_3_0(bzmysql_2_18):
506 506 '''support for bugzilla 3.0 series.'''
507 507
508 508 def __init__(self, ui):
509 509 bzmysql_2_18.__init__(self, ui)
510 510
511 511 def get_longdesc_id(self):
512 512 '''get identity of longdesc field'''
513 513 self.run('select id from fielddefs where name = "longdesc"')
514 514 ids = self.cursor.fetchall()
515 515 if len(ids) != 1:
516 516 raise util.Abort(_('unknown database schema'))
517 517 return ids[0][0]
518 518
519 519 # Bugzilla via XMLRPC interface.
520 520
521 521 class cookietransportrequest(object):
522 522 """A Transport request method that retains cookies over its lifetime.
523 523
524 524 The regular xmlrpclib transports ignore cookies. Which causes
525 525 a bit of a problem when you need a cookie-based login, as with
526 526 the Bugzilla XMLRPC interface.
527 527
528 528 So this is a helper for defining a Transport which looks for
529 529 cookies being set in responses and saves them to add to all future
530 530 requests.
531 531 """
532 532
533 533 # Inspiration drawn from
534 534 # http://blog.godson.in/2010/09/how-to-make-python-xmlrpclib-client.html
535 535 # http://www.itkovian.net/base/transport-class-for-pythons-xml-rpc-lib/
536 536
537 537 cookies = []
538 538 def send_cookies(self, connection):
539 539 if self.cookies:
540 540 for cookie in self.cookies:
541 541 connection.putheader("Cookie", cookie)
542 542
543 543 def request(self, host, handler, request_body, verbose=0):
544 544 self.verbose = verbose
545 545 self.accept_gzip_encoding = False
546 546
547 547 # issue XML-RPC request
548 548 h = self.make_connection(host)
549 549 if verbose:
550 550 h.set_debuglevel(1)
551 551
552 552 self.send_request(h, handler, request_body)
553 553 self.send_host(h, host)
554 554 self.send_cookies(h)
555 555 self.send_user_agent(h)
556 556 self.send_content(h, request_body)
557 557
558 558 # Deal with differences between Python 2.4-2.6 and 2.7.
559 559 # In the former h is a HTTP(S). In the latter it's a
560 560 # HTTP(S)Connection. Luckily, the 2.4-2.6 implementation of
561 561 # HTTP(S) has an underlying HTTP(S)Connection, so extract
562 562 # that and use it.
563 563 try:
564 564 response = h.getresponse()
565 565 except AttributeError:
566 566 response = h._conn.getresponse()
567 567
568 568 # Add any cookie definitions to our list.
569 569 for header in response.msg.getallmatchingheaders("Set-Cookie"):
570 570 val = header.split(": ", 1)[1]
571 571 cookie = val.split(";", 1)[0]
572 572 self.cookies.append(cookie)
573 573
574 574 if response.status != 200:
575 575 raise xmlrpclib.ProtocolError(host + handler, response.status,
576 576 response.reason, response.msg.headers)
577 577
578 578 payload = response.read()
579 579 parser, unmarshaller = self.getparser()
580 580 parser.feed(payload)
581 581 parser.close()
582 582
583 583 return unmarshaller.close()
584 584
585 585 # The explicit calls to the underlying xmlrpclib __init__() methods are
586 586 # necessary. The xmlrpclib.Transport classes are old-style classes, and
587 587 # it turns out their __init__() doesn't get called when doing multiple
588 588 # inheritance with a new-style class.
589 589 class cookietransport(cookietransportrequest, xmlrpclib.Transport):
590 590 def __init__(self, use_datetime=0):
591 591 if util.safehasattr(xmlrpclib.Transport, "__init__"):
592 592 xmlrpclib.Transport.__init__(self, use_datetime)
593 593
594 594 class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport):
595 595 def __init__(self, use_datetime=0):
596 596 if util.safehasattr(xmlrpclib.Transport, "__init__"):
597 597 xmlrpclib.SafeTransport.__init__(self, use_datetime)
598 598
599 599 class bzxmlrpc(bzaccess):
600 600 """Support for access to Bugzilla via the Bugzilla XMLRPC API.
601 601
602 602 Requires a minimum Bugzilla version 3.4.
603 603 """
604 604
605 605 def __init__(self, ui):
606 606 bzaccess.__init__(self, ui)
607 607
608 608 bzweb = self.ui.config('bugzilla', 'bzurl',
609 609 'http://localhost/bugzilla/')
610 610 bzweb = bzweb.rstrip("/") + "/xmlrpc.cgi"
611 611
612 612 user = self.ui.config('bugzilla', 'user', 'bugs')
613 613 passwd = self.ui.config('bugzilla', 'password')
614 614
615 615 self.fixstatus = self.ui.config('bugzilla', 'fixstatus', 'RESOLVED')
616 616 self.fixresolution = self.ui.config('bugzilla', 'fixresolution',
617 617 'FIXED')
618 618
619 619 self.bzproxy = xmlrpclib.ServerProxy(bzweb, self.transport(bzweb))
620 620 ver = self.bzproxy.Bugzilla.version()['version'].split('.')
621 621 self.bzvermajor = int(ver[0])
622 622 self.bzverminor = int(ver[1])
623 623 self.bzproxy.User.login(dict(login=user, password=passwd))
624 624
625 625 def transport(self, uri):
626 626 if urlparse.urlparse(uri, "http")[0] == "https":
627 627 return cookiesafetransport()
628 628 else:
629 629 return cookietransport()
630 630
631 631 def get_bug_comments(self, id):
632 632 """Return a string with all comment text for a bug."""
633 633 c = self.bzproxy.Bug.comments(dict(ids=[id], include_fields=['text']))
634 634 return ''.join([t['text'] for t in c['bugs'][str(id)]['comments']])
635 635
636 636 def filter_real_bug_ids(self, bugs):
637 637 probe = self.bzproxy.Bug.get(dict(ids=sorted(bugs.keys()),
638 638 include_fields=[],
639 639 permissive=True))
640 640 for badbug in probe['faults']:
641 641 id = badbug['id']
642 642 self.ui.status(_('bug %d does not exist\n') % id)
643 643 del bugs[id]
644 644
645 645 def filter_cset_known_bug_ids(self, node, bugs):
646 646 for id in sorted(bugs.keys()):
647 647 if self.get_bug_comments(id).find(short(node)) != -1:
648 648 self.ui.status(_('bug %d already knows about changeset %s\n') %
649 649 (id, short(node)))
650 650 del bugs[id]
651 651
652 652 def updatebug(self, bugid, newstate, text, committer):
653 653 args = {}
654 654 if 'hours' in newstate:
655 655 args['work_time'] = newstate['hours']
656 656
657 657 if self.bzvermajor >= 4:
658 658 args['ids'] = [bugid]
659 659 args['comment'] = {'body' : text}
660 660 if 'fix' in newstate:
661 661 args['status'] = self.fixstatus
662 662 args['resolution'] = self.fixresolution
663 663 self.bzproxy.Bug.update(args)
664 664 else:
665 665 if 'fix' in newstate:
666 666 self.ui.warn(_("Bugzilla/XMLRPC needs Bugzilla 4.0 or later "
667 667 "to mark bugs fixed\n"))
668 668 args['id'] = bugid
669 669 args['comment'] = text
670 670 self.bzproxy.Bug.add_comment(args)
671 671
672 672 class bzxmlrpcemail(bzxmlrpc):
673 673 """Read data from Bugzilla via XMLRPC, send updates via email.
674 674
675 675 Advantages of sending updates via email:
676 676 1. Comments can be added as any user, not just logged in user.
677 677 2. Bug statuses or other fields not accessible via XMLRPC can
678 678 potentially be updated.
679 679
680 680 There is no XMLRPC function to change bug status before Bugzilla
681 681 4.0, so bugs cannot be marked fixed via XMLRPC before Bugzilla 4.0.
682 682 But bugs can be marked fixed via email from 3.4 onwards.
683 683 """
684 684
685 685 # The email interface changes subtly between 3.4 and 3.6. In 3.4,
686 686 # in-email fields are specified as '@<fieldname> = <value>'. In
687 687 # 3.6 this becomes '@<fieldname> <value>'. And fieldname @bug_id
688 688 # in 3.4 becomes @id in 3.6. 3.6 and 4.0 both maintain backwards
689 689 # compatibility, but rather than rely on this use the new format for
690 690 # 4.0 onwards.
691 691
692 692 def __init__(self, ui):
693 693 bzxmlrpc.__init__(self, ui)
694 694
695 695 self.bzemail = self.ui.config('bugzilla', 'bzemail')
696 696 if not self.bzemail:
697 697 raise util.Abort(_("configuration 'bzemail' missing"))
698 698 mail.validateconfig(self.ui)
699 699
700 700 def makecommandline(self, fieldname, value):
701 701 if self.bzvermajor >= 4:
702 702 return "@%s %s" % (fieldname, str(value))
703 703 else:
704 704 if fieldname == "id":
705 705 fieldname = "bug_id"
706 706 return "@%s = %s" % (fieldname, str(value))
707 707
708 708 def send_bug_modify_email(self, bugid, commands, comment, committer):
709 709 '''send modification message to Bugzilla bug via email.
710 710
711 711 The message format is documented in the Bugzilla email_in.pl
712 712 specification. commands is a list of command lines, comment is the
713 713 comment text.
714 714
715 715 To stop users from crafting commit comments with
716 716 Bugzilla commands, specify the bug ID via the message body, rather
717 717 than the subject line, and leave a blank line after it.
718 718 '''
719 719 user = self.map_committer(committer)
720 720 matches = self.bzproxy.User.get(dict(match=[user]))
721 721 if not matches['users']:
722 722 user = self.ui.config('bugzilla', 'user', 'bugs')
723 723 matches = self.bzproxy.User.get(dict(match=[user]))
724 724 if not matches['users']:
725 725 raise util.Abort(_("default bugzilla user %s email not found") %
726 726 user)
727 727 user = matches['users'][0]['email']
728 728 commands.append(self.makecommandline("id", bugid))
729 729
730 730 text = "\n".join(commands) + "\n\n" + comment
731 731
732 732 _charsets = mail._charsets(self.ui)
733 733 user = mail.addressencode(self.ui, user, _charsets)
734 734 bzemail = mail.addressencode(self.ui, self.bzemail, _charsets)
735 735 msg = mail.mimeencode(self.ui, text, _charsets)
736 736 msg['From'] = user
737 737 msg['To'] = bzemail
738 738 msg['Subject'] = mail.headencode(self.ui, "Bug modification", _charsets)
739 739 sendmail = mail.connect(self.ui)
740 740 sendmail(user, bzemail, msg.as_string())
741 741
742 742 def updatebug(self, bugid, newstate, text, committer):
743 743 cmds = []
744 744 if 'hours' in newstate:
745 745 cmds.append(self.makecommandline("work_time", newstate['hours']))
746 746 if 'fix' in newstate:
747 747 cmds.append(self.makecommandline("bug_status", self.fixstatus))
748 748 cmds.append(self.makecommandline("resolution", self.fixresolution))
749 749 self.send_bug_modify_email(bugid, cmds, text, committer)
750 750
751 751 class bugzilla(object):
752 752 # supported versions of bugzilla. different versions have
753 753 # different schemas.
754 754 _versions = {
755 755 '2.16': bzmysql,
756 756 '2.18': bzmysql_2_18,
757 757 '3.0': bzmysql_3_0,
758 758 'xmlrpc': bzxmlrpc,
759 759 'xmlrpc+email': bzxmlrpcemail
760 760 }
761 761
762 762 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
763 763 r'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
764 764 r'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?')
765 765
766 766 _default_fix_re = (r'fix(?:es)?\s*(?:bugs?\s*)?,?\s*'
767 767 r'(?:nos?\.?|num(?:ber)?s?)?\s*'
768 768 r'(?P<ids>(?:#?\d+\s*(?:,?\s*(?:and)?)?\s*)+)'
769 769 r'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?')
770 770
771 771 _bz = None
772 772
773 773 def __init__(self, ui, repo):
774 774 self.ui = ui
775 775 self.repo = repo
776 776
777 777 def bz(self):
778 778 '''return object that knows how to talk to bugzilla version in
779 779 use.'''
780 780
781 781 if bugzilla._bz is None:
782 782 bzversion = self.ui.config('bugzilla', 'version')
783 783 try:
784 784 bzclass = bugzilla._versions[bzversion]
785 785 except KeyError:
786 786 raise util.Abort(_('bugzilla version %s not supported') %
787 787 bzversion)
788 788 bugzilla._bz = bzclass(self.ui)
789 789 return bugzilla._bz
790 790
791 791 def __getattr__(self, key):
792 792 return getattr(self.bz(), key)
793 793
794 794 _bug_re = None
795 795 _fix_re = None
796 796 _split_re = None
797 797
798 798 def find_bugs(self, ctx):
799 799 '''return bugs dictionary created from commit comment.
800 800
801 801 Extract bug info from changeset comments. Filter out any that are
802 802 not known to Bugzilla, and any that already have a reference to
803 803 the given changeset in their comments.
804 804 '''
805 805 if bugzilla._bug_re is None:
806 806 bugzilla._bug_re = re.compile(
807 807 self.ui.config('bugzilla', 'regexp',
808 808 bugzilla._default_bug_re), re.IGNORECASE)
809 809 bugzilla._fix_re = re.compile(
810 810 self.ui.config('bugzilla', 'fixregexp',
811 811 bugzilla._default_fix_re), re.IGNORECASE)
812 812 bugzilla._split_re = re.compile(r'\D+')
813 813 start = 0
814 814 hours = 0.0
815 815 bugs = {}
816 816 bugmatch = bugzilla._bug_re.search(ctx.description(), start)
817 817 fixmatch = bugzilla._fix_re.search(ctx.description(), start)
818 818 while True:
819 819 bugattribs = {}
820 820 if not bugmatch and not fixmatch:
821 821 break
822 822 if not bugmatch:
823 823 m = fixmatch
824 824 elif not fixmatch:
825 825 m = bugmatch
826 826 else:
827 827 if bugmatch.start() < fixmatch.start():
828 828 m = bugmatch
829 829 else:
830 830 m = fixmatch
831 831 start = m.end()
832 832 if m is bugmatch:
833 833 bugmatch = bugzilla._bug_re.search(ctx.description(), start)
834 834 if 'fix' in bugattribs:
835 835 del bugattribs['fix']
836 836 else:
837 837 fixmatch = bugzilla._fix_re.search(ctx.description(), start)
838 838 bugattribs['fix'] = None
839 839
840 840 try:
841 841 ids = m.group('ids')
842 842 except IndexError:
843 843 ids = m.group(1)
844 844 try:
845 845 hours = float(m.group('hours'))
846 846 bugattribs['hours'] = hours
847 847 except IndexError:
848 848 pass
849 849 except TypeError:
850 850 pass
851 851 except ValueError:
852 852 self.ui.status(_("%s: invalid hours\n") % m.group('hours'))
853 853
854 854 for id in bugzilla._split_re.split(ids):
855 855 if not id:
856 856 continue
857 857 bugs[int(id)] = bugattribs
858 858 if bugs:
859 859 self.filter_real_bug_ids(bugs)
860 860 if bugs:
861 861 self.filter_cset_known_bug_ids(ctx.node(), bugs)
862 862 return bugs
863 863
864 864 def update(self, bugid, newstate, ctx):
865 865 '''update bugzilla bug with reference to changeset.'''
866 866
867 867 def webroot(root):
868 868 '''strip leading prefix of repo root and turn into
869 869 url-safe path.'''
870 870 count = int(self.ui.config('bugzilla', 'strip', 0))
871 871 root = util.pconvert(root)
872 872 while count > 0:
873 873 c = root.find('/')
874 874 if c == -1:
875 875 break
876 876 root = root[c + 1:]
877 877 count -= 1
878 878 return root
879 879
880 880 mapfile = self.ui.config('bugzilla', 'style')
881 881 tmpl = self.ui.config('bugzilla', 'template')
882 882 t = cmdutil.changeset_templater(self.ui, self.repo,
883 883 False, None, mapfile, False)
884 884 if not mapfile and not tmpl:
885 885 tmpl = _('changeset {node|short} in repo {root} refers '
886 886 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
887 887 if tmpl:
888 888 tmpl = templater.parsestring(tmpl, quoted=False)
889 889 t.use_template(tmpl)
890 890 self.ui.pushbuffer()
891 891 t.show(ctx, changes=ctx.changeset(),
892 892 bug=str(bugid),
893 893 hgweb=self.ui.config('web', 'baseurl'),
894 894 root=self.repo.root,
895 895 webroot=webroot(self.repo.root))
896 896 data = self.ui.popbuffer()
897 897 self.updatebug(bugid, newstate, data, util.email(ctx.user()))
898 898
899 899 def hook(ui, repo, hooktype, node=None, **kwargs):
900 900 '''add comment to bugzilla for each changeset that refers to a
901 901 bugzilla bug id. only add a comment once per bug, so same change
902 902 seen multiple times does not fill bug with duplicate data.'''
903 903 if node is None:
904 904 raise util.Abort(_('hook type %s does not pass a changeset id') %
905 905 hooktype)
906 906 try:
907 907 bz = bugzilla(ui, repo)
908 908 ctx = repo[node]
909 909 bugs = bz.find_bugs(ctx)
910 910 if bugs:
911 911 for bug in bugs:
912 912 bz.update(bug, bugs[bug], ctx)
913 913 bz.notify(bugs, util.email(ctx.user()))
914 914 except Exception, e:
915 915 raise util.Abort(_('Bugzilla error: %s') % e)
@@ -1,128 +1,128
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2007 Daniel Holth <dholth@fastmail.fm>
4 4 # This is a stripped-down version of the original bzr-svn transport.py,
5 5 # Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
6 6
7 7 # This program is free software; you can redistribute it and/or modify
8 8 # it under the terms of the GNU General Public License as published by
9 9 # the Free Software Foundation; either version 2 of the License, or
10 10 # (at your option) any later version.
11 11
12 12 # This program is distributed in the hope that it will be useful,
13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 15 # GNU General Public License for more details.
16 16
17 17 # You should have received a copy of the GNU General Public License
18 18 # along with this program; if not, see <http://www.gnu.org/licenses/>.
19 19
20 20 from mercurial import util
21 21 from svn.core import SubversionException, Pool
22 22 import svn.ra
23 23 import svn.client
24 24 import svn.core
25 25
26 26 # Some older versions of the Python bindings need to be
27 27 # explicitly initialized. But what we want to do probably
28 28 # won't work worth a darn against those libraries anyway!
29 29 svn.ra.initialize()
30 30
31 31 svn_config = svn.core.svn_config_get_config(None)
32 32
33 33
34 34 def _create_auth_baton(pool):
35 35 """Create a Subversion authentication baton. """
36 36 import svn.client
37 37 # Give the client context baton a suite of authentication
38 38 # providers.h
39 39 providers = [
40 40 svn.client.get_simple_provider(pool),
41 41 svn.client.get_username_provider(pool),
42 42 svn.client.get_ssl_client_cert_file_provider(pool),
43 43 svn.client.get_ssl_client_cert_pw_file_provider(pool),
44 44 svn.client.get_ssl_server_trust_file_provider(pool),
45 45 ]
46 # Platform-dependant authentication methods
46 # Platform-dependent authentication methods
47 47 getprovider = getattr(svn.core, 'svn_auth_get_platform_specific_provider',
48 48 None)
49 49 if getprovider:
50 50 # Available in svn >= 1.6
51 51 for name in ('gnome_keyring', 'keychain', 'kwallet', 'windows'):
52 52 for type in ('simple', 'ssl_client_cert_pw', 'ssl_server_trust'):
53 53 p = getprovider(name, type, pool)
54 54 if p:
55 55 providers.append(p)
56 56 else:
57 57 if util.safehasattr(svn.client, 'get_windows_simple_provider'):
58 58 providers.append(svn.client.get_windows_simple_provider(pool))
59 59
60 60 return svn.core.svn_auth_open(providers, pool)
61 61
62 62 class NotBranchError(SubversionException):
63 63 pass
64 64
65 65 class SvnRaTransport(object):
66 66 """
67 67 Open an ra connection to a Subversion repository.
68 68 """
69 69 def __init__(self, url="", ra=None):
70 70 self.pool = Pool()
71 71 self.svn_url = url
72 72 self.username = ''
73 73 self.password = ''
74 74
75 75 # Only Subversion 1.4 has reparent()
76 76 if ra is None or not util.safehasattr(svn.ra, 'reparent'):
77 77 self.client = svn.client.create_context(self.pool)
78 78 ab = _create_auth_baton(self.pool)
79 79 if False:
80 80 svn.core.svn_auth_set_parameter(
81 81 ab, svn.core.SVN_AUTH_PARAM_DEFAULT_USERNAME, self.username)
82 82 svn.core.svn_auth_set_parameter(
83 83 ab, svn.core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, self.password)
84 84 self.client.auth_baton = ab
85 85 self.client.config = svn_config
86 86 try:
87 87 self.ra = svn.client.open_ra_session(
88 88 self.svn_url,
89 89 self.client, self.pool)
90 90 except SubversionException, (inst, num):
91 91 if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL,
92 92 svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED,
93 93 svn.core.SVN_ERR_BAD_URL):
94 94 raise NotBranchError(url)
95 95 raise
96 96 else:
97 97 self.ra = ra
98 98 svn.ra.reparent(self.ra, self.svn_url.encode('utf8'))
99 99
100 100 class Reporter(object):
101 101 def __init__(self, reporter_data):
102 102 self._reporter, self._baton = reporter_data
103 103
104 104 def set_path(self, path, revnum, start_empty, lock_token, pool=None):
105 105 svn.ra.reporter2_invoke_set_path(self._reporter, self._baton,
106 106 path, revnum, start_empty, lock_token, pool)
107 107
108 108 def delete_path(self, path, pool=None):
109 109 svn.ra.reporter2_invoke_delete_path(self._reporter, self._baton,
110 110 path, pool)
111 111
112 112 def link_path(self, path, url, revision, start_empty, lock_token,
113 113 pool=None):
114 114 svn.ra.reporter2_invoke_link_path(self._reporter, self._baton,
115 115 path, url, revision, start_empty, lock_token,
116 116 pool)
117 117
118 118 def finish_report(self, pool=None):
119 119 svn.ra.reporter2_invoke_finish_report(self._reporter,
120 120 self._baton, pool)
121 121
122 122 def abort_report(self, pool=None):
123 123 svn.ra.reporter2_invoke_abort_report(self._reporter,
124 124 self._baton, pool)
125 125
126 126 def do_update(self, revnum, path, *args, **kwargs):
127 127 return self.Reporter(svn.ra.do_update(self.ra, revnum, path,
128 128 *args, **kwargs))
@@ -1,349 +1,349
1 1 """automatically manage newlines in repository files
2 2
3 3 This extension allows you to manage the type of line endings (CRLF or
4 4 LF) that are used in the repository and in the local working
5 5 directory. That way you can get CRLF line endings on Windows and LF on
6 6 Unix/Mac, thereby letting everybody use their OS native line endings.
7 7
8 8 The extension reads its configuration from a versioned ``.hgeol``
9 9 configuration file found in the root of the working copy. The
10 10 ``.hgeol`` file use the same syntax as all other Mercurial
11 11 configuration files. It uses two sections, ``[patterns]`` and
12 12 ``[repository]``.
13 13
14 14 The ``[patterns]`` section specifies how line endings should be
15 15 converted between the working copy and the repository. The format is
16 16 specified by a file pattern. The first match is used, so put more
17 17 specific patterns first. The available line endings are ``LF``,
18 18 ``CRLF``, and ``BIN``.
19 19
20 20 Files with the declared format of ``CRLF`` or ``LF`` are always
21 21 checked out and stored in the repository in that format and files
22 22 declared to be binary (``BIN``) are left unchanged. Additionally,
23 23 ``native`` is an alias for checking out in the platform's default line
24 24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
25 25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
26 26 default behaviour; it is only needed if you need to override a later,
27 27 more general pattern.
28 28
29 29 The optional ``[repository]`` section specifies the line endings to
30 30 use for files stored in the repository. It has a single setting,
31 31 ``native``, which determines the storage line endings for files
32 32 declared as ``native`` in the ``[patterns]`` section. It can be set to
33 33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
34 34 that on Windows, files configured as ``native`` (``CRLF`` by default)
35 35 will be converted to ``LF`` when stored in the repository. Files
36 36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
37 37 are always stored as-is in the repository.
38 38
39 39 Example versioned ``.hgeol`` file::
40 40
41 41 [patterns]
42 42 **.py = native
43 43 **.vcproj = CRLF
44 44 **.txt = native
45 45 Makefile = LF
46 46 **.jpg = BIN
47 47
48 48 [repository]
49 49 native = LF
50 50
51 51 .. note::
52 52 The rules will first apply when files are touched in the working
53 53 copy, e.g. by updating to null and back to tip to touch all files.
54 54
55 55 The extension uses an optional ``[eol]`` section read from both the
56 56 normal Mercurial configuration files and the ``.hgeol`` file, with the
57 57 latter overriding the former. You can use that section to control the
58 58 overall behavior. There are three settings:
59 59
60 60 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
61 61 ``CRLF`` to override the default interpretation of ``native`` for
62 62 checkout. This can be used with :hg:`archive` on Unix, say, to
63 63 generate an archive where files have line endings for Windows.
64 64
65 65 - ``eol.only-consistent`` (default True) can be set to False to make
66 66 the extension convert files with inconsistent EOLs. Inconsistent
67 67 means that there is both ``CRLF`` and ``LF`` present in the file.
68 68 Such files are normally not touched under the assumption that they
69 69 have mixed EOLs on purpose.
70 70
71 71 - ``eol.fix-trailing-newline`` (default False) can be set to True to
72 72 ensure that converted files end with a EOL character (either ``\\n``
73 73 or ``\\r\\n`` as per the configured patterns).
74 74
75 75 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
76 76 like the deprecated win32text extension does. This means that you can
77 77 disable win32text and enable eol and your filters will still work. You
78 78 only need to these filters until you have prepared a ``.hgeol`` file.
79 79
80 80 The ``win32text.forbid*`` hooks provided by the win32text extension
81 81 have been unified into a single hook named ``eol.checkheadshook``. The
82 82 hook will lookup the expected line endings from the ``.hgeol`` file,
83 83 which means you must migrate to a ``.hgeol`` file first before using
84 84 the hook. ``eol.checkheadshook`` only checks heads, intermediate
85 85 invalid revisions will be pushed. To forbid them completely, use the
86 86 ``eol.checkallhook`` hook. These hooks are best used as
87 87 ``pretxnchangegroup`` hooks.
88 88
89 89 See :hg:`help patterns` for more information about the glob patterns
90 90 used.
91 91 """
92 92
93 93 from mercurial.i18n import _
94 94 from mercurial import util, config, extensions, match, error
95 95 import re, os
96 96
97 97 testedwith = 'internal'
98 98
99 99 # Matches a lone LF, i.e., one that is not part of CRLF.
100 100 singlelf = re.compile('(^|[^\r])\n')
101 101 # Matches a single EOL which can either be a CRLF where repeated CR
102 # are removed or a LF. We do not care about old Machintosh files, so a
102 # are removed or a LF. We do not care about old Macintosh files, so a
103 103 # stray CR is an error.
104 104 eolre = re.compile('\r*\n')
105 105
106 106
107 107 def inconsistenteol(data):
108 108 return '\r\n' in data and singlelf.search(data)
109 109
110 110 def tolf(s, params, ui, **kwargs):
111 111 """Filter to convert to LF EOLs."""
112 112 if util.binary(s):
113 113 return s
114 114 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
115 115 return s
116 116 if (ui.configbool('eol', 'fix-trailing-newline', False)
117 117 and s and s[-1] != '\n'):
118 118 s = s + '\n'
119 119 return eolre.sub('\n', s)
120 120
121 121 def tocrlf(s, params, ui, **kwargs):
122 122 """Filter to convert to CRLF EOLs."""
123 123 if util.binary(s):
124 124 return s
125 125 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
126 126 return s
127 127 if (ui.configbool('eol', 'fix-trailing-newline', False)
128 128 and s and s[-1] != '\n'):
129 129 s = s + '\n'
130 130 return eolre.sub('\r\n', s)
131 131
132 132 def isbinary(s, params):
133 133 """Filter to do nothing with the file."""
134 134 return s
135 135
136 136 filters = {
137 137 'to-lf': tolf,
138 138 'to-crlf': tocrlf,
139 139 'is-binary': isbinary,
140 140 # The following provide backwards compatibility with win32text
141 141 'cleverencode:': tolf,
142 142 'cleverdecode:': tocrlf
143 143 }
144 144
145 145 class eolfile(object):
146 146 def __init__(self, ui, root, data):
147 147 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
148 148 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
149 149
150 150 self.cfg = config.config()
151 151 # Our files should not be touched. The pattern must be
152 152 # inserted first override a '** = native' pattern.
153 153 self.cfg.set('patterns', '.hg*', 'BIN')
154 154 # We can then parse the user's patterns.
155 155 self.cfg.parse('.hgeol', data)
156 156
157 157 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
158 158 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
159 159 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
160 160 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
161 161
162 162 include = []
163 163 exclude = []
164 164 for pattern, style in self.cfg.items('patterns'):
165 165 key = style.upper()
166 166 if key == 'BIN':
167 167 exclude.append(pattern)
168 168 else:
169 169 include.append(pattern)
170 170 # This will match the files for which we need to care
171 171 # about inconsistent newlines.
172 172 self.match = match.match(root, '', [], include, exclude)
173 173
174 174 def copytoui(self, ui):
175 175 for pattern, style in self.cfg.items('patterns'):
176 176 key = style.upper()
177 177 try:
178 178 ui.setconfig('decode', pattern, self._decode[key])
179 179 ui.setconfig('encode', pattern, self._encode[key])
180 180 except KeyError:
181 181 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
182 182 % (style, self.cfg.source('patterns', pattern)))
183 183 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
184 184 for k, v in self.cfg.items('eol'):
185 185 ui.setconfig('eol', k, v)
186 186
187 187 def checkrev(self, repo, ctx, files):
188 188 failed = []
189 189 for f in (files or ctx.files()):
190 190 if f not in ctx:
191 191 continue
192 192 for pattern, style in self.cfg.items('patterns'):
193 193 if not match.match(repo.root, '', [pattern])(f):
194 194 continue
195 195 target = self._encode[style.upper()]
196 196 data = ctx[f].data()
197 197 if (target == "to-lf" and "\r\n" in data
198 198 or target == "to-crlf" and singlelf.search(data)):
199 199 failed.append((str(ctx), target, f))
200 200 break
201 201 return failed
202 202
203 203 def parseeol(ui, repo, nodes):
204 204 try:
205 205 for node in nodes:
206 206 try:
207 207 if node is None:
208 208 # Cannot use workingctx.data() since it would load
209 209 # and cache the filters before we configure them.
210 210 data = repo.wfile('.hgeol').read()
211 211 else:
212 212 data = repo[node]['.hgeol'].data()
213 213 return eolfile(ui, repo.root, data)
214 214 except (IOError, LookupError):
215 215 pass
216 216 except error.ParseError, inst:
217 217 ui.warn(_("warning: ignoring .hgeol file due to parse error "
218 218 "at %s: %s\n") % (inst.args[1], inst.args[0]))
219 219 return None
220 220
221 221 def _checkhook(ui, repo, node, headsonly):
222 222 # Get revisions to check and touched files at the same time
223 223 files = set()
224 224 revs = set()
225 225 for rev in xrange(repo[node].rev(), len(repo)):
226 226 revs.add(rev)
227 227 if headsonly:
228 228 ctx = repo[rev]
229 229 files.update(ctx.files())
230 230 for pctx in ctx.parents():
231 231 revs.discard(pctx.rev())
232 232 failed = []
233 233 for rev in revs:
234 234 ctx = repo[rev]
235 235 eol = parseeol(ui, repo, [ctx.node()])
236 236 if eol:
237 237 failed.extend(eol.checkrev(repo, ctx, files))
238 238
239 239 if failed:
240 240 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
241 241 msgs = []
242 242 for node, target, f in failed:
243 243 msgs.append(_(" %s in %s should not have %s line endings") %
244 244 (f, node, eols[target]))
245 245 raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
246 246
247 247 def checkallhook(ui, repo, node, hooktype, **kwargs):
248 248 """verify that files have expected EOLs"""
249 249 _checkhook(ui, repo, node, False)
250 250
251 251 def checkheadshook(ui, repo, node, hooktype, **kwargs):
252 252 """verify that files have expected EOLs"""
253 253 _checkhook(ui, repo, node, True)
254 254
255 255 # "checkheadshook" used to be called "hook"
256 256 hook = checkheadshook
257 257
258 258 def preupdate(ui, repo, hooktype, parent1, parent2):
259 259 repo.loadeol([parent1])
260 260 return False
261 261
262 262 def uisetup(ui):
263 263 ui.setconfig('hooks', 'preupdate.eol', preupdate)
264 264
265 265 def extsetup(ui):
266 266 try:
267 267 extensions.find('win32text')
268 268 ui.warn(_("the eol extension is incompatible with the "
269 269 "win32text extension\n"))
270 270 except KeyError:
271 271 pass
272 272
273 273
274 274 def reposetup(ui, repo):
275 275 uisetup(repo.ui)
276 276
277 277 if not repo.local():
278 278 return
279 279 for name, fn in filters.iteritems():
280 280 repo.adddatafilter(name, fn)
281 281
282 282 ui.setconfig('patch', 'eol', 'auto')
283 283
284 284 class eolrepo(repo.__class__):
285 285
286 286 def loadeol(self, nodes):
287 287 eol = parseeol(self.ui, self, nodes)
288 288 if eol is None:
289 289 return None
290 290 eol.copytoui(self.ui)
291 291 return eol.match
292 292
293 293 def _hgcleardirstate(self):
294 294 self._eolfile = self.loadeol([None, 'tip'])
295 295 if not self._eolfile:
296 296 self._eolfile = util.never
297 297 return
298 298
299 299 try:
300 300 cachemtime = os.path.getmtime(self.join("eol.cache"))
301 301 except OSError:
302 302 cachemtime = 0
303 303
304 304 try:
305 305 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
306 306 except OSError:
307 307 eolmtime = 0
308 308
309 309 if eolmtime > cachemtime:
310 310 ui.debug("eol: detected change in .hgeol\n")
311 311 wlock = None
312 312 try:
313 313 wlock = self.wlock()
314 314 for f in self.dirstate:
315 315 if self.dirstate[f] == 'n':
316 316 # all normal files need to be looked at
317 317 # again since the new .hgeol file might no
318 318 # longer match a file it matched before
319 319 self.dirstate.normallookup(f)
320 320 # Create or touch the cache to update mtime
321 321 self.opener("eol.cache", "w").close()
322 322 wlock.release()
323 323 except error.LockUnavailable:
324 324 # If we cannot lock the repository and clear the
325 325 # dirstate, then a commit might not see all files
326 326 # as modified. But if we cannot lock the
327 327 # repository, then we can also not make a commit,
328 328 # so ignore the error.
329 329 pass
330 330
331 331 def commitctx(self, ctx, error=False):
332 332 for f in sorted(ctx.added() + ctx.modified()):
333 333 if not self._eolfile(f):
334 334 continue
335 335 try:
336 336 data = ctx[f].data()
337 337 except IOError:
338 338 continue
339 339 if util.binary(data):
340 340 # We should not abort here, since the user should
341 341 # be able to say "** = native" to automatically
342 342 # have all non-binary files taken care of.
343 343 continue
344 344 if inconsistenteol(data):
345 345 raise util.Abort(_("inconsistent newline style "
346 346 "in %s\n" % f))
347 347 return super(eolrepo, self).commitctx(ctx, error)
348 348 repo.__class__ = eolrepo
349 349 repo._hgcleardirstate()
@@ -1,649 +1,649
1 1 /*
2 2 * _inotify.c - Python extension interfacing to the Linux inotify subsystem
3 3 *
4 4 * Copyright 2006 Bryan O'Sullivan <bos@serpentine.com>
5 5 *
6 6 * This library is free software; you can redistribute it and/or
7 7 * modify it under the terms of version 2.1 of the GNU Lesser General
8 8 * Public License or any later version.
9 9 */
10 10
11 11 #include <Python.h>
12 12 #include <alloca.h>
13 13 #include <sys/inotify.h>
14 14 #include <stdint.h>
15 15 #include <sys/ioctl.h>
16 16 #include <unistd.h>
17 17
18 18 #include <util.h>
19 19
20 20 /* Variables used in the event string representation */
21 21 static PyObject *join;
22 22 static PyObject *er_wm;
23 23 static PyObject *er_wmc;
24 24 static PyObject *er_wmn;
25 25 static PyObject *er_wmcn;
26 26
27 27 static PyObject *init(PyObject *self, PyObject *args)
28 28 {
29 29 PyObject *ret = NULL;
30 30 int fd = -1;
31 31
32 32 if (!PyArg_ParseTuple(args, ":init"))
33 33 goto bail;
34 34
35 35 Py_BEGIN_ALLOW_THREADS;
36 36 fd = inotify_init();
37 37 Py_END_ALLOW_THREADS;
38 38
39 39 if (fd == -1) {
40 40 PyErr_SetFromErrno(PyExc_OSError);
41 41 goto bail;
42 42 }
43 43
44 44 ret = PyInt_FromLong(fd);
45 45 if (ret == NULL)
46 46 goto bail;
47 47
48 48 goto done;
49 49
50 50 bail:
51 51 if (fd != -1)
52 52 close(fd);
53 53
54 54 Py_CLEAR(ret);
55 55
56 56 done:
57 57 return ret;
58 58 }
59 59
60 60 PyDoc_STRVAR(
61 61 init_doc,
62 62 "init() -> fd\n"
63 63 "\n"
64 "Initialise an inotify instance.\n"
64 "Initialize an inotify instance.\n"
65 65 "Return a file descriptor associated with a new inotify event queue.");
66 66
67 67 static PyObject *add_watch(PyObject *self, PyObject *args)
68 68 {
69 69 PyObject *ret = NULL;
70 70 uint32_t mask;
71 71 int wd = -1;
72 72 char *path;
73 73 int fd;
74 74
75 75 if (!PyArg_ParseTuple(args, "isI:add_watch", &fd, &path, &mask))
76 76 goto bail;
77 77
78 78 Py_BEGIN_ALLOW_THREADS;
79 79 wd = inotify_add_watch(fd, path, mask);
80 80 Py_END_ALLOW_THREADS;
81 81
82 82 if (wd == -1) {
83 83 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
84 84 goto bail;
85 85 }
86 86
87 87 ret = PyInt_FromLong(wd);
88 88 if (ret == NULL)
89 89 goto bail;
90 90
91 91 goto done;
92 92
93 93 bail:
94 94 if (wd != -1)
95 95 inotify_rm_watch(fd, wd);
96 96
97 97 Py_CLEAR(ret);
98 98
99 99 done:
100 100 return ret;
101 101 }
102 102
103 103 PyDoc_STRVAR(
104 104 add_watch_doc,
105 105 "add_watch(fd, path, mask) -> wd\n"
106 106 "\n"
107 107 "Add a watch to an inotify instance, or modify an existing watch.\n"
108 108 "\n"
109 109 " fd: file descriptor returned by init()\n"
110 110 " path: path to watch\n"
111 111 " mask: mask of events to watch for\n"
112 112 "\n"
113 113 "Return a unique numeric watch descriptor for the inotify instance\n"
114 114 "mapped by the file descriptor.");
115 115
116 116 static PyObject *remove_watch(PyObject *self, PyObject *args)
117 117 {
118 118 uint32_t wd;
119 119 int fd;
120 120 int r;
121 121
122 122 if (!PyArg_ParseTuple(args, "iI:remove_watch", &fd, &wd))
123 123 return NULL;
124 124
125 125 Py_BEGIN_ALLOW_THREADS;
126 126 r = inotify_rm_watch(fd, wd);
127 127 Py_END_ALLOW_THREADS;
128 128
129 129 if (r == -1) {
130 130 PyErr_SetFromErrno(PyExc_OSError);
131 131 return NULL;
132 132 }
133 133
134 134 Py_INCREF(Py_None);
135 135 return Py_None;
136 136 }
137 137
138 138 PyDoc_STRVAR(
139 139 remove_watch_doc,
140 140 "remove_watch(fd, wd)\n"
141 141 "\n"
142 142 " fd: file descriptor returned by init()\n"
143 143 " wd: watch descriptor returned by add_watch()\n"
144 144 "\n"
145 145 "Remove a watch associated with the watch descriptor wd from the\n"
146 146 "inotify instance associated with the file descriptor fd.\n"
147 147 "\n"
148 148 "Removing a watch causes an IN_IGNORED event to be generated for this\n"
149 149 "watch descriptor.");
150 150
151 151 #define bit_name(x) {x, #x}
152 152
153 153 static struct {
154 154 int bit;
155 155 const char *name;
156 156 PyObject *pyname;
157 157 } bit_names[] = {
158 158 bit_name(IN_ACCESS),
159 159 bit_name(IN_MODIFY),
160 160 bit_name(IN_ATTRIB),
161 161 bit_name(IN_CLOSE_WRITE),
162 162 bit_name(IN_CLOSE_NOWRITE),
163 163 bit_name(IN_OPEN),
164 164 bit_name(IN_MOVED_FROM),
165 165 bit_name(IN_MOVED_TO),
166 166 bit_name(IN_CREATE),
167 167 bit_name(IN_DELETE),
168 168 bit_name(IN_DELETE_SELF),
169 169 bit_name(IN_MOVE_SELF),
170 170 bit_name(IN_UNMOUNT),
171 171 bit_name(IN_Q_OVERFLOW),
172 172 bit_name(IN_IGNORED),
173 173 bit_name(IN_ONLYDIR),
174 174 bit_name(IN_DONT_FOLLOW),
175 175 bit_name(IN_MASK_ADD),
176 176 bit_name(IN_ISDIR),
177 177 bit_name(IN_ONESHOT),
178 178 {0}
179 179 };
180 180
181 181 static PyObject *decode_mask(int mask)
182 182 {
183 183 PyObject *ret = PyList_New(0);
184 184 int i;
185 185
186 186 if (ret == NULL)
187 187 goto bail;
188 188
189 189 for (i = 0; bit_names[i].bit; i++) {
190 190 if (mask & bit_names[i].bit) {
191 191 if (bit_names[i].pyname == NULL) {
192 192 bit_names[i].pyname = PyString_FromString(bit_names[i].name);
193 193 if (bit_names[i].pyname == NULL)
194 194 goto bail;
195 195 }
196 196 Py_INCREF(bit_names[i].pyname);
197 197 if (PyList_Append(ret, bit_names[i].pyname) == -1)
198 198 goto bail;
199 199 }
200 200 }
201 201
202 202 goto done;
203 203
204 204 bail:
205 205 Py_CLEAR(ret);
206 206
207 207 done:
208 208 return ret;
209 209 }
210 210
211 211 static PyObject *pydecode_mask(PyObject *self, PyObject *args)
212 212 {
213 213 int mask;
214 214
215 215 if (!PyArg_ParseTuple(args, "i:decode_mask", &mask))
216 216 return NULL;
217 217
218 218 return decode_mask(mask);
219 219 }
220 220
221 221 PyDoc_STRVAR(
222 222 decode_mask_doc,
223 223 "decode_mask(mask) -> list_of_strings\n"
224 224 "\n"
225 225 "Decode an inotify mask value into a list of strings that give the\n"
226 226 "name of each bit set in the mask.");
227 227
228 228 static char doc[] = "Low-level inotify interface wrappers.";
229 229
230 230 static void define_const(PyObject *dict, const char *name, uint32_t val)
231 231 {
232 232 PyObject *pyval = PyInt_FromLong(val);
233 233 PyObject *pyname = PyString_FromString(name);
234 234
235 235 if (!pyname || !pyval)
236 236 goto bail;
237 237
238 238 PyDict_SetItem(dict, pyname, pyval);
239 239
240 240 bail:
241 241 Py_XDECREF(pyname);
242 242 Py_XDECREF(pyval);
243 243 }
244 244
245 245 static void define_consts(PyObject *dict)
246 246 {
247 247 define_const(dict, "IN_ACCESS", IN_ACCESS);
248 248 define_const(dict, "IN_MODIFY", IN_MODIFY);
249 249 define_const(dict, "IN_ATTRIB", IN_ATTRIB);
250 250 define_const(dict, "IN_CLOSE_WRITE", IN_CLOSE_WRITE);
251 251 define_const(dict, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE);
252 252 define_const(dict, "IN_OPEN", IN_OPEN);
253 253 define_const(dict, "IN_MOVED_FROM", IN_MOVED_FROM);
254 254 define_const(dict, "IN_MOVED_TO", IN_MOVED_TO);
255 255
256 256 define_const(dict, "IN_CLOSE", IN_CLOSE);
257 257 define_const(dict, "IN_MOVE", IN_MOVE);
258 258
259 259 define_const(dict, "IN_CREATE", IN_CREATE);
260 260 define_const(dict, "IN_DELETE", IN_DELETE);
261 261 define_const(dict, "IN_DELETE_SELF", IN_DELETE_SELF);
262 262 define_const(dict, "IN_MOVE_SELF", IN_MOVE_SELF);
263 263 define_const(dict, "IN_UNMOUNT", IN_UNMOUNT);
264 264 define_const(dict, "IN_Q_OVERFLOW", IN_Q_OVERFLOW);
265 265 define_const(dict, "IN_IGNORED", IN_IGNORED);
266 266
267 267 define_const(dict, "IN_ONLYDIR", IN_ONLYDIR);
268 268 define_const(dict, "IN_DONT_FOLLOW", IN_DONT_FOLLOW);
269 269 define_const(dict, "IN_MASK_ADD", IN_MASK_ADD);
270 270 define_const(dict, "IN_ISDIR", IN_ISDIR);
271 271 define_const(dict, "IN_ONESHOT", IN_ONESHOT);
272 272 define_const(dict, "IN_ALL_EVENTS", IN_ALL_EVENTS);
273 273 }
274 274
275 275 struct event {
276 276 PyObject_HEAD
277 277 PyObject *wd;
278 278 PyObject *mask;
279 279 PyObject *cookie;
280 280 PyObject *name;
281 281 };
282 282
283 283 static PyObject *event_wd(PyObject *self, void *x)
284 284 {
285 285 struct event *evt = (struct event *)self;
286 286 Py_INCREF(evt->wd);
287 287 return evt->wd;
288 288 }
289 289
290 290 static PyObject *event_mask(PyObject *self, void *x)
291 291 {
292 292 struct event *evt = (struct event *)self;
293 293 Py_INCREF(evt->mask);
294 294 return evt->mask;
295 295 }
296 296
297 297 static PyObject *event_cookie(PyObject *self, void *x)
298 298 {
299 299 struct event *evt = (struct event *)self;
300 300 Py_INCREF(evt->cookie);
301 301 return evt->cookie;
302 302 }
303 303
304 304 static PyObject *event_name(PyObject *self, void *x)
305 305 {
306 306 struct event *evt = (struct event *)self;
307 307 Py_INCREF(evt->name);
308 308 return evt->name;
309 309 }
310 310
311 311 static struct PyGetSetDef event_getsets[] = {
312 312 {"wd", event_wd, NULL,
313 313 "watch descriptor"},
314 314 {"mask", event_mask, NULL,
315 315 "event mask"},
316 316 {"cookie", event_cookie, NULL,
317 317 "rename cookie, if rename-related event"},
318 318 {"name", event_name, NULL,
319 319 "file name"},
320 320 {NULL}
321 321 };
322 322
323 323 PyDoc_STRVAR(
324 324 event_doc,
325 325 "event: Structure describing an inotify event.");
326 326
327 327 static PyObject *event_new(PyTypeObject *t, PyObject *a, PyObject *k)
328 328 {
329 329 return (*t->tp_alloc)(t, 0);
330 330 }
331 331
332 332 static void event_dealloc(struct event *evt)
333 333 {
334 334 Py_XDECREF(evt->wd);
335 335 Py_XDECREF(evt->mask);
336 336 Py_XDECREF(evt->cookie);
337 337 Py_XDECREF(evt->name);
338 338
339 339 Py_TYPE(evt)->tp_free(evt);
340 340 }
341 341
342 342 static PyObject *event_repr(struct event *evt)
343 343 {
344 344 int cookie = evt->cookie == Py_None ? -1 : PyInt_AsLong(evt->cookie);
345 345 PyObject *ret = NULL, *pymasks = NULL, *pymask = NULL;
346 346 PyObject *tuple = NULL, *formatstr = NULL;
347 347
348 348 pymasks = decode_mask(PyInt_AsLong(evt->mask));
349 349 if (pymasks == NULL)
350 350 goto bail;
351 351
352 352 pymask = _PyString_Join(join, pymasks);
353 353 if (pymask == NULL)
354 354 goto bail;
355 355
356 356 if (evt->name != Py_None) {
357 357 if (cookie == -1) {
358 358 formatstr = er_wmn;
359 359 tuple = PyTuple_Pack(3, evt->wd, pymask, evt->name);
360 360 }
361 361 else {
362 362 formatstr = er_wmcn;
363 363 tuple = PyTuple_Pack(4, evt->wd, pymask,
364 364 evt->cookie, evt->name);
365 365 }
366 366 } else {
367 367 if (cookie == -1) {
368 368 formatstr = er_wm;
369 369 tuple = PyTuple_Pack(2, evt->wd, pymask);
370 370 }
371 371 else {
372 372 formatstr = er_wmc;
373 373 tuple = PyTuple_Pack(3, evt->wd, pymask, evt->cookie);
374 374 }
375 375 }
376 376
377 377 if (tuple == NULL)
378 378 goto bail;
379 379
380 380 ret = PyNumber_Remainder(formatstr, tuple);
381 381
382 382 if (ret == NULL)
383 383 goto bail;
384 384
385 385 goto done;
386 386 bail:
387 387 Py_CLEAR(ret);
388 388
389 389 done:
390 390 Py_XDECREF(pymask);
391 391 Py_XDECREF(pymasks);
392 392 Py_XDECREF(tuple);
393 393
394 394 return ret;
395 395 }
396 396
397 397 static PyTypeObject event_type = {
398 398 PyVarObject_HEAD_INIT(NULL, 0)
399 399 "_inotify.event", /*tp_name*/
400 400 sizeof(struct event), /*tp_basicsize*/
401 401 0, /*tp_itemsize*/
402 402 (destructor)event_dealloc, /*tp_dealloc*/
403 403 0, /*tp_print*/
404 404 0, /*tp_getattr*/
405 405 0, /*tp_setattr*/
406 406 0, /*tp_compare*/
407 407 (reprfunc)event_repr, /*tp_repr*/
408 408 0, /*tp_as_number*/
409 409 0, /*tp_as_sequence*/
410 410 0, /*tp_as_mapping*/
411 411 0, /*tp_hash */
412 412 0, /*tp_call*/
413 413 0, /*tp_str*/
414 414 0, /*tp_getattro*/
415 415 0, /*tp_setattro*/
416 416 0, /*tp_as_buffer*/
417 417 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
418 418 event_doc, /* tp_doc */
419 419 0, /* tp_traverse */
420 420 0, /* tp_clear */
421 421 0, /* tp_richcompare */
422 422 0, /* tp_weaklistoffset */
423 423 0, /* tp_iter */
424 424 0, /* tp_iternext */
425 425 0, /* tp_methods */
426 426 0, /* tp_members */
427 427 event_getsets, /* tp_getset */
428 428 0, /* tp_base */
429 429 0, /* tp_dict */
430 430 0, /* tp_descr_get */
431 431 0, /* tp_descr_set */
432 432 0, /* tp_dictoffset */
433 433 0, /* tp_init */
434 434 0, /* tp_alloc */
435 435 event_new, /* tp_new */
436 436 };
437 437
438 438 PyObject *read_events(PyObject *self, PyObject *args)
439 439 {
440 440 PyObject *ctor_args = NULL;
441 441 PyObject *pybufsize = NULL;
442 442 PyObject *ret = NULL;
443 443 int bufsize = 65536;
444 444 char *buf = NULL;
445 445 int nread, pos;
446 446 int fd;
447 447
448 448 if (!PyArg_ParseTuple(args, "i|O:read", &fd, &pybufsize))
449 449 goto bail;
450 450
451 451 if (pybufsize && pybufsize != Py_None)
452 452 bufsize = PyInt_AsLong(pybufsize);
453 453
454 454 ret = PyList_New(0);
455 455 if (ret == NULL)
456 456 goto bail;
457 457
458 458 if (bufsize <= 0) {
459 459 int r;
460 460
461 461 Py_BEGIN_ALLOW_THREADS;
462 462 r = ioctl(fd, FIONREAD, &bufsize);
463 463 Py_END_ALLOW_THREADS;
464 464
465 465 if (r == -1) {
466 466 PyErr_SetFromErrno(PyExc_OSError);
467 467 goto bail;
468 468 }
469 469 if (bufsize == 0)
470 470 goto done;
471 471 }
472 472 else {
473 473 static long name_max;
474 474 static long name_fd = -1;
475 475 long min;
476 476
477 477 if (name_fd != fd) {
478 478 name_fd = fd;
479 479 Py_BEGIN_ALLOW_THREADS;
480 480 name_max = fpathconf(fd, _PC_NAME_MAX);
481 481 Py_END_ALLOW_THREADS;
482 482 }
483 483
484 484 min = sizeof(struct inotify_event) + name_max + 1;
485 485
486 486 if (bufsize < min) {
487 487 PyErr_Format(PyExc_ValueError,
488 488 "bufsize must be at least %d", (int)min);
489 489 goto bail;
490 490 }
491 491 }
492 492
493 493 buf = alloca(bufsize);
494 494
495 495 Py_BEGIN_ALLOW_THREADS;
496 496 nread = read(fd, buf, bufsize);
497 497 Py_END_ALLOW_THREADS;
498 498
499 499 if (nread == -1) {
500 500 PyErr_SetFromErrno(PyExc_OSError);
501 501 goto bail;
502 502 }
503 503
504 504 ctor_args = PyTuple_New(0);
505 505
506 506 if (ctor_args == NULL)
507 507 goto bail;
508 508
509 509 pos = 0;
510 510
511 511 while (pos < nread) {
512 512 struct inotify_event *in = (struct inotify_event *)(buf + pos);
513 513 struct event *evt;
514 514 PyObject *obj;
515 515
516 516 obj = PyObject_CallObject((PyObject *)&event_type, ctor_args);
517 517
518 518 if (obj == NULL)
519 519 goto bail;
520 520
521 521 evt = (struct event *)obj;
522 522
523 523 evt->wd = PyInt_FromLong(in->wd);
524 524 evt->mask = PyInt_FromLong(in->mask);
525 525 if (in->mask & IN_MOVE)
526 526 evt->cookie = PyInt_FromLong(in->cookie);
527 527 else {
528 528 Py_INCREF(Py_None);
529 529 evt->cookie = Py_None;
530 530 }
531 531 if (in->len)
532 532 evt->name = PyString_FromString(in->name);
533 533 else {
534 534 Py_INCREF(Py_None);
535 535 evt->name = Py_None;
536 536 }
537 537
538 538 if (!evt->wd || !evt->mask || !evt->cookie || !evt->name)
539 539 goto mybail;
540 540
541 541 if (PyList_Append(ret, obj) == -1)
542 542 goto mybail;
543 543
544 544 pos += sizeof(struct inotify_event) + in->len;
545 545 continue;
546 546
547 547 mybail:
548 548 Py_CLEAR(evt->wd);
549 549 Py_CLEAR(evt->mask);
550 550 Py_CLEAR(evt->cookie);
551 551 Py_CLEAR(evt->name);
552 552 Py_DECREF(obj);
553 553
554 554 goto bail;
555 555 }
556 556
557 557 goto done;
558 558
559 559 bail:
560 560 Py_CLEAR(ret);
561 561
562 562 done:
563 563 Py_XDECREF(ctor_args);
564 564
565 565 return ret;
566 566 }
567 567
568 568 static int init_globals(void)
569 569 {
570 570 join = PyString_FromString("|");
571 571 er_wm = PyString_FromString("event(wd=%d, mask=%s)");
572 572 er_wmn = PyString_FromString("event(wd=%d, mask=%s, name=%s)");
573 573 er_wmc = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x)");
574 574 er_wmcn = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x, name=%s)");
575 575
576 576 return join && er_wm && er_wmn && er_wmc && er_wmcn;
577 577 }
578 578
579 579 PyDoc_STRVAR(
580 580 read_doc,
581 581 "read(fd, bufsize[=65536]) -> list_of_events\n"
582 582 "\n"
583 583 "\nRead inotify events from a file descriptor.\n"
584 584 "\n"
585 585 " fd: file descriptor returned by init()\n"
586 586 " bufsize: size of buffer to read into, in bytes\n"
587 587 "\n"
588 588 "Return a list of event objects.\n"
589 589 "\n"
590 590 "If bufsize is > 0, block until events are available to be read.\n"
591 591 "Otherwise, immediately return all events that can be read without\n"
592 592 "blocking.");
593 593
594 594 static PyMethodDef methods[] = {
595 595 {"init", init, METH_VARARGS, init_doc},
596 596 {"add_watch", add_watch, METH_VARARGS, add_watch_doc},
597 597 {"remove_watch", remove_watch, METH_VARARGS, remove_watch_doc},
598 598 {"read", read_events, METH_VARARGS, read_doc},
599 599 {"decode_mask", pydecode_mask, METH_VARARGS, decode_mask_doc},
600 600 {NULL},
601 601 };
602 602
603 603 #ifdef IS_PY3K
604 604 static struct PyModuleDef _inotify_module = {
605 605 PyModuleDef_HEAD_INIT,
606 606 "_inotify",
607 607 doc,
608 608 -1,
609 609 methods
610 610 };
611 611
612 612 PyMODINIT_FUNC PyInit__inotify(void)
613 613 {
614 614 PyObject *mod, *dict;
615 615
616 616 mod = PyModule_Create(&_inotify_module);
617 617
618 618 if (mod == NULL)
619 619 return NULL;
620 620
621 621 if (!init_globals())
622 622 return;
623 623
624 624 dict = PyModule_GetDict(mod);
625 625
626 626 if (dict)
627 627 define_consts(dict);
628 628
629 629 return mod;
630 630 }
631 631 #else
632 632 void init_inotify(void)
633 633 {
634 634 PyObject *mod, *dict;
635 635
636 636 if (PyType_Ready(&event_type) == -1)
637 637 return;
638 638
639 639 if (!init_globals())
640 640 return;
641 641
642 642 mod = Py_InitModule3("_inotify", methods, doc);
643 643
644 644 dict = PyModule_GetDict(mod);
645 645
646 646 if (dict)
647 647 define_consts(dict);
648 648 }
649 649 #endif
@@ -1,5902 +1,5902
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, difflib, time, tempfile, errno
12 12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 13 import patch, help, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, hbisect
15 15 import sshserver, hgweb, hgweb.server, commandserver
16 16 import merge as mergemod
17 17 import minirst, revset, fileset
18 18 import dagparser, context, simplemerge, graphmod
19 19 import random, setdiscovery, treediscovery, dagutil, pvec, localrepo
20 20 import phases, obsolete
21 21
22 22 table = {}
23 23
24 24 command = cmdutil.command(table)
25 25
26 26 # common command options
27 27
28 28 globalopts = [
29 29 ('R', 'repository', '',
30 30 _('repository root directory or name of overlay bundle file'),
31 31 _('REPO')),
32 32 ('', 'cwd', '',
33 33 _('change working directory'), _('DIR')),
34 34 ('y', 'noninteractive', None,
35 35 _('do not prompt, automatically pick the first choice for all prompts')),
36 36 ('q', 'quiet', None, _('suppress output')),
37 37 ('v', 'verbose', None, _('enable additional output')),
38 38 ('', 'config', [],
39 39 _('set/override config option (use \'section.name=value\')'),
40 40 _('CONFIG')),
41 41 ('', 'debug', None, _('enable debugging output')),
42 42 ('', 'debugger', None, _('start debugger')),
43 43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
44 44 _('ENCODE')),
45 45 ('', 'encodingmode', encoding.encodingmode,
46 46 _('set the charset encoding mode'), _('MODE')),
47 47 ('', 'traceback', None, _('always print a traceback on exception')),
48 48 ('', 'time', None, _('time how long the command takes')),
49 49 ('', 'profile', None, _('print command execution profile')),
50 50 ('', 'version', None, _('output version information and exit')),
51 51 ('h', 'help', None, _('display help and exit')),
52 52 ]
53 53
54 54 dryrunopts = [('n', 'dry-run', None,
55 55 _('do not perform actions, just print output'))]
56 56
57 57 remoteopts = [
58 58 ('e', 'ssh', '',
59 59 _('specify ssh command to use'), _('CMD')),
60 60 ('', 'remotecmd', '',
61 61 _('specify hg command to run on the remote side'), _('CMD')),
62 62 ('', 'insecure', None,
63 63 _('do not verify server certificate (ignoring web.cacerts config)')),
64 64 ]
65 65
66 66 walkopts = [
67 67 ('I', 'include', [],
68 68 _('include names matching the given patterns'), _('PATTERN')),
69 69 ('X', 'exclude', [],
70 70 _('exclude names matching the given patterns'), _('PATTERN')),
71 71 ]
72 72
73 73 commitopts = [
74 74 ('m', 'message', '',
75 75 _('use text as commit message'), _('TEXT')),
76 76 ('l', 'logfile', '',
77 77 _('read commit message from file'), _('FILE')),
78 78 ]
79 79
80 80 commitopts2 = [
81 81 ('d', 'date', '',
82 82 _('record the specified date as commit date'), _('DATE')),
83 83 ('u', 'user', '',
84 84 _('record the specified user as committer'), _('USER')),
85 85 ]
86 86
87 87 templateopts = [
88 88 ('', 'style', '',
89 89 _('display using template map file'), _('STYLE')),
90 90 ('', 'template', '',
91 91 _('display with template'), _('TEMPLATE')),
92 92 ]
93 93
94 94 logopts = [
95 95 ('p', 'patch', None, _('show patch')),
96 96 ('g', 'git', None, _('use git extended diff format')),
97 97 ('l', 'limit', '',
98 98 _('limit number of changes displayed'), _('NUM')),
99 99 ('M', 'no-merges', None, _('do not show merges')),
100 100 ('', 'stat', None, _('output diffstat-style summary of changes')),
101 101 ('G', 'graph', None, _("show the revision DAG")),
102 102 ] + templateopts
103 103
104 104 diffopts = [
105 105 ('a', 'text', None, _('treat all files as text')),
106 106 ('g', 'git', None, _('use git extended diff format')),
107 107 ('', 'nodates', None, _('omit dates from diff headers'))
108 108 ]
109 109
110 110 diffwsopts = [
111 111 ('w', 'ignore-all-space', None,
112 112 _('ignore white space when comparing lines')),
113 113 ('b', 'ignore-space-change', None,
114 114 _('ignore changes in the amount of white space')),
115 115 ('B', 'ignore-blank-lines', None,
116 116 _('ignore changes whose lines are all blank')),
117 117 ]
118 118
119 119 diffopts2 = [
120 120 ('p', 'show-function', None, _('show which function each change is in')),
121 121 ('', 'reverse', None, _('produce a diff that undoes the changes')),
122 122 ] + diffwsopts + [
123 123 ('U', 'unified', '',
124 124 _('number of lines of context to show'), _('NUM')),
125 125 ('', 'stat', None, _('output diffstat-style summary of changes')),
126 126 ]
127 127
128 128 mergetoolopts = [
129 129 ('t', 'tool', '', _('specify merge tool')),
130 130 ]
131 131
132 132 similarityopts = [
133 133 ('s', 'similarity', '',
134 134 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
135 135 ]
136 136
137 137 subrepoopts = [
138 138 ('S', 'subrepos', None,
139 139 _('recurse into subrepositories'))
140 140 ]
141 141
142 142 # Commands start here, listed alphabetically
143 143
144 144 @command('^add',
145 145 walkopts + subrepoopts + dryrunopts,
146 146 _('[OPTION]... [FILE]...'))
147 147 def add(ui, repo, *pats, **opts):
148 148 """add the specified files on the next commit
149 149
150 150 Schedule files to be version controlled and added to the
151 151 repository.
152 152
153 153 The files will be added to the repository at the next commit. To
154 154 undo an add before that, see :hg:`forget`.
155 155
156 156 If no names are given, add all files to the repository.
157 157
158 158 .. container:: verbose
159 159
160 160 An example showing how new (unknown) files are added
161 161 automatically by :hg:`add`::
162 162
163 163 $ ls
164 164 foo.c
165 165 $ hg status
166 166 ? foo.c
167 167 $ hg add
168 168 adding foo.c
169 169 $ hg status
170 170 A foo.c
171 171
172 172 Returns 0 if all files are successfully added.
173 173 """
174 174
175 175 m = scmutil.match(repo[None], pats, opts)
176 176 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
177 177 opts.get('subrepos'), prefix="", explicitonly=False)
178 178 return rejected and 1 or 0
179 179
180 180 @command('addremove',
181 181 similarityopts + walkopts + dryrunopts,
182 182 _('[OPTION]... [FILE]...'))
183 183 def addremove(ui, repo, *pats, **opts):
184 184 """add all new files, delete all missing files
185 185
186 186 Add all new files and remove all missing files from the
187 187 repository.
188 188
189 189 New files are ignored if they match any of the patterns in
190 190 ``.hgignore``. As with add, these changes take effect at the next
191 191 commit.
192 192
193 193 Use the -s/--similarity option to detect renamed files. This
194 194 option takes a percentage between 0 (disabled) and 100 (files must
195 195 be identical) as its parameter. With a parameter greater than 0,
196 196 this compares every removed file with every added file and records
197 197 those similar enough as renames. Detecting renamed files this way
198 198 can be expensive. After using this option, :hg:`status -C` can be
199 199 used to check which files were identified as moved or renamed. If
200 200 not specified, -s/--similarity defaults to 100 and only renames of
201 201 identical files are detected.
202 202
203 203 Returns 0 if all files are successfully added.
204 204 """
205 205 try:
206 206 sim = float(opts.get('similarity') or 100)
207 207 except ValueError:
208 208 raise util.Abort(_('similarity must be a number'))
209 209 if sim < 0 or sim > 100:
210 210 raise util.Abort(_('similarity must be between 0 and 100'))
211 211 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
212 212
213 213 @command('^annotate|blame',
214 214 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
215 215 ('', 'follow', None,
216 216 _('follow copies/renames and list the filename (DEPRECATED)')),
217 217 ('', 'no-follow', None, _("don't follow copies and renames")),
218 218 ('a', 'text', None, _('treat all files as text')),
219 219 ('u', 'user', None, _('list the author (long with -v)')),
220 220 ('f', 'file', None, _('list the filename')),
221 221 ('d', 'date', None, _('list the date (short with -q)')),
222 222 ('n', 'number', None, _('list the revision number (default)')),
223 223 ('c', 'changeset', None, _('list the changeset')),
224 224 ('l', 'line-number', None, _('show line number at the first appearance'))
225 225 ] + diffwsopts + walkopts,
226 226 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
227 227 def annotate(ui, repo, *pats, **opts):
228 228 """show changeset information by line for each file
229 229
230 230 List changes in files, showing the revision id responsible for
231 231 each line
232 232
233 233 This command is useful for discovering when a change was made and
234 234 by whom.
235 235
236 236 Without the -a/--text option, annotate will avoid processing files
237 237 it detects as binary. With -a, annotate will annotate the file
238 238 anyway, although the results will probably be neither useful
239 239 nor desirable.
240 240
241 241 Returns 0 on success.
242 242 """
243 243 if opts.get('follow'):
244 244 # --follow is deprecated and now just an alias for -f/--file
245 245 # to mimic the behavior of Mercurial before version 1.5
246 246 opts['file'] = True
247 247
248 248 datefunc = ui.quiet and util.shortdate or util.datestr
249 249 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
250 250
251 251 if not pats:
252 252 raise util.Abort(_('at least one filename or pattern is required'))
253 253
254 254 hexfn = ui.debugflag and hex or short
255 255
256 256 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
257 257 ('number', ' ', lambda x: str(x[0].rev())),
258 258 ('changeset', ' ', lambda x: hexfn(x[0].node())),
259 259 ('date', ' ', getdate),
260 260 ('file', ' ', lambda x: x[0].path()),
261 261 ('line_number', ':', lambda x: str(x[1])),
262 262 ]
263 263
264 264 if (not opts.get('user') and not opts.get('changeset')
265 265 and not opts.get('date') and not opts.get('file')):
266 266 opts['number'] = True
267 267
268 268 linenumber = opts.get('line_number') is not None
269 269 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
270 270 raise util.Abort(_('at least one of -n/-c is required for -l'))
271 271
272 272 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
273 273 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
274 274
275 275 def bad(x, y):
276 276 raise util.Abort("%s: %s" % (x, y))
277 277
278 278 ctx = scmutil.revsingle(repo, opts.get('rev'))
279 279 m = scmutil.match(ctx, pats, opts)
280 280 m.bad = bad
281 281 follow = not opts.get('no_follow')
282 282 diffopts = patch.diffopts(ui, opts, section='annotate')
283 283 for abs in ctx.walk(m):
284 284 fctx = ctx[abs]
285 285 if not opts.get('text') and util.binary(fctx.data()):
286 286 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
287 287 continue
288 288
289 289 lines = fctx.annotate(follow=follow, linenumber=linenumber,
290 290 diffopts=diffopts)
291 291 pieces = []
292 292
293 293 for f, sep in funcmap:
294 294 l = [f(n) for n, dummy in lines]
295 295 if l:
296 296 sized = [(x, encoding.colwidth(x)) for x in l]
297 297 ml = max([w for x, w in sized])
298 298 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
299 299 for x, w in sized])
300 300
301 301 if pieces:
302 302 for p, l in zip(zip(*pieces), lines):
303 303 ui.write("%s: %s" % ("".join(p), l[1]))
304 304
305 305 if lines and not lines[-1][1].endswith('\n'):
306 306 ui.write('\n')
307 307
308 308 @command('archive',
309 309 [('', 'no-decode', None, _('do not pass files through decoders')),
310 310 ('p', 'prefix', '', _('directory prefix for files in archive'),
311 311 _('PREFIX')),
312 312 ('r', 'rev', '', _('revision to distribute'), _('REV')),
313 313 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
314 314 ] + subrepoopts + walkopts,
315 315 _('[OPTION]... DEST'))
316 316 def archive(ui, repo, dest, **opts):
317 317 '''create an unversioned archive of a repository revision
318 318
319 319 By default, the revision used is the parent of the working
320 320 directory; use -r/--rev to specify a different revision.
321 321
322 322 The archive type is automatically detected based on file
323 323 extension (or override using -t/--type).
324 324
325 325 .. container:: verbose
326 326
327 327 Examples:
328 328
329 329 - create a zip file containing the 1.0 release::
330 330
331 331 hg archive -r 1.0 project-1.0.zip
332 332
333 333 - create a tarball excluding .hg files::
334 334
335 335 hg archive project.tar.gz -X ".hg*"
336 336
337 337 Valid types are:
338 338
339 339 :``files``: a directory full of files (default)
340 340 :``tar``: tar archive, uncompressed
341 341 :``tbz2``: tar archive, compressed using bzip2
342 342 :``tgz``: tar archive, compressed using gzip
343 343 :``uzip``: zip archive, uncompressed
344 344 :``zip``: zip archive, compressed using deflate
345 345
346 346 The exact name of the destination archive or directory is given
347 347 using a format string; see :hg:`help export` for details.
348 348
349 349 Each member added to an archive file has a directory prefix
350 350 prepended. Use -p/--prefix to specify a format string for the
351 351 prefix. The default is the basename of the archive, with suffixes
352 352 removed.
353 353
354 354 Returns 0 on success.
355 355 '''
356 356
357 357 ctx = scmutil.revsingle(repo, opts.get('rev'))
358 358 if not ctx:
359 359 raise util.Abort(_('no working directory: please specify a revision'))
360 360 node = ctx.node()
361 361 dest = cmdutil.makefilename(repo, dest, node)
362 362 if os.path.realpath(dest) == repo.root:
363 363 raise util.Abort(_('repository root cannot be destination'))
364 364
365 365 kind = opts.get('type') or archival.guesskind(dest) or 'files'
366 366 prefix = opts.get('prefix')
367 367
368 368 if dest == '-':
369 369 if kind == 'files':
370 370 raise util.Abort(_('cannot archive plain files to stdout'))
371 371 dest = cmdutil.makefileobj(repo, dest)
372 372 if not prefix:
373 373 prefix = os.path.basename(repo.root) + '-%h'
374 374
375 375 prefix = cmdutil.makefilename(repo, prefix, node)
376 376 matchfn = scmutil.match(ctx, [], opts)
377 377 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
378 378 matchfn, prefix, subrepos=opts.get('subrepos'))
379 379
380 380 @command('backout',
381 381 [('', 'merge', None, _('merge with old dirstate parent after backout')),
382 382 ('', 'parent', '',
383 383 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
384 384 ('r', 'rev', '', _('revision to backout'), _('REV')),
385 385 ] + mergetoolopts + walkopts + commitopts + commitopts2,
386 386 _('[OPTION]... [-r] REV'))
387 387 def backout(ui, repo, node=None, rev=None, **opts):
388 388 '''reverse effect of earlier changeset
389 389
390 390 Prepare a new changeset with the effect of REV undone in the
391 391 current working directory.
392 392
393 393 If REV is the parent of the working directory, then this new changeset
394 394 is committed automatically. Otherwise, hg needs to merge the
395 395 changes and the merged result is left uncommitted.
396 396
397 397 .. note::
398 398 backout cannot be used to fix either an unwanted or
399 399 incorrect merge.
400 400
401 401 .. container:: verbose
402 402
403 403 By default, the pending changeset will have one parent,
404 404 maintaining a linear history. With --merge, the pending
405 405 changeset will instead have two parents: the old parent of the
406 406 working directory and a new child of REV that simply undoes REV.
407 407
408 408 Before version 1.7, the behavior without --merge was equivalent
409 409 to specifying --merge followed by :hg:`update --clean .` to
410 410 cancel the merge and leave the child of REV as a head to be
411 411 merged separately.
412 412
413 413 See :hg:`help dates` for a list of formats valid for -d/--date.
414 414
415 415 Returns 0 on success.
416 416 '''
417 417 if rev and node:
418 418 raise util.Abort(_("please specify just one revision"))
419 419
420 420 if not rev:
421 421 rev = node
422 422
423 423 if not rev:
424 424 raise util.Abort(_("please specify a revision to backout"))
425 425
426 426 date = opts.get('date')
427 427 if date:
428 428 opts['date'] = util.parsedate(date)
429 429
430 430 cmdutil.bailifchanged(repo)
431 431 node = scmutil.revsingle(repo, rev).node()
432 432
433 433 op1, op2 = repo.dirstate.parents()
434 434 a = repo.changelog.ancestor(op1, node)
435 435 if a != node:
436 436 raise util.Abort(_('cannot backout change on a different branch'))
437 437
438 438 p1, p2 = repo.changelog.parents(node)
439 439 if p1 == nullid:
440 440 raise util.Abort(_('cannot backout a change with no parents'))
441 441 if p2 != nullid:
442 442 if not opts.get('parent'):
443 443 raise util.Abort(_('cannot backout a merge changeset'))
444 444 p = repo.lookup(opts['parent'])
445 445 if p not in (p1, p2):
446 446 raise util.Abort(_('%s is not a parent of %s') %
447 447 (short(p), short(node)))
448 448 parent = p
449 449 else:
450 450 if opts.get('parent'):
451 451 raise util.Abort(_('cannot use --parent on non-merge changeset'))
452 452 parent = p1
453 453
454 454 # the backout should appear on the same branch
455 455 wlock = repo.wlock()
456 456 try:
457 457 branch = repo.dirstate.branch()
458 458 hg.clean(repo, node, show_stats=False)
459 459 repo.dirstate.setbranch(branch)
460 460 revert_opts = opts.copy()
461 461 revert_opts['date'] = None
462 462 revert_opts['all'] = True
463 463 revert_opts['rev'] = hex(parent)
464 464 revert_opts['no_backup'] = None
465 465 revert(ui, repo, **revert_opts)
466 466 if not opts.get('merge') and op1 != node:
467 467 try:
468 468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
469 469 return hg.update(repo, op1)
470 470 finally:
471 471 ui.setconfig('ui', 'forcemerge', '')
472 472
473 473 commit_opts = opts.copy()
474 474 commit_opts['addremove'] = False
475 475 if not commit_opts['message'] and not commit_opts['logfile']:
476 476 # we don't translate commit messages
477 477 commit_opts['message'] = "Backed out changeset %s" % short(node)
478 478 commit_opts['force_editor'] = True
479 479 commit(ui, repo, **commit_opts)
480 480 def nice(node):
481 481 return '%d:%s' % (repo.changelog.rev(node), short(node))
482 482 ui.status(_('changeset %s backs out changeset %s\n') %
483 483 (nice(repo.changelog.tip()), nice(node)))
484 484 if opts.get('merge') and op1 != node:
485 485 hg.clean(repo, op1, show_stats=False)
486 486 ui.status(_('merging with changeset %s\n')
487 487 % nice(repo.changelog.tip()))
488 488 try:
489 489 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
490 490 return hg.merge(repo, hex(repo.changelog.tip()))
491 491 finally:
492 492 ui.setconfig('ui', 'forcemerge', '')
493 493 finally:
494 494 wlock.release()
495 495 return 0
496 496
497 497 @command('bisect',
498 498 [('r', 'reset', False, _('reset bisect state')),
499 499 ('g', 'good', False, _('mark changeset good')),
500 500 ('b', 'bad', False, _('mark changeset bad')),
501 501 ('s', 'skip', False, _('skip testing changeset')),
502 502 ('e', 'extend', False, _('extend the bisect range')),
503 503 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
504 504 ('U', 'noupdate', False, _('do not update to target'))],
505 505 _("[-gbsr] [-U] [-c CMD] [REV]"))
506 506 def bisect(ui, repo, rev=None, extra=None, command=None,
507 507 reset=None, good=None, bad=None, skip=None, extend=None,
508 508 noupdate=None):
509 509 """subdivision search of changesets
510 510
511 511 This command helps to find changesets which introduce problems. To
512 512 use, mark the earliest changeset you know exhibits the problem as
513 513 bad, then mark the latest changeset which is free from the problem
514 514 as good. Bisect will update your working directory to a revision
515 515 for testing (unless the -U/--noupdate option is specified). Once
516 516 you have performed tests, mark the working directory as good or
517 517 bad, and bisect will either update to another candidate changeset
518 518 or announce that it has found the bad revision.
519 519
520 520 As a shortcut, you can also use the revision argument to mark a
521 521 revision as good or bad without checking it out first.
522 522
523 523 If you supply a command, it will be used for automatic bisection.
524 524 The environment variable HG_NODE will contain the ID of the
525 525 changeset being tested. The exit status of the command will be
526 526 used to mark revisions as good or bad: status 0 means good, 125
527 527 means to skip the revision, 127 (command not found) will abort the
528 528 bisection, and any other non-zero exit status means the revision
529 529 is bad.
530 530
531 531 .. container:: verbose
532 532
533 533 Some examples:
534 534
535 535 - start a bisection with known bad revision 12, and good revision 34::
536 536
537 537 hg bisect --bad 34
538 538 hg bisect --good 12
539 539
540 540 - advance the current bisection by marking current revision as good or
541 541 bad::
542 542
543 543 hg bisect --good
544 544 hg bisect --bad
545 545
546 546 - mark the current revision, or a known revision, to be skipped (e.g. if
547 547 that revision is not usable because of another issue)::
548 548
549 549 hg bisect --skip
550 550 hg bisect --skip 23
551 551
552 552 - forget the current bisection::
553 553
554 554 hg bisect --reset
555 555
556 556 - use 'make && make tests' to automatically find the first broken
557 557 revision::
558 558
559 559 hg bisect --reset
560 560 hg bisect --bad 34
561 561 hg bisect --good 12
562 562 hg bisect --command 'make && make tests'
563 563
564 564 - see all changesets whose states are already known in the current
565 565 bisection::
566 566
567 567 hg log -r "bisect(pruned)"
568 568
569 569 - see the changeset currently being bisected (especially useful
570 570 if running with -U/--noupdate)::
571 571
572 572 hg log -r "bisect(current)"
573 573
574 574 - see all changesets that took part in the current bisection::
575 575
576 576 hg log -r "bisect(range)"
577 577
578 578 - with the graphlog extension, you can even get a nice graph::
579 579
580 580 hg log --graph -r "bisect(range)"
581 581
582 582 See :hg:`help revsets` for more about the `bisect()` keyword.
583 583
584 584 Returns 0 on success.
585 585 """
586 586 def extendbisectrange(nodes, good):
587 587 # bisect is incomplete when it ends on a merge node and
588 588 # one of the parent was not checked.
589 589 parents = repo[nodes[0]].parents()
590 590 if len(parents) > 1:
591 591 side = good and state['bad'] or state['good']
592 592 num = len(set(i.node() for i in parents) & set(side))
593 593 if num == 1:
594 594 return parents[0].ancestor(parents[1])
595 595 return None
596 596
597 597 def print_result(nodes, good):
598 598 displayer = cmdutil.show_changeset(ui, repo, {})
599 599 if len(nodes) == 1:
600 600 # narrowed it down to a single revision
601 601 if good:
602 602 ui.write(_("The first good revision is:\n"))
603 603 else:
604 604 ui.write(_("The first bad revision is:\n"))
605 605 displayer.show(repo[nodes[0]])
606 606 extendnode = extendbisectrange(nodes, good)
607 607 if extendnode is not None:
608 608 ui.write(_('Not all ancestors of this changeset have been'
609 609 ' checked.\nUse bisect --extend to continue the '
610 610 'bisection from\nthe common ancestor, %s.\n')
611 611 % extendnode)
612 612 else:
613 613 # multiple possible revisions
614 614 if good:
615 615 ui.write(_("Due to skipped revisions, the first "
616 616 "good revision could be any of:\n"))
617 617 else:
618 618 ui.write(_("Due to skipped revisions, the first "
619 619 "bad revision could be any of:\n"))
620 620 for n in nodes:
621 621 displayer.show(repo[n])
622 622 displayer.close()
623 623
624 624 def check_state(state, interactive=True):
625 625 if not state['good'] or not state['bad']:
626 626 if (good or bad or skip or reset) and interactive:
627 627 return
628 628 if not state['good']:
629 629 raise util.Abort(_('cannot bisect (no known good revisions)'))
630 630 else:
631 631 raise util.Abort(_('cannot bisect (no known bad revisions)'))
632 632 return True
633 633
634 634 # backward compatibility
635 635 if rev in "good bad reset init".split():
636 636 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
637 637 cmd, rev, extra = rev, extra, None
638 638 if cmd == "good":
639 639 good = True
640 640 elif cmd == "bad":
641 641 bad = True
642 642 else:
643 643 reset = True
644 644 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
645 645 raise util.Abort(_('incompatible arguments'))
646 646
647 647 if reset:
648 648 p = repo.join("bisect.state")
649 649 if os.path.exists(p):
650 650 os.unlink(p)
651 651 return
652 652
653 653 state = hbisect.load_state(repo)
654 654
655 655 if command:
656 656 changesets = 1
657 657 try:
658 658 node = state['current'][0]
659 659 except LookupError:
660 660 if noupdate:
661 661 raise util.Abort(_('current bisect revision is unknown - '
662 662 'start a new bisect to fix'))
663 663 node, p2 = repo.dirstate.parents()
664 664 if p2 != nullid:
665 665 raise util.Abort(_('current bisect revision is a merge'))
666 666 try:
667 667 while changesets:
668 668 # update state
669 669 state['current'] = [node]
670 670 hbisect.save_state(repo, state)
671 671 status = util.system(command,
672 672 environ={'HG_NODE': hex(node)},
673 673 out=ui.fout)
674 674 if status == 125:
675 675 transition = "skip"
676 676 elif status == 0:
677 677 transition = "good"
678 678 # status < 0 means process was killed
679 679 elif status == 127:
680 680 raise util.Abort(_("failed to execute %s") % command)
681 681 elif status < 0:
682 682 raise util.Abort(_("%s killed") % command)
683 683 else:
684 684 transition = "bad"
685 685 ctx = scmutil.revsingle(repo, rev, node)
686 686 rev = None # clear for future iterations
687 687 state[transition].append(ctx.node())
688 688 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
689 689 check_state(state, interactive=False)
690 690 # bisect
691 691 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
692 692 # update to next check
693 693 node = nodes[0]
694 694 if not noupdate:
695 695 cmdutil.bailifchanged(repo)
696 696 hg.clean(repo, node, show_stats=False)
697 697 finally:
698 698 state['current'] = [node]
699 699 hbisect.save_state(repo, state)
700 700 print_result(nodes, good)
701 701 return
702 702
703 703 # update state
704 704
705 705 if rev:
706 706 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
707 707 else:
708 708 nodes = [repo.lookup('.')]
709 709
710 710 if good or bad or skip:
711 711 if good:
712 712 state['good'] += nodes
713 713 elif bad:
714 714 state['bad'] += nodes
715 715 elif skip:
716 716 state['skip'] += nodes
717 717 hbisect.save_state(repo, state)
718 718
719 719 if not check_state(state):
720 720 return
721 721
722 722 # actually bisect
723 723 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
724 724 if extend:
725 725 if not changesets:
726 726 extendnode = extendbisectrange(nodes, good)
727 727 if extendnode is not None:
728 728 ui.write(_("Extending search to changeset %d:%s\n"
729 729 % (extendnode.rev(), extendnode)))
730 730 state['current'] = [extendnode.node()]
731 731 hbisect.save_state(repo, state)
732 732 if noupdate:
733 733 return
734 734 cmdutil.bailifchanged(repo)
735 735 return hg.clean(repo, extendnode.node())
736 736 raise util.Abort(_("nothing to extend"))
737 737
738 738 if changesets == 0:
739 739 print_result(nodes, good)
740 740 else:
741 741 assert len(nodes) == 1 # only a single node can be tested next
742 742 node = nodes[0]
743 743 # compute the approximate number of remaining tests
744 744 tests, size = 0, 2
745 745 while size <= changesets:
746 746 tests, size = tests + 1, size * 2
747 747 rev = repo.changelog.rev(node)
748 748 ui.write(_("Testing changeset %d:%s "
749 749 "(%d changesets remaining, ~%d tests)\n")
750 750 % (rev, short(node), changesets, tests))
751 751 state['current'] = [node]
752 752 hbisect.save_state(repo, state)
753 753 if not noupdate:
754 754 cmdutil.bailifchanged(repo)
755 755 return hg.clean(repo, node)
756 756
757 757 @command('bookmarks',
758 758 [('f', 'force', False, _('force')),
759 759 ('r', 'rev', '', _('revision'), _('REV')),
760 760 ('d', 'delete', False, _('delete a given bookmark')),
761 761 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
762 762 ('i', 'inactive', False, _('mark a bookmark inactive'))],
763 763 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
764 764 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
765 765 rename=None, inactive=False):
766 766 '''track a line of development with movable markers
767 767
768 768 Bookmarks are pointers to certain commits that move when committing.
769 769 Bookmarks are local. They can be renamed, copied and deleted. It is
770 770 possible to use :hg:`merge NAME` to merge from a given bookmark, and
771 771 :hg:`update NAME` to update to a given bookmark.
772 772
773 773 You can use :hg:`bookmark NAME` to set a bookmark on the working
774 774 directory's parent revision with the given name. If you specify
775 775 a revision using -r REV (where REV may be an existing bookmark),
776 776 the bookmark is assigned to that revision.
777 777
778 778 Bookmarks can be pushed and pulled between repositories (see :hg:`help
779 779 push` and :hg:`help pull`). This requires both the local and remote
780 780 repositories to support bookmarks. For versions prior to 1.8, this means
781 781 the bookmarks extension must be enabled.
782 782
783 783 With -i/--inactive, the new bookmark will not be made the active
784 784 bookmark. If -r/--rev is given, the new bookmark will not be made
785 785 active even if -i/--inactive is not given. If no NAME is given, the
786 786 current active bookmark will be marked inactive.
787 787 '''
788 788 hexfn = ui.debugflag and hex or short
789 789 marks = repo._bookmarks
790 790 cur = repo.changectx('.').node()
791 791
792 792 if delete:
793 793 if mark is None:
794 794 raise util.Abort(_("bookmark name required"))
795 795 if mark not in marks:
796 796 raise util.Abort(_("bookmark '%s' does not exist") % mark)
797 797 if mark == repo._bookmarkcurrent:
798 798 bookmarks.setcurrent(repo, None)
799 799 del marks[mark]
800 800 bookmarks.write(repo)
801 801 return
802 802
803 803 if rename:
804 804 if rename not in marks:
805 805 raise util.Abort(_("bookmark '%s' does not exist") % rename)
806 806 if mark in marks and not force:
807 807 raise util.Abort(_("bookmark '%s' already exists "
808 808 "(use -f to force)") % mark)
809 809 if mark is None:
810 810 raise util.Abort(_("new bookmark name required"))
811 811 marks[mark] = marks[rename]
812 812 if repo._bookmarkcurrent == rename and not inactive:
813 813 bookmarks.setcurrent(repo, mark)
814 814 del marks[rename]
815 815 bookmarks.write(repo)
816 816 return
817 817
818 818 if mark is not None:
819 819 if "\n" in mark:
820 820 raise util.Abort(_("bookmark name cannot contain newlines"))
821 821 mark = mark.strip()
822 822 if not mark:
823 823 raise util.Abort(_("bookmark names cannot consist entirely of "
824 824 "whitespace"))
825 825 if inactive and mark == repo._bookmarkcurrent:
826 826 bookmarks.setcurrent(repo, None)
827 827 return
828 828 if mark in marks and not force:
829 829 raise util.Abort(_("bookmark '%s' already exists "
830 830 "(use -f to force)") % mark)
831 831 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
832 832 and not force):
833 833 raise util.Abort(
834 834 _("a bookmark cannot have the name of an existing branch"))
835 835 if rev:
836 836 marks[mark] = repo.lookup(rev)
837 837 else:
838 838 marks[mark] = cur
839 839 if not inactive and cur == marks[mark]:
840 840 bookmarks.setcurrent(repo, mark)
841 841 bookmarks.write(repo)
842 842 return
843 843
844 844 if mark is None:
845 845 if rev:
846 846 raise util.Abort(_("bookmark name required"))
847 847 if len(marks) == 0:
848 848 ui.status(_("no bookmarks set\n"))
849 849 else:
850 850 for bmark, n in sorted(marks.iteritems()):
851 851 current = repo._bookmarkcurrent
852 852 if bmark == current and n == cur:
853 853 prefix, label = '*', 'bookmarks.current'
854 854 else:
855 855 prefix, label = ' ', ''
856 856
857 857 if ui.quiet:
858 858 ui.write("%s\n" % bmark, label=label)
859 859 else:
860 860 ui.write(" %s %-25s %d:%s\n" % (
861 861 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
862 862 label=label)
863 863 return
864 864
865 865 @command('branch',
866 866 [('f', 'force', None,
867 867 _('set branch name even if it shadows an existing branch')),
868 868 ('C', 'clean', None, _('reset branch name to parent branch name'))],
869 869 _('[-fC] [NAME]'))
870 870 def branch(ui, repo, label=None, **opts):
871 871 """set or show the current branch name
872 872
873 873 .. note::
874 874 Branch names are permanent and global. Use :hg:`bookmark` to create a
875 875 light-weight bookmark instead. See :hg:`help glossary` for more
876 876 information about named branches and bookmarks.
877 877
878 878 With no argument, show the current branch name. With one argument,
879 879 set the working directory branch name (the branch will not exist
880 880 in the repository until the next commit). Standard practice
881 881 recommends that primary development take place on the 'default'
882 882 branch.
883 883
884 884 Unless -f/--force is specified, branch will not let you set a
885 885 branch name that already exists, even if it's inactive.
886 886
887 887 Use -C/--clean to reset the working directory branch to that of
888 888 the parent of the working directory, negating a previous branch
889 889 change.
890 890
891 891 Use the command :hg:`update` to switch to an existing branch. Use
892 892 :hg:`commit --close-branch` to mark this branch as closed.
893 893
894 894 Returns 0 on success.
895 895 """
896 896 if not opts.get('clean') and not label:
897 897 ui.write("%s\n" % repo.dirstate.branch())
898 898 return
899 899
900 900 wlock = repo.wlock()
901 901 try:
902 902 if opts.get('clean'):
903 903 label = repo[None].p1().branch()
904 904 repo.dirstate.setbranch(label)
905 905 ui.status(_('reset working directory to branch %s\n') % label)
906 906 elif label:
907 907 if not opts.get('force') and label in repo.branchmap():
908 908 if label not in [p.branch() for p in repo.parents()]:
909 909 raise util.Abort(_('a branch of the same name already'
910 910 ' exists'),
911 911 # i18n: "it" refers to an existing branch
912 912 hint=_("use 'hg update' to switch to it"))
913 913 repo.dirstate.setbranch(label)
914 914 ui.status(_('marked working directory as branch %s\n') % label)
915 915 ui.status(_('(branches are permanent and global, '
916 916 'did you want a bookmark?)\n'))
917 917 finally:
918 918 wlock.release()
919 919
920 920 @command('branches',
921 921 [('a', 'active', False, _('show only branches that have unmerged heads')),
922 922 ('c', 'closed', False, _('show normal and closed branches'))],
923 923 _('[-ac]'))
924 924 def branches(ui, repo, active=False, closed=False):
925 925 """list repository named branches
926 926
927 927 List the repository's named branches, indicating which ones are
928 928 inactive. If -c/--closed is specified, also list branches which have
929 929 been marked closed (see :hg:`commit --close-branch`).
930 930
931 931 If -a/--active is specified, only show active branches. A branch
932 932 is considered active if it contains repository heads.
933 933
934 934 Use the command :hg:`update` to switch to an existing branch.
935 935
936 936 Returns 0.
937 937 """
938 938
939 939 hexfunc = ui.debugflag and hex or short
940 940
941 941 activebranches = set([repo[n].branch() for n in repo.heads()])
942 942 branches = []
943 943 for tag, heads in repo.branchmap().iteritems():
944 944 for h in reversed(heads):
945 945 ctx = repo[h]
946 946 isopen = not ctx.closesbranch()
947 947 if isopen:
948 948 tip = ctx
949 949 break
950 950 else:
951 951 tip = repo[heads[-1]]
952 952 isactive = tag in activebranches and isopen
953 953 branches.append((tip, isactive, isopen))
954 954 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
955 955 reverse=True)
956 956
957 957 for ctx, isactive, isopen in branches:
958 958 if (not active) or isactive:
959 959 if isactive:
960 960 label = 'branches.active'
961 961 notice = ''
962 962 elif not isopen:
963 963 if not closed:
964 964 continue
965 965 label = 'branches.closed'
966 966 notice = _(' (closed)')
967 967 else:
968 968 label = 'branches.inactive'
969 969 notice = _(' (inactive)')
970 970 if ctx.branch() == repo.dirstate.branch():
971 971 label = 'branches.current'
972 972 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
973 973 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
974 974 'log.changeset')
975 975 tag = ui.label(ctx.branch(), label)
976 976 if ui.quiet:
977 977 ui.write("%s\n" % tag)
978 978 else:
979 979 ui.write("%s %s%s\n" % (tag, rev, notice))
980 980
981 981 @command('bundle',
982 982 [('f', 'force', None, _('run even when the destination is unrelated')),
983 983 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
984 984 _('REV')),
985 985 ('b', 'branch', [], _('a specific branch you would like to bundle'),
986 986 _('BRANCH')),
987 987 ('', 'base', [],
988 988 _('a base changeset assumed to be available at the destination'),
989 989 _('REV')),
990 990 ('a', 'all', None, _('bundle all changesets in the repository')),
991 991 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
992 992 ] + remoteopts,
993 993 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
994 994 def bundle(ui, repo, fname, dest=None, **opts):
995 995 """create a changegroup file
996 996
997 997 Generate a compressed changegroup file collecting changesets not
998 998 known to be in another repository.
999 999
1000 1000 If you omit the destination repository, then hg assumes the
1001 1001 destination will have all the nodes you specify with --base
1002 1002 parameters. To create a bundle containing all changesets, use
1003 1003 -a/--all (or --base null).
1004 1004
1005 1005 You can change compression method with the -t/--type option.
1006 1006 The available compression methods are: none, bzip2, and
1007 1007 gzip (by default, bundles are compressed using bzip2).
1008 1008
1009 1009 The bundle file can then be transferred using conventional means
1010 1010 and applied to another repository with the unbundle or pull
1011 1011 command. This is useful when direct push and pull are not
1012 1012 available or when exporting an entire repository is undesirable.
1013 1013
1014 1014 Applying bundles preserves all changeset contents including
1015 1015 permissions, copy/rename information, and revision history.
1016 1016
1017 1017 Returns 0 on success, 1 if no changes found.
1018 1018 """
1019 1019 revs = None
1020 1020 if 'rev' in opts:
1021 1021 revs = scmutil.revrange(repo, opts['rev'])
1022 1022
1023 1023 bundletype = opts.get('type', 'bzip2').lower()
1024 1024 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1025 1025 bundletype = btypes.get(bundletype)
1026 1026 if bundletype not in changegroup.bundletypes:
1027 1027 raise util.Abort(_('unknown bundle type specified with --type'))
1028 1028
1029 1029 if opts.get('all'):
1030 1030 base = ['null']
1031 1031 else:
1032 1032 base = scmutil.revrange(repo, opts.get('base'))
1033 1033 if base:
1034 1034 if dest:
1035 1035 raise util.Abort(_("--base is incompatible with specifying "
1036 1036 "a destination"))
1037 1037 common = [repo.lookup(rev) for rev in base]
1038 1038 heads = revs and map(repo.lookup, revs) or revs
1039 1039 cg = repo.getbundle('bundle', heads=heads, common=common)
1040 1040 outgoing = None
1041 1041 else:
1042 1042 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1043 1043 dest, branches = hg.parseurl(dest, opts.get('branch'))
1044 1044 other = hg.peer(repo, opts, dest)
1045 1045 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1046 1046 heads = revs and map(repo.lookup, revs) or revs
1047 1047 outgoing = discovery.findcommonoutgoing(repo, other,
1048 1048 onlyheads=heads,
1049 1049 force=opts.get('force'),
1050 1050 portable=True)
1051 1051 cg = repo.getlocalbundle('bundle', outgoing)
1052 1052 if not cg:
1053 1053 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1054 1054 return 1
1055 1055
1056 1056 changegroup.writebundle(cg, fname, bundletype)
1057 1057
1058 1058 @command('cat',
1059 1059 [('o', 'output', '',
1060 1060 _('print output to file with formatted name'), _('FORMAT')),
1061 1061 ('r', 'rev', '', _('print the given revision'), _('REV')),
1062 1062 ('', 'decode', None, _('apply any matching decode filter')),
1063 1063 ] + walkopts,
1064 1064 _('[OPTION]... FILE...'))
1065 1065 def cat(ui, repo, file1, *pats, **opts):
1066 1066 """output the current or given revision of files
1067 1067
1068 1068 Print the specified files as they were at the given revision. If
1069 1069 no revision is given, the parent of the working directory is used,
1070 1070 or tip if no revision is checked out.
1071 1071
1072 1072 Output may be to a file, in which case the name of the file is
1073 1073 given using a format string. The formatting rules are the same as
1074 1074 for the export command, with the following additions:
1075 1075
1076 1076 :``%s``: basename of file being printed
1077 1077 :``%d``: dirname of file being printed, or '.' if in repository root
1078 1078 :``%p``: root-relative path name of file being printed
1079 1079
1080 1080 Returns 0 on success.
1081 1081 """
1082 1082 ctx = scmutil.revsingle(repo, opts.get('rev'))
1083 1083 err = 1
1084 1084 m = scmutil.match(ctx, (file1,) + pats, opts)
1085 1085 for abs in ctx.walk(m):
1086 1086 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1087 1087 pathname=abs)
1088 1088 data = ctx[abs].data()
1089 1089 if opts.get('decode'):
1090 1090 data = repo.wwritedata(abs, data)
1091 1091 fp.write(data)
1092 1092 fp.close()
1093 1093 err = 0
1094 1094 return err
1095 1095
1096 1096 @command('^clone',
1097 1097 [('U', 'noupdate', None,
1098 1098 _('the clone will include an empty working copy (only a repository)')),
1099 1099 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1100 1100 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1101 1101 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1102 1102 ('', 'pull', None, _('use pull protocol to copy metadata')),
1103 1103 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1104 1104 ] + remoteopts,
1105 1105 _('[OPTION]... SOURCE [DEST]'))
1106 1106 def clone(ui, source, dest=None, **opts):
1107 1107 """make a copy of an existing repository
1108 1108
1109 1109 Create a copy of an existing repository in a new directory.
1110 1110
1111 1111 If no destination directory name is specified, it defaults to the
1112 1112 basename of the source.
1113 1113
1114 1114 The location of the source is added to the new repository's
1115 1115 ``.hg/hgrc`` file, as the default to be used for future pulls.
1116 1116
1117 1117 Only local paths and ``ssh://`` URLs are supported as
1118 1118 destinations. For ``ssh://`` destinations, no working directory or
1119 1119 ``.hg/hgrc`` will be created on the remote side.
1120 1120
1121 1121 To pull only a subset of changesets, specify one or more revisions
1122 1122 identifiers with -r/--rev or branches with -b/--branch. The
1123 1123 resulting clone will contain only the specified changesets and
1124 1124 their ancestors. These options (or 'clone src#rev dest') imply
1125 1125 --pull, even for local source repositories. Note that specifying a
1126 1126 tag will include the tagged changeset but not the changeset
1127 1127 containing the tag.
1128 1128
1129 1129 To check out a particular version, use -u/--update, or
1130 1130 -U/--noupdate to create a clone with no working directory.
1131 1131
1132 1132 .. container:: verbose
1133 1133
1134 1134 For efficiency, hardlinks are used for cloning whenever the
1135 1135 source and destination are on the same filesystem (note this
1136 1136 applies only to the repository data, not to the working
1137 1137 directory). Some filesystems, such as AFS, implement hardlinking
1138 1138 incorrectly, but do not report errors. In these cases, use the
1139 1139 --pull option to avoid hardlinking.
1140 1140
1141 1141 In some cases, you can clone repositories and the working
1142 1142 directory using full hardlinks with ::
1143 1143
1144 1144 $ cp -al REPO REPOCLONE
1145 1145
1146 1146 This is the fastest way to clone, but it is not always safe. The
1147 1147 operation is not atomic (making sure REPO is not modified during
1148 1148 the operation is up to you) and you have to make sure your
1149 1149 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1150 1150 so). Also, this is not compatible with certain extensions that
1151 1151 place their metadata under the .hg directory, such as mq.
1152 1152
1153 1153 Mercurial will update the working directory to the first applicable
1154 1154 revision from this list:
1155 1155
1156 1156 a) null if -U or the source repository has no changesets
1157 1157 b) if -u . and the source repository is local, the first parent of
1158 1158 the source repository's working directory
1159 1159 c) the changeset specified with -u (if a branch name, this means the
1160 1160 latest head of that branch)
1161 1161 d) the changeset specified with -r
1162 1162 e) the tipmost head specified with -b
1163 1163 f) the tipmost head specified with the url#branch source syntax
1164 1164 g) the tipmost head of the default branch
1165 1165 h) tip
1166 1166
1167 1167 Examples:
1168 1168
1169 1169 - clone a remote repository to a new directory named hg/::
1170 1170
1171 1171 hg clone http://selenic.com/hg
1172 1172
1173 1173 - create a lightweight local clone::
1174 1174
1175 1175 hg clone project/ project-feature/
1176 1176
1177 1177 - clone from an absolute path on an ssh server (note double-slash)::
1178 1178
1179 1179 hg clone ssh://user@server//home/projects/alpha/
1180 1180
1181 1181 - do a high-speed clone over a LAN while checking out a
1182 1182 specified version::
1183 1183
1184 1184 hg clone --uncompressed http://server/repo -u 1.5
1185 1185
1186 1186 - create a repository without changesets after a particular revision::
1187 1187
1188 1188 hg clone -r 04e544 experimental/ good/
1189 1189
1190 1190 - clone (and track) a particular named branch::
1191 1191
1192 1192 hg clone http://selenic.com/hg#stable
1193 1193
1194 1194 See :hg:`help urls` for details on specifying URLs.
1195 1195
1196 1196 Returns 0 on success.
1197 1197 """
1198 1198 if opts.get('noupdate') and opts.get('updaterev'):
1199 1199 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1200 1200
1201 1201 r = hg.clone(ui, opts, source, dest,
1202 1202 pull=opts.get('pull'),
1203 1203 stream=opts.get('uncompressed'),
1204 1204 rev=opts.get('rev'),
1205 1205 update=opts.get('updaterev') or not opts.get('noupdate'),
1206 1206 branch=opts.get('branch'))
1207 1207
1208 1208 return r is None
1209 1209
1210 1210 @command('^commit|ci',
1211 1211 [('A', 'addremove', None,
1212 1212 _('mark new/missing files as added/removed before committing')),
1213 1213 ('', 'close-branch', None,
1214 1214 _('mark a branch as closed, hiding it from the branch list')),
1215 1215 ('', 'amend', None, _('amend the parent of the working dir')),
1216 1216 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1217 1217 _('[OPTION]... [FILE]...'))
1218 1218 def commit(ui, repo, *pats, **opts):
1219 1219 """commit the specified files or all outstanding changes
1220 1220
1221 1221 Commit changes to the given files into the repository. Unlike a
1222 1222 centralized SCM, this operation is a local operation. See
1223 1223 :hg:`push` for a way to actively distribute your changes.
1224 1224
1225 1225 If a list of files is omitted, all changes reported by :hg:`status`
1226 1226 will be committed.
1227 1227
1228 1228 If you are committing the result of a merge, do not provide any
1229 1229 filenames or -I/-X filters.
1230 1230
1231 1231 If no commit message is specified, Mercurial starts your
1232 1232 configured editor where you can enter a message. In case your
1233 1233 commit fails, you will find a backup of your message in
1234 1234 ``.hg/last-message.txt``.
1235 1235
1236 1236 The --amend flag can be used to amend the parent of the
1237 1237 working directory with a new commit that contains the changes
1238 1238 in the parent in addition to those currently reported by :hg:`status`,
1239 1239 if there are any. The old commit is stored in a backup bundle in
1240 1240 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1241 1241 on how to restore it).
1242 1242
1243 1243 Message, user and date are taken from the amended commit unless
1244 1244 specified. When a message isn't specified on the command line,
1245 1245 the editor will open with the message of the amended commit.
1246 1246
1247 1247 It is not possible to amend public changesets (see :hg:`help phases`)
1248 1248 or changesets that have children.
1249 1249
1250 1250 See :hg:`help dates` for a list of formats valid for -d/--date.
1251 1251
1252 1252 Returns 0 on success, 1 if nothing changed.
1253 1253 """
1254 1254 if opts.get('subrepos'):
1255 1255 # Let --subrepos on the command line override config setting.
1256 1256 ui.setconfig('ui', 'commitsubrepos', True)
1257 1257
1258 1258 extra = {}
1259 1259 if opts.get('close_branch'):
1260 1260 if repo['.'].node() not in repo.branchheads():
1261 1261 # The topo heads set is included in the branch heads set of the
1262 1262 # current branch, so it's sufficient to test branchheads
1263 1263 raise util.Abort(_('can only close branch heads'))
1264 1264 extra['close'] = 1
1265 1265
1266 1266 branch = repo[None].branch()
1267 1267 bheads = repo.branchheads(branch)
1268 1268
1269 1269 if opts.get('amend'):
1270 1270 if ui.configbool('ui', 'commitsubrepos'):
1271 1271 raise util.Abort(_('cannot amend recursively'))
1272 1272
1273 1273 old = repo['.']
1274 1274 if old.phase() == phases.public:
1275 1275 raise util.Abort(_('cannot amend public changesets'))
1276 1276 if len(old.parents()) > 1:
1277 1277 raise util.Abort(_('cannot amend merge changesets'))
1278 1278 if len(repo[None].parents()) > 1:
1279 1279 raise util.Abort(_('cannot amend while merging'))
1280 1280 if old.children():
1281 1281 raise util.Abort(_('cannot amend changeset with children'))
1282 1282
1283 1283 e = cmdutil.commiteditor
1284 1284 if opts.get('force_editor'):
1285 1285 e = cmdutil.commitforceeditor
1286 1286
1287 1287 def commitfunc(ui, repo, message, match, opts):
1288 1288 editor = e
1289 1289 # message contains text from -m or -l, if it's empty,
1290 1290 # open the editor with the old message
1291 1291 if not message:
1292 1292 message = old.description()
1293 1293 editor = cmdutil.commitforceeditor
1294 1294 return repo.commit(message,
1295 1295 opts.get('user') or old.user(),
1296 1296 opts.get('date') or old.date(),
1297 1297 match,
1298 1298 editor=editor,
1299 1299 extra=extra)
1300 1300
1301 1301 current = repo._bookmarkcurrent
1302 1302 marks = old.bookmarks()
1303 1303 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1304 1304 if node == old.node():
1305 1305 ui.status(_("nothing changed\n"))
1306 1306 return 1
1307 1307 elif marks:
1308 1308 ui.debug('moving bookmarks %r from %s to %s\n' %
1309 1309 (marks, old.hex(), hex(node)))
1310 1310 for bm in marks:
1311 1311 repo._bookmarks[bm] = node
1312 1312 if bm == current:
1313 1313 bookmarks.setcurrent(repo, bm)
1314 1314 bookmarks.write(repo)
1315 1315 else:
1316 1316 e = cmdutil.commiteditor
1317 1317 if opts.get('force_editor'):
1318 1318 e = cmdutil.commitforceeditor
1319 1319
1320 1320 def commitfunc(ui, repo, message, match, opts):
1321 1321 return repo.commit(message, opts.get('user'), opts.get('date'),
1322 1322 match, editor=e, extra=extra)
1323 1323
1324 1324 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1325 1325
1326 1326 if not node:
1327 1327 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1328 1328 if stat[3]:
1329 1329 ui.status(_("nothing changed (%d missing files, see "
1330 1330 "'hg status')\n") % len(stat[3]))
1331 1331 else:
1332 1332 ui.status(_("nothing changed\n"))
1333 1333 return 1
1334 1334
1335 1335 ctx = repo[node]
1336 1336 parents = ctx.parents()
1337 1337
1338 1338 if (not opts.get('amend') and bheads and node not in bheads and not
1339 1339 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1340 1340 ui.status(_('created new head\n'))
1341 1341 # The message is not printed for initial roots. For the other
1342 1342 # changesets, it is printed in the following situations:
1343 1343 #
1344 1344 # Par column: for the 2 parents with ...
1345 1345 # N: null or no parent
1346 1346 # B: parent is on another named branch
1347 1347 # C: parent is a regular non head changeset
1348 1348 # H: parent was a branch head of the current branch
1349 1349 # Msg column: whether we print "created new head" message
1350 1350 # In the following, it is assumed that there already exists some
1351 1351 # initial branch heads of the current branch, otherwise nothing is
1352 1352 # printed anyway.
1353 1353 #
1354 1354 # Par Msg Comment
1355 1355 # N N y additional topo root
1356 1356 #
1357 1357 # B N y additional branch root
1358 1358 # C N y additional topo head
1359 1359 # H N n usual case
1360 1360 #
1361 1361 # B B y weird additional branch root
1362 1362 # C B y branch merge
1363 1363 # H B n merge with named branch
1364 1364 #
1365 1365 # C C y additional head from merge
1366 1366 # C H n merge with a head
1367 1367 #
1368 1368 # H H n head merge: head count decreases
1369 1369
1370 1370 if not opts.get('close_branch'):
1371 1371 for r in parents:
1372 1372 if r.closesbranch() and r.branch() == branch:
1373 1373 ui.status(_('reopening closed branch head %d\n') % r)
1374 1374
1375 1375 if ui.debugflag:
1376 1376 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1377 1377 elif ui.verbose:
1378 1378 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1379 1379
1380 1380 @command('copy|cp',
1381 1381 [('A', 'after', None, _('record a copy that has already occurred')),
1382 1382 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1383 1383 ] + walkopts + dryrunopts,
1384 1384 _('[OPTION]... [SOURCE]... DEST'))
1385 1385 def copy(ui, repo, *pats, **opts):
1386 1386 """mark files as copied for the next commit
1387 1387
1388 1388 Mark dest as having copies of source files. If dest is a
1389 1389 directory, copies are put in that directory. If dest is a file,
1390 1390 the source must be a single file.
1391 1391
1392 1392 By default, this command copies the contents of files as they
1393 1393 exist in the working directory. If invoked with -A/--after, the
1394 1394 operation is recorded, but no copying is performed.
1395 1395
1396 1396 This command takes effect with the next commit. To undo a copy
1397 1397 before that, see :hg:`revert`.
1398 1398
1399 1399 Returns 0 on success, 1 if errors are encountered.
1400 1400 """
1401 1401 wlock = repo.wlock(False)
1402 1402 try:
1403 1403 return cmdutil.copy(ui, repo, pats, opts)
1404 1404 finally:
1405 1405 wlock.release()
1406 1406
1407 1407 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1408 1408 def debugancestor(ui, repo, *args):
1409 1409 """find the ancestor revision of two revisions in a given index"""
1410 1410 if len(args) == 3:
1411 1411 index, rev1, rev2 = args
1412 1412 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1413 1413 lookup = r.lookup
1414 1414 elif len(args) == 2:
1415 1415 if not repo:
1416 1416 raise util.Abort(_("there is no Mercurial repository here "
1417 1417 "(.hg not found)"))
1418 1418 rev1, rev2 = args
1419 1419 r = repo.changelog
1420 1420 lookup = repo.lookup
1421 1421 else:
1422 1422 raise util.Abort(_('either two or three arguments required'))
1423 1423 a = r.ancestor(lookup(rev1), lookup(rev2))
1424 1424 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1425 1425
1426 1426 @command('debugbuilddag',
1427 1427 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1428 1428 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1429 1429 ('n', 'new-file', None, _('add new file at each rev'))],
1430 1430 _('[OPTION]... [TEXT]'))
1431 1431 def debugbuilddag(ui, repo, text=None,
1432 1432 mergeable_file=False,
1433 1433 overwritten_file=False,
1434 1434 new_file=False):
1435 1435 """builds a repo with a given DAG from scratch in the current empty repo
1436 1436
1437 1437 The description of the DAG is read from stdin if not given on the
1438 1438 command line.
1439 1439
1440 1440 Elements:
1441 1441
1442 1442 - "+n" is a linear run of n nodes based on the current default parent
1443 1443 - "." is a single node based on the current default parent
1444 1444 - "$" resets the default parent to null (implied at the start);
1445 1445 otherwise the default parent is always the last node created
1446 1446 - "<p" sets the default parent to the backref p
1447 1447 - "*p" is a fork at parent p, which is a backref
1448 1448 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1449 1449 - "/p2" is a merge of the preceding node and p2
1450 1450 - ":tag" defines a local tag for the preceding node
1451 1451 - "@branch" sets the named branch for subsequent nodes
1452 1452 - "#...\\n" is a comment up to the end of the line
1453 1453
1454 1454 Whitespace between the above elements is ignored.
1455 1455
1456 1456 A backref is either
1457 1457
1458 1458 - a number n, which references the node curr-n, where curr is the current
1459 1459 node, or
1460 1460 - the name of a local tag you placed earlier using ":tag", or
1461 1461 - empty to denote the default parent.
1462 1462
1463 1463 All string valued-elements are either strictly alphanumeric, or must
1464 1464 be enclosed in double quotes ("..."), with "\\" as escape character.
1465 1465 """
1466 1466
1467 1467 if text is None:
1468 1468 ui.status(_("reading DAG from stdin\n"))
1469 1469 text = ui.fin.read()
1470 1470
1471 1471 cl = repo.changelog
1472 1472 if len(cl) > 0:
1473 1473 raise util.Abort(_('repository is not empty'))
1474 1474
1475 1475 # determine number of revs in DAG
1476 1476 total = 0
1477 1477 for type, data in dagparser.parsedag(text):
1478 1478 if type == 'n':
1479 1479 total += 1
1480 1480
1481 1481 if mergeable_file:
1482 1482 linesperrev = 2
1483 1483 # make a file with k lines per rev
1484 1484 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1485 1485 initialmergedlines.append("")
1486 1486
1487 1487 tags = []
1488 1488
1489 1489 lock = tr = None
1490 1490 try:
1491 1491 lock = repo.lock()
1492 1492 tr = repo.transaction("builddag")
1493 1493
1494 1494 at = -1
1495 1495 atbranch = 'default'
1496 1496 nodeids = []
1497 1497 id = 0
1498 1498 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1499 1499 for type, data in dagparser.parsedag(text):
1500 1500 if type == 'n':
1501 1501 ui.note('node %s\n' % str(data))
1502 1502 id, ps = data
1503 1503
1504 1504 files = []
1505 1505 fctxs = {}
1506 1506
1507 1507 p2 = None
1508 1508 if mergeable_file:
1509 1509 fn = "mf"
1510 1510 p1 = repo[ps[0]]
1511 1511 if len(ps) > 1:
1512 1512 p2 = repo[ps[1]]
1513 1513 pa = p1.ancestor(p2)
1514 1514 base, local, other = [x[fn].data() for x in pa, p1, p2]
1515 1515 m3 = simplemerge.Merge3Text(base, local, other)
1516 1516 ml = [l.strip() for l in m3.merge_lines()]
1517 1517 ml.append("")
1518 1518 elif at > 0:
1519 1519 ml = p1[fn].data().split("\n")
1520 1520 else:
1521 1521 ml = initialmergedlines
1522 1522 ml[id * linesperrev] += " r%i" % id
1523 1523 mergedtext = "\n".join(ml)
1524 1524 files.append(fn)
1525 1525 fctxs[fn] = context.memfilectx(fn, mergedtext)
1526 1526
1527 1527 if overwritten_file:
1528 1528 fn = "of"
1529 1529 files.append(fn)
1530 1530 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1531 1531
1532 1532 if new_file:
1533 1533 fn = "nf%i" % id
1534 1534 files.append(fn)
1535 1535 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1536 1536 if len(ps) > 1:
1537 1537 if not p2:
1538 1538 p2 = repo[ps[1]]
1539 1539 for fn in p2:
1540 1540 if fn.startswith("nf"):
1541 1541 files.append(fn)
1542 1542 fctxs[fn] = p2[fn]
1543 1543
1544 1544 def fctxfn(repo, cx, path):
1545 1545 return fctxs.get(path)
1546 1546
1547 1547 if len(ps) == 0 or ps[0] < 0:
1548 1548 pars = [None, None]
1549 1549 elif len(ps) == 1:
1550 1550 pars = [nodeids[ps[0]], None]
1551 1551 else:
1552 1552 pars = [nodeids[p] for p in ps]
1553 1553 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1554 1554 date=(id, 0),
1555 1555 user="debugbuilddag",
1556 1556 extra={'branch': atbranch})
1557 1557 nodeid = repo.commitctx(cx)
1558 1558 nodeids.append(nodeid)
1559 1559 at = id
1560 1560 elif type == 'l':
1561 1561 id, name = data
1562 1562 ui.note('tag %s\n' % name)
1563 1563 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1564 1564 elif type == 'a':
1565 1565 ui.note('branch %s\n' % data)
1566 1566 atbranch = data
1567 1567 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1568 1568 tr.close()
1569 1569
1570 1570 if tags:
1571 1571 repo.opener.write("localtags", "".join(tags))
1572 1572 finally:
1573 1573 ui.progress(_('building'), None)
1574 1574 release(tr, lock)
1575 1575
1576 1576 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1577 1577 def debugbundle(ui, bundlepath, all=None, **opts):
1578 1578 """lists the contents of a bundle"""
1579 1579 f = url.open(ui, bundlepath)
1580 1580 try:
1581 1581 gen = changegroup.readbundle(f, bundlepath)
1582 1582 if all:
1583 1583 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1584 1584
1585 1585 def showchunks(named):
1586 1586 ui.write("\n%s\n" % named)
1587 1587 chain = None
1588 1588 while True:
1589 1589 chunkdata = gen.deltachunk(chain)
1590 1590 if not chunkdata:
1591 1591 break
1592 1592 node = chunkdata['node']
1593 1593 p1 = chunkdata['p1']
1594 1594 p2 = chunkdata['p2']
1595 1595 cs = chunkdata['cs']
1596 1596 deltabase = chunkdata['deltabase']
1597 1597 delta = chunkdata['delta']
1598 1598 ui.write("%s %s %s %s %s %s\n" %
1599 1599 (hex(node), hex(p1), hex(p2),
1600 1600 hex(cs), hex(deltabase), len(delta)))
1601 1601 chain = node
1602 1602
1603 1603 chunkdata = gen.changelogheader()
1604 1604 showchunks("changelog")
1605 1605 chunkdata = gen.manifestheader()
1606 1606 showchunks("manifest")
1607 1607 while True:
1608 1608 chunkdata = gen.filelogheader()
1609 1609 if not chunkdata:
1610 1610 break
1611 1611 fname = chunkdata['filename']
1612 1612 showchunks(fname)
1613 1613 else:
1614 1614 chunkdata = gen.changelogheader()
1615 1615 chain = None
1616 1616 while True:
1617 1617 chunkdata = gen.deltachunk(chain)
1618 1618 if not chunkdata:
1619 1619 break
1620 1620 node = chunkdata['node']
1621 1621 ui.write("%s\n" % hex(node))
1622 1622 chain = node
1623 1623 finally:
1624 1624 f.close()
1625 1625
1626 1626 @command('debugcheckstate', [], '')
1627 1627 def debugcheckstate(ui, repo):
1628 1628 """validate the correctness of the current dirstate"""
1629 1629 parent1, parent2 = repo.dirstate.parents()
1630 1630 m1 = repo[parent1].manifest()
1631 1631 m2 = repo[parent2].manifest()
1632 1632 errors = 0
1633 1633 for f in repo.dirstate:
1634 1634 state = repo.dirstate[f]
1635 1635 if state in "nr" and f not in m1:
1636 1636 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1637 1637 errors += 1
1638 1638 if state in "a" and f in m1:
1639 1639 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1640 1640 errors += 1
1641 1641 if state in "m" and f not in m1 and f not in m2:
1642 1642 ui.warn(_("%s in state %s, but not in either manifest\n") %
1643 1643 (f, state))
1644 1644 errors += 1
1645 1645 for f in m1:
1646 1646 state = repo.dirstate[f]
1647 1647 if state not in "nrm":
1648 1648 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1649 1649 errors += 1
1650 1650 if errors:
1651 1651 error = _(".hg/dirstate inconsistent with current parent's manifest")
1652 1652 raise util.Abort(error)
1653 1653
1654 1654 @command('debugcommands', [], _('[COMMAND]'))
1655 1655 def debugcommands(ui, cmd='', *args):
1656 1656 """list all available commands and options"""
1657 1657 for cmd, vals in sorted(table.iteritems()):
1658 1658 cmd = cmd.split('|')[0].strip('^')
1659 1659 opts = ', '.join([i[1] for i in vals[1]])
1660 1660 ui.write('%s: %s\n' % (cmd, opts))
1661 1661
1662 1662 @command('debugcomplete',
1663 1663 [('o', 'options', None, _('show the command options'))],
1664 1664 _('[-o] CMD'))
1665 1665 def debugcomplete(ui, cmd='', **opts):
1666 1666 """returns the completion list associated with the given command"""
1667 1667
1668 1668 if opts.get('options'):
1669 1669 options = []
1670 1670 otables = [globalopts]
1671 1671 if cmd:
1672 1672 aliases, entry = cmdutil.findcmd(cmd, table, False)
1673 1673 otables.append(entry[1])
1674 1674 for t in otables:
1675 1675 for o in t:
1676 1676 if "(DEPRECATED)" in o[3]:
1677 1677 continue
1678 1678 if o[0]:
1679 1679 options.append('-%s' % o[0])
1680 1680 options.append('--%s' % o[1])
1681 1681 ui.write("%s\n" % "\n".join(options))
1682 1682 return
1683 1683
1684 1684 cmdlist = cmdutil.findpossible(cmd, table)
1685 1685 if ui.verbose:
1686 1686 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1687 1687 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1688 1688
1689 1689 @command('debugdag',
1690 1690 [('t', 'tags', None, _('use tags as labels')),
1691 1691 ('b', 'branches', None, _('annotate with branch names')),
1692 1692 ('', 'dots', None, _('use dots for runs')),
1693 1693 ('s', 'spaces', None, _('separate elements by spaces'))],
1694 1694 _('[OPTION]... [FILE [REV]...]'))
1695 1695 def debugdag(ui, repo, file_=None, *revs, **opts):
1696 1696 """format the changelog or an index DAG as a concise textual description
1697 1697
1698 1698 If you pass a revlog index, the revlog's DAG is emitted. If you list
1699 revision numbers, they get labelled in the output as rN.
1699 revision numbers, they get labeled in the output as rN.
1700 1700
1701 1701 Otherwise, the changelog DAG of the current repo is emitted.
1702 1702 """
1703 1703 spaces = opts.get('spaces')
1704 1704 dots = opts.get('dots')
1705 1705 if file_:
1706 1706 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1707 1707 revs = set((int(r) for r in revs))
1708 1708 def events():
1709 1709 for r in rlog:
1710 1710 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1711 1711 if p != -1)))
1712 1712 if r in revs:
1713 1713 yield 'l', (r, "r%i" % r)
1714 1714 elif repo:
1715 1715 cl = repo.changelog
1716 1716 tags = opts.get('tags')
1717 1717 branches = opts.get('branches')
1718 1718 if tags:
1719 1719 labels = {}
1720 1720 for l, n in repo.tags().items():
1721 1721 labels.setdefault(cl.rev(n), []).append(l)
1722 1722 def events():
1723 1723 b = "default"
1724 1724 for r in cl:
1725 1725 if branches:
1726 1726 newb = cl.read(cl.node(r))[5]['branch']
1727 1727 if newb != b:
1728 1728 yield 'a', newb
1729 1729 b = newb
1730 1730 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1731 1731 if p != -1)))
1732 1732 if tags:
1733 1733 ls = labels.get(r)
1734 1734 if ls:
1735 1735 for l in ls:
1736 1736 yield 'l', (r, l)
1737 1737 else:
1738 1738 raise util.Abort(_('need repo for changelog dag'))
1739 1739
1740 1740 for line in dagparser.dagtextlines(events(),
1741 1741 addspaces=spaces,
1742 1742 wraplabels=True,
1743 1743 wrapannotations=True,
1744 1744 wrapnonlinear=dots,
1745 1745 usedots=dots,
1746 1746 maxlinewidth=70):
1747 1747 ui.write(line)
1748 1748 ui.write("\n")
1749 1749
1750 1750 @command('debugdata',
1751 1751 [('c', 'changelog', False, _('open changelog')),
1752 1752 ('m', 'manifest', False, _('open manifest'))],
1753 1753 _('-c|-m|FILE REV'))
1754 1754 def debugdata(ui, repo, file_, rev = None, **opts):
1755 1755 """dump the contents of a data file revision"""
1756 1756 if opts.get('changelog') or opts.get('manifest'):
1757 1757 file_, rev = None, file_
1758 1758 elif rev is None:
1759 1759 raise error.CommandError('debugdata', _('invalid arguments'))
1760 1760 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1761 1761 try:
1762 1762 ui.write(r.revision(r.lookup(rev)))
1763 1763 except KeyError:
1764 1764 raise util.Abort(_('invalid revision identifier %s') % rev)
1765 1765
1766 1766 @command('debugdate',
1767 1767 [('e', 'extended', None, _('try extended date formats'))],
1768 1768 _('[-e] DATE [RANGE]'))
1769 1769 def debugdate(ui, date, range=None, **opts):
1770 1770 """parse and display a date"""
1771 1771 if opts["extended"]:
1772 1772 d = util.parsedate(date, util.extendeddateformats)
1773 1773 else:
1774 1774 d = util.parsedate(date)
1775 1775 ui.write("internal: %s %s\n" % d)
1776 1776 ui.write("standard: %s\n" % util.datestr(d))
1777 1777 if range:
1778 1778 m = util.matchdate(range)
1779 1779 ui.write("match: %s\n" % m(d[0]))
1780 1780
1781 1781 @command('debugdiscovery',
1782 1782 [('', 'old', None, _('use old-style discovery')),
1783 1783 ('', 'nonheads', None,
1784 1784 _('use old-style discovery with non-heads included')),
1785 1785 ] + remoteopts,
1786 1786 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1787 1787 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1788 1788 """runs the changeset discovery protocol in isolation"""
1789 1789 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1790 1790 opts.get('branch'))
1791 1791 remote = hg.peer(repo, opts, remoteurl)
1792 1792 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1793 1793
1794 1794 # make sure tests are repeatable
1795 1795 random.seed(12323)
1796 1796
1797 1797 def doit(localheads, remoteheads, remote=remote):
1798 1798 if opts.get('old'):
1799 1799 if localheads:
1800 1800 raise util.Abort('cannot use localheads with old style '
1801 1801 'discovery')
1802 1802 if not util.safehasattr(remote, 'branches'):
1803 1803 # enable in-client legacy support
1804 1804 remote = localrepo.locallegacypeer(remote.local())
1805 1805 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1806 1806 force=True)
1807 1807 common = set(common)
1808 1808 if not opts.get('nonheads'):
1809 1809 ui.write("unpruned common: %s\n" % " ".join([short(n)
1810 1810 for n in common]))
1811 1811 dag = dagutil.revlogdag(repo.changelog)
1812 1812 all = dag.ancestorset(dag.internalizeall(common))
1813 1813 common = dag.externalizeall(dag.headsetofconnecteds(all))
1814 1814 else:
1815 1815 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1816 1816 common = set(common)
1817 1817 rheads = set(hds)
1818 1818 lheads = set(repo.heads())
1819 1819 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1820 1820 if lheads <= common:
1821 1821 ui.write("local is subset\n")
1822 1822 elif rheads <= common:
1823 1823 ui.write("remote is subset\n")
1824 1824
1825 1825 serverlogs = opts.get('serverlog')
1826 1826 if serverlogs:
1827 1827 for filename in serverlogs:
1828 1828 logfile = open(filename, 'r')
1829 1829 try:
1830 1830 line = logfile.readline()
1831 1831 while line:
1832 1832 parts = line.strip().split(';')
1833 1833 op = parts[1]
1834 1834 if op == 'cg':
1835 1835 pass
1836 1836 elif op == 'cgss':
1837 1837 doit(parts[2].split(' '), parts[3].split(' '))
1838 1838 elif op == 'unb':
1839 1839 doit(parts[3].split(' '), parts[2].split(' '))
1840 1840 line = logfile.readline()
1841 1841 finally:
1842 1842 logfile.close()
1843 1843
1844 1844 else:
1845 1845 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1846 1846 opts.get('remote_head'))
1847 1847 localrevs = opts.get('local_head')
1848 1848 doit(localrevs, remoterevs)
1849 1849
1850 1850 @command('debugfileset',
1851 1851 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1852 1852 _('[-r REV] FILESPEC'))
1853 1853 def debugfileset(ui, repo, expr, **opts):
1854 1854 '''parse and apply a fileset specification'''
1855 1855 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1856 1856 if ui.verbose:
1857 1857 tree = fileset.parse(expr)[0]
1858 1858 ui.note(tree, "\n")
1859 1859
1860 1860 for f in fileset.getfileset(ctx, expr):
1861 1861 ui.write("%s\n" % f)
1862 1862
1863 1863 @command('debugfsinfo', [], _('[PATH]'))
1864 1864 def debugfsinfo(ui, path = "."):
1865 1865 """show information detected about current filesystem"""
1866 1866 util.writefile('.debugfsinfo', '')
1867 1867 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1868 1868 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1869 1869 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1870 1870 and 'yes' or 'no'))
1871 1871 os.unlink('.debugfsinfo')
1872 1872
1873 1873 @command('debuggetbundle',
1874 1874 [('H', 'head', [], _('id of head node'), _('ID')),
1875 1875 ('C', 'common', [], _('id of common node'), _('ID')),
1876 1876 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1877 1877 _('REPO FILE [-H|-C ID]...'))
1878 1878 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1879 1879 """retrieves a bundle from a repo
1880 1880
1881 1881 Every ID must be a full-length hex node id string. Saves the bundle to the
1882 1882 given file.
1883 1883 """
1884 1884 repo = hg.peer(ui, opts, repopath)
1885 1885 if not repo.capable('getbundle'):
1886 1886 raise util.Abort("getbundle() not supported by target repository")
1887 1887 args = {}
1888 1888 if common:
1889 1889 args['common'] = [bin(s) for s in common]
1890 1890 if head:
1891 1891 args['heads'] = [bin(s) for s in head]
1892 1892 bundle = repo.getbundle('debug', **args)
1893 1893
1894 1894 bundletype = opts.get('type', 'bzip2').lower()
1895 1895 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1896 1896 bundletype = btypes.get(bundletype)
1897 1897 if bundletype not in changegroup.bundletypes:
1898 1898 raise util.Abort(_('unknown bundle type specified with --type'))
1899 1899 changegroup.writebundle(bundle, bundlepath, bundletype)
1900 1900
1901 1901 @command('debugignore', [], '')
1902 1902 def debugignore(ui, repo, *values, **opts):
1903 1903 """display the combined ignore pattern"""
1904 1904 ignore = repo.dirstate._ignore
1905 1905 includepat = getattr(ignore, 'includepat', None)
1906 1906 if includepat is not None:
1907 1907 ui.write("%s\n" % includepat)
1908 1908 else:
1909 1909 raise util.Abort(_("no ignore patterns found"))
1910 1910
1911 1911 @command('debugindex',
1912 1912 [('c', 'changelog', False, _('open changelog')),
1913 1913 ('m', 'manifest', False, _('open manifest')),
1914 1914 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1915 1915 _('[-f FORMAT] -c|-m|FILE'))
1916 1916 def debugindex(ui, repo, file_ = None, **opts):
1917 1917 """dump the contents of an index file"""
1918 1918 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1919 1919 format = opts.get('format', 0)
1920 1920 if format not in (0, 1):
1921 1921 raise util.Abort(_("unknown format %d") % format)
1922 1922
1923 1923 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1924 1924 if generaldelta:
1925 1925 basehdr = ' delta'
1926 1926 else:
1927 1927 basehdr = ' base'
1928 1928
1929 1929 if format == 0:
1930 1930 ui.write(" rev offset length " + basehdr + " linkrev"
1931 1931 " nodeid p1 p2\n")
1932 1932 elif format == 1:
1933 1933 ui.write(" rev flag offset length"
1934 1934 " size " + basehdr + " link p1 p2"
1935 1935 " nodeid\n")
1936 1936
1937 1937 for i in r:
1938 1938 node = r.node(i)
1939 1939 if generaldelta:
1940 1940 base = r.deltaparent(i)
1941 1941 else:
1942 1942 base = r.chainbase(i)
1943 1943 if format == 0:
1944 1944 try:
1945 1945 pp = r.parents(node)
1946 1946 except Exception:
1947 1947 pp = [nullid, nullid]
1948 1948 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1949 1949 i, r.start(i), r.length(i), base, r.linkrev(i),
1950 1950 short(node), short(pp[0]), short(pp[1])))
1951 1951 elif format == 1:
1952 1952 pr = r.parentrevs(i)
1953 1953 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1954 1954 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1955 1955 base, r.linkrev(i), pr[0], pr[1], short(node)))
1956 1956
1957 1957 @command('debugindexdot', [], _('FILE'))
1958 1958 def debugindexdot(ui, repo, file_):
1959 1959 """dump an index DAG as a graphviz dot file"""
1960 1960 r = None
1961 1961 if repo:
1962 1962 filelog = repo.file(file_)
1963 1963 if len(filelog):
1964 1964 r = filelog
1965 1965 if not r:
1966 1966 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1967 1967 ui.write("digraph G {\n")
1968 1968 for i in r:
1969 1969 node = r.node(i)
1970 1970 pp = r.parents(node)
1971 1971 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1972 1972 if pp[1] != nullid:
1973 1973 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1974 1974 ui.write("}\n")
1975 1975
1976 1976 @command('debuginstall', [], '')
1977 1977 def debuginstall(ui):
1978 1978 '''test Mercurial installation
1979 1979
1980 1980 Returns 0 on success.
1981 1981 '''
1982 1982
1983 1983 def writetemp(contents):
1984 1984 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1985 1985 f = os.fdopen(fd, "wb")
1986 1986 f.write(contents)
1987 1987 f.close()
1988 1988 return name
1989 1989
1990 1990 problems = 0
1991 1991
1992 1992 # encoding
1993 1993 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1994 1994 try:
1995 1995 encoding.fromlocal("test")
1996 1996 except util.Abort, inst:
1997 1997 ui.write(" %s\n" % inst)
1998 1998 ui.write(_(" (check that your locale is properly set)\n"))
1999 1999 problems += 1
2000 2000
2001 2001 # Python lib
2002 2002 ui.status(_("checking Python lib (%s)...\n")
2003 2003 % os.path.dirname(os.__file__))
2004 2004
2005 2005 # compiled modules
2006 2006 ui.status(_("checking installed modules (%s)...\n")
2007 2007 % os.path.dirname(__file__))
2008 2008 try:
2009 2009 import bdiff, mpatch, base85, osutil
2010 2010 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2011 2011 except Exception, inst:
2012 2012 ui.write(" %s\n" % inst)
2013 2013 ui.write(_(" One or more extensions could not be found"))
2014 2014 ui.write(_(" (check that you compiled the extensions)\n"))
2015 2015 problems += 1
2016 2016
2017 2017 # templates
2018 2018 import templater
2019 2019 p = templater.templatepath()
2020 2020 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2021 2021 try:
2022 2022 templater.templater(templater.templatepath("map-cmdline.default"))
2023 2023 except Exception, inst:
2024 2024 ui.write(" %s\n" % inst)
2025 2025 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2026 2026 problems += 1
2027 2027
2028 2028 # editor
2029 2029 ui.status(_("checking commit editor...\n"))
2030 2030 editor = ui.geteditor()
2031 2031 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2032 2032 if not cmdpath:
2033 2033 if editor == 'vi':
2034 2034 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2035 2035 ui.write(_(" (specify a commit editor in your configuration"
2036 2036 " file)\n"))
2037 2037 else:
2038 2038 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2039 2039 ui.write(_(" (specify a commit editor in your configuration"
2040 2040 " file)\n"))
2041 2041 problems += 1
2042 2042
2043 2043 # check username
2044 2044 ui.status(_("checking username...\n"))
2045 2045 try:
2046 2046 ui.username()
2047 2047 except util.Abort, e:
2048 2048 ui.write(" %s\n" % e)
2049 2049 ui.write(_(" (specify a username in your configuration file)\n"))
2050 2050 problems += 1
2051 2051
2052 2052 if not problems:
2053 2053 ui.status(_("no problems detected\n"))
2054 2054 else:
2055 2055 ui.write(_("%s problems detected,"
2056 2056 " please check your install!\n") % problems)
2057 2057
2058 2058 return problems
2059 2059
2060 2060 @command('debugknown', [], _('REPO ID...'))
2061 2061 def debugknown(ui, repopath, *ids, **opts):
2062 2062 """test whether node ids are known to a repo
2063 2063
2064 2064 Every ID must be a full-length hex node id string. Returns a list of 0s
2065 2065 and 1s indicating unknown/known.
2066 2066 """
2067 2067 repo = hg.peer(ui, opts, repopath)
2068 2068 if not repo.capable('known'):
2069 2069 raise util.Abort("known() not supported by target repository")
2070 2070 flags = repo.known([bin(s) for s in ids])
2071 2071 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2072 2072
2073 2073 @command('debugobsolete', [] + commitopts2,
2074 2074 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2075 2075 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2076 2076 """create arbitrary obsolete marker"""
2077 2077 def parsenodeid(s):
2078 2078 try:
2079 2079 # We do not use revsingle/revrange functions here to accept
2080 2080 # arbitrary node identifiers, possibly not present in the
2081 2081 # local repository.
2082 2082 n = bin(s)
2083 2083 if len(n) != len(nullid):
2084 2084 raise TypeError()
2085 2085 return n
2086 2086 except TypeError:
2087 2087 raise util.Abort('changeset references must be full hexadecimal '
2088 2088 'node identifiers')
2089 2089
2090 2090 if precursor is not None:
2091 2091 metadata = {}
2092 2092 if 'date' in opts:
2093 2093 metadata['date'] = opts['date']
2094 2094 metadata['user'] = opts['user'] or ui.username()
2095 2095 succs = tuple(parsenodeid(succ) for succ in successors)
2096 2096 l = repo.lock()
2097 2097 try:
2098 2098 tr = repo.transaction('debugobsolete')
2099 2099 try:
2100 2100 repo.obsstore.create(tr, parsenodeid(precursor), succs, 0,
2101 2101 metadata)
2102 2102 tr.close()
2103 2103 finally:
2104 2104 tr.release()
2105 2105 finally:
2106 2106 l.release()
2107 2107 else:
2108 2108 for m in obsolete.allmarkers(repo):
2109 2109 ui.write(hex(m.precnode()))
2110 2110 for repl in m.succnodes():
2111 2111 ui.write(' ')
2112 2112 ui.write(hex(repl))
2113 2113 ui.write(' %X ' % m._data[2])
2114 2114 ui.write(m.metadata())
2115 2115 ui.write('\n')
2116 2116
2117 2117 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2118 2118 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2119 2119 '''access the pushkey key/value protocol
2120 2120
2121 2121 With two args, list the keys in the given namespace.
2122 2122
2123 2123 With five args, set a key to new if it currently is set to old.
2124 2124 Reports success or failure.
2125 2125 '''
2126 2126
2127 2127 target = hg.peer(ui, {}, repopath)
2128 2128 if keyinfo:
2129 2129 key, old, new = keyinfo
2130 2130 r = target.pushkey(namespace, key, old, new)
2131 2131 ui.status(str(r) + '\n')
2132 2132 return not r
2133 2133 else:
2134 2134 for k, v in target.listkeys(namespace).iteritems():
2135 2135 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2136 2136 v.encode('string-escape')))
2137 2137
2138 2138 @command('debugpvec', [], _('A B'))
2139 2139 def debugpvec(ui, repo, a, b=None):
2140 2140 ca = scmutil.revsingle(repo, a)
2141 2141 cb = scmutil.revsingle(repo, b)
2142 2142 pa = pvec.ctxpvec(ca)
2143 2143 pb = pvec.ctxpvec(cb)
2144 2144 if pa == pb:
2145 2145 rel = "="
2146 2146 elif pa > pb:
2147 2147 rel = ">"
2148 2148 elif pa < pb:
2149 2149 rel = "<"
2150 2150 elif pa | pb:
2151 2151 rel = "|"
2152 2152 ui.write(_("a: %s\n") % pa)
2153 2153 ui.write(_("b: %s\n") % pb)
2154 2154 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2155 2155 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2156 2156 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2157 2157 pa.distance(pb), rel))
2158 2158
2159 2159 @command('debugrebuildstate',
2160 2160 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2161 2161 _('[-r REV] [REV]'))
2162 2162 def debugrebuildstate(ui, repo, rev="tip"):
2163 2163 """rebuild the dirstate as it would look like for the given revision"""
2164 2164 ctx = scmutil.revsingle(repo, rev)
2165 2165 wlock = repo.wlock()
2166 2166 try:
2167 2167 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2168 2168 finally:
2169 2169 wlock.release()
2170 2170
2171 2171 @command('debugrename',
2172 2172 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2173 2173 _('[-r REV] FILE'))
2174 2174 def debugrename(ui, repo, file1, *pats, **opts):
2175 2175 """dump rename information"""
2176 2176
2177 2177 ctx = scmutil.revsingle(repo, opts.get('rev'))
2178 2178 m = scmutil.match(ctx, (file1,) + pats, opts)
2179 2179 for abs in ctx.walk(m):
2180 2180 fctx = ctx[abs]
2181 2181 o = fctx.filelog().renamed(fctx.filenode())
2182 2182 rel = m.rel(abs)
2183 2183 if o:
2184 2184 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2185 2185 else:
2186 2186 ui.write(_("%s not renamed\n") % rel)
2187 2187
2188 2188 @command('debugrevlog',
2189 2189 [('c', 'changelog', False, _('open changelog')),
2190 2190 ('m', 'manifest', False, _('open manifest')),
2191 2191 ('d', 'dump', False, _('dump index data'))],
2192 2192 _('-c|-m|FILE'))
2193 2193 def debugrevlog(ui, repo, file_ = None, **opts):
2194 2194 """show data and statistics about a revlog"""
2195 2195 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2196 2196
2197 2197 if opts.get("dump"):
2198 2198 numrevs = len(r)
2199 2199 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2200 2200 " rawsize totalsize compression heads\n")
2201 2201 ts = 0
2202 2202 heads = set()
2203 2203 for rev in xrange(numrevs):
2204 2204 dbase = r.deltaparent(rev)
2205 2205 if dbase == -1:
2206 2206 dbase = rev
2207 2207 cbase = r.chainbase(rev)
2208 2208 p1, p2 = r.parentrevs(rev)
2209 2209 rs = r.rawsize(rev)
2210 2210 ts = ts + rs
2211 2211 heads -= set(r.parentrevs(rev))
2212 2212 heads.add(rev)
2213 2213 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2214 2214 (rev, p1, p2, r.start(rev), r.end(rev),
2215 2215 r.start(dbase), r.start(cbase),
2216 2216 r.start(p1), r.start(p2),
2217 2217 rs, ts, ts / r.end(rev), len(heads)))
2218 2218 return 0
2219 2219
2220 2220 v = r.version
2221 2221 format = v & 0xFFFF
2222 2222 flags = []
2223 2223 gdelta = False
2224 2224 if v & revlog.REVLOGNGINLINEDATA:
2225 2225 flags.append('inline')
2226 2226 if v & revlog.REVLOGGENERALDELTA:
2227 2227 gdelta = True
2228 2228 flags.append('generaldelta')
2229 2229 if not flags:
2230 2230 flags = ['(none)']
2231 2231
2232 2232 nummerges = 0
2233 2233 numfull = 0
2234 2234 numprev = 0
2235 2235 nump1 = 0
2236 2236 nump2 = 0
2237 2237 numother = 0
2238 2238 nump1prev = 0
2239 2239 nump2prev = 0
2240 2240 chainlengths = []
2241 2241
2242 2242 datasize = [None, 0, 0L]
2243 2243 fullsize = [None, 0, 0L]
2244 2244 deltasize = [None, 0, 0L]
2245 2245
2246 2246 def addsize(size, l):
2247 2247 if l[0] is None or size < l[0]:
2248 2248 l[0] = size
2249 2249 if size > l[1]:
2250 2250 l[1] = size
2251 2251 l[2] += size
2252 2252
2253 2253 numrevs = len(r)
2254 2254 for rev in xrange(numrevs):
2255 2255 p1, p2 = r.parentrevs(rev)
2256 2256 delta = r.deltaparent(rev)
2257 2257 if format > 0:
2258 2258 addsize(r.rawsize(rev), datasize)
2259 2259 if p2 != nullrev:
2260 2260 nummerges += 1
2261 2261 size = r.length(rev)
2262 2262 if delta == nullrev:
2263 2263 chainlengths.append(0)
2264 2264 numfull += 1
2265 2265 addsize(size, fullsize)
2266 2266 else:
2267 2267 chainlengths.append(chainlengths[delta] + 1)
2268 2268 addsize(size, deltasize)
2269 2269 if delta == rev - 1:
2270 2270 numprev += 1
2271 2271 if delta == p1:
2272 2272 nump1prev += 1
2273 2273 elif delta == p2:
2274 2274 nump2prev += 1
2275 2275 elif delta == p1:
2276 2276 nump1 += 1
2277 2277 elif delta == p2:
2278 2278 nump2 += 1
2279 2279 elif delta != nullrev:
2280 2280 numother += 1
2281 2281
2282 2282 # Adjust size min value for empty cases
2283 2283 for size in (datasize, fullsize, deltasize):
2284 2284 if size[0] is None:
2285 2285 size[0] = 0
2286 2286
2287 2287 numdeltas = numrevs - numfull
2288 2288 numoprev = numprev - nump1prev - nump2prev
2289 2289 totalrawsize = datasize[2]
2290 2290 datasize[2] /= numrevs
2291 2291 fulltotal = fullsize[2]
2292 2292 fullsize[2] /= numfull
2293 2293 deltatotal = deltasize[2]
2294 2294 if numrevs - numfull > 0:
2295 2295 deltasize[2] /= numrevs - numfull
2296 2296 totalsize = fulltotal + deltatotal
2297 2297 avgchainlen = sum(chainlengths) / numrevs
2298 2298 compratio = totalrawsize / totalsize
2299 2299
2300 2300 basedfmtstr = '%%%dd\n'
2301 2301 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2302 2302
2303 2303 def dfmtstr(max):
2304 2304 return basedfmtstr % len(str(max))
2305 2305 def pcfmtstr(max, padding=0):
2306 2306 return basepcfmtstr % (len(str(max)), ' ' * padding)
2307 2307
2308 2308 def pcfmt(value, total):
2309 2309 return (value, 100 * float(value) / total)
2310 2310
2311 2311 ui.write('format : %d\n' % format)
2312 2312 ui.write('flags : %s\n' % ', '.join(flags))
2313 2313
2314 2314 ui.write('\n')
2315 2315 fmt = pcfmtstr(totalsize)
2316 2316 fmt2 = dfmtstr(totalsize)
2317 2317 ui.write('revisions : ' + fmt2 % numrevs)
2318 2318 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2319 2319 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2320 2320 ui.write('revisions : ' + fmt2 % numrevs)
2321 2321 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2322 2322 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2323 2323 ui.write('revision size : ' + fmt2 % totalsize)
2324 2324 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2325 2325 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2326 2326
2327 2327 ui.write('\n')
2328 2328 fmt = dfmtstr(max(avgchainlen, compratio))
2329 2329 ui.write('avg chain length : ' + fmt % avgchainlen)
2330 2330 ui.write('compression ratio : ' + fmt % compratio)
2331 2331
2332 2332 if format > 0:
2333 2333 ui.write('\n')
2334 2334 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2335 2335 % tuple(datasize))
2336 2336 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2337 2337 % tuple(fullsize))
2338 2338 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2339 2339 % tuple(deltasize))
2340 2340
2341 2341 if numdeltas > 0:
2342 2342 ui.write('\n')
2343 2343 fmt = pcfmtstr(numdeltas)
2344 2344 fmt2 = pcfmtstr(numdeltas, 4)
2345 2345 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2346 2346 if numprev > 0:
2347 2347 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2348 2348 numprev))
2349 2349 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2350 2350 numprev))
2351 2351 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2352 2352 numprev))
2353 2353 if gdelta:
2354 2354 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2355 2355 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2356 2356 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2357 2357 numdeltas))
2358 2358
2359 2359 @command('debugrevspec', [], ('REVSPEC'))
2360 2360 def debugrevspec(ui, repo, expr):
2361 2361 """parse and apply a revision specification
2362 2362
2363 2363 Use --verbose to print the parsed tree before and after aliases
2364 2364 expansion.
2365 2365 """
2366 2366 if ui.verbose:
2367 2367 tree = revset.parse(expr)[0]
2368 2368 ui.note(revset.prettyformat(tree), "\n")
2369 2369 newtree = revset.findaliases(ui, tree)
2370 2370 if newtree != tree:
2371 2371 ui.note(revset.prettyformat(newtree), "\n")
2372 2372 func = revset.match(ui, expr)
2373 2373 for c in func(repo, range(len(repo))):
2374 2374 ui.write("%s\n" % c)
2375 2375
2376 2376 @command('debugsetparents', [], _('REV1 [REV2]'))
2377 2377 def debugsetparents(ui, repo, rev1, rev2=None):
2378 2378 """manually set the parents of the current working directory
2379 2379
2380 2380 This is useful for writing repository conversion tools, but should
2381 2381 be used with care.
2382 2382
2383 2383 Returns 0 on success.
2384 2384 """
2385 2385
2386 2386 r1 = scmutil.revsingle(repo, rev1).node()
2387 2387 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2388 2388
2389 2389 wlock = repo.wlock()
2390 2390 try:
2391 2391 repo.setparents(r1, r2)
2392 2392 finally:
2393 2393 wlock.release()
2394 2394
2395 2395 @command('debugstate',
2396 2396 [('', 'nodates', None, _('do not display the saved mtime')),
2397 2397 ('', 'datesort', None, _('sort by saved mtime'))],
2398 2398 _('[OPTION]...'))
2399 2399 def debugstate(ui, repo, nodates=None, datesort=None):
2400 2400 """show the contents of the current dirstate"""
2401 2401 timestr = ""
2402 2402 showdate = not nodates
2403 2403 if datesort:
2404 2404 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2405 2405 else:
2406 2406 keyfunc = None # sort by filename
2407 2407 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2408 2408 if showdate:
2409 2409 if ent[3] == -1:
2410 2410 # Pad or slice to locale representation
2411 2411 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2412 2412 time.localtime(0)))
2413 2413 timestr = 'unset'
2414 2414 timestr = (timestr[:locale_len] +
2415 2415 ' ' * (locale_len - len(timestr)))
2416 2416 else:
2417 2417 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2418 2418 time.localtime(ent[3]))
2419 2419 if ent[1] & 020000:
2420 2420 mode = 'lnk'
2421 2421 else:
2422 2422 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2423 2423 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2424 2424 for f in repo.dirstate.copies():
2425 2425 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2426 2426
2427 2427 @command('debugsub',
2428 2428 [('r', 'rev', '',
2429 2429 _('revision to check'), _('REV'))],
2430 2430 _('[-r REV] [REV]'))
2431 2431 def debugsub(ui, repo, rev=None):
2432 2432 ctx = scmutil.revsingle(repo, rev, None)
2433 2433 for k, v in sorted(ctx.substate.items()):
2434 2434 ui.write('path %s\n' % k)
2435 2435 ui.write(' source %s\n' % v[0])
2436 2436 ui.write(' revision %s\n' % v[1])
2437 2437
2438 2438 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2439 2439 def debugwalk(ui, repo, *pats, **opts):
2440 2440 """show how files match on given patterns"""
2441 2441 m = scmutil.match(repo[None], pats, opts)
2442 2442 items = list(repo.walk(m))
2443 2443 if not items:
2444 2444 return
2445 2445 f = lambda fn: fn
2446 2446 if ui.configbool('ui', 'slash') and os.sep != '/':
2447 2447 f = lambda fn: util.normpath(fn)
2448 2448 fmt = 'f %%-%ds %%-%ds %%s' % (
2449 2449 max([len(abs) for abs in items]),
2450 2450 max([len(m.rel(abs)) for abs in items]))
2451 2451 for abs in items:
2452 2452 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2453 2453 ui.write("%s\n" % line.rstrip())
2454 2454
2455 2455 @command('debugwireargs',
2456 2456 [('', 'three', '', 'three'),
2457 2457 ('', 'four', '', 'four'),
2458 2458 ('', 'five', '', 'five'),
2459 2459 ] + remoteopts,
2460 2460 _('REPO [OPTIONS]... [ONE [TWO]]'))
2461 2461 def debugwireargs(ui, repopath, *vals, **opts):
2462 2462 repo = hg.peer(ui, opts, repopath)
2463 2463 for opt in remoteopts:
2464 2464 del opts[opt[1]]
2465 2465 args = {}
2466 2466 for k, v in opts.iteritems():
2467 2467 if v:
2468 2468 args[k] = v
2469 2469 # run twice to check that we don't mess up the stream for the next command
2470 2470 res1 = repo.debugwireargs(*vals, **args)
2471 2471 res2 = repo.debugwireargs(*vals, **args)
2472 2472 ui.write("%s\n" % res1)
2473 2473 if res1 != res2:
2474 2474 ui.warn("%s\n" % res2)
2475 2475
2476 2476 @command('^diff',
2477 2477 [('r', 'rev', [], _('revision'), _('REV')),
2478 2478 ('c', 'change', '', _('change made by revision'), _('REV'))
2479 2479 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2480 2480 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2481 2481 def diff(ui, repo, *pats, **opts):
2482 2482 """diff repository (or selected files)
2483 2483
2484 2484 Show differences between revisions for the specified files.
2485 2485
2486 2486 Differences between files are shown using the unified diff format.
2487 2487
2488 2488 .. note::
2489 2489 diff may generate unexpected results for merges, as it will
2490 2490 default to comparing against the working directory's first
2491 2491 parent changeset if no revisions are specified.
2492 2492
2493 2493 When two revision arguments are given, then changes are shown
2494 2494 between those revisions. If only one revision is specified then
2495 2495 that revision is compared to the working directory, and, when no
2496 2496 revisions are specified, the working directory files are compared
2497 2497 to its parent.
2498 2498
2499 2499 Alternatively you can specify -c/--change with a revision to see
2500 2500 the changes in that changeset relative to its first parent.
2501 2501
2502 2502 Without the -a/--text option, diff will avoid generating diffs of
2503 2503 files it detects as binary. With -a, diff will generate a diff
2504 2504 anyway, probably with undesirable results.
2505 2505
2506 2506 Use the -g/--git option to generate diffs in the git extended diff
2507 2507 format. For more information, read :hg:`help diffs`.
2508 2508
2509 2509 .. container:: verbose
2510 2510
2511 2511 Examples:
2512 2512
2513 2513 - compare a file in the current working directory to its parent::
2514 2514
2515 2515 hg diff foo.c
2516 2516
2517 2517 - compare two historical versions of a directory, with rename info::
2518 2518
2519 2519 hg diff --git -r 1.0:1.2 lib/
2520 2520
2521 2521 - get change stats relative to the last change on some date::
2522 2522
2523 2523 hg diff --stat -r "date('may 2')"
2524 2524
2525 2525 - diff all newly-added files that contain a keyword::
2526 2526
2527 2527 hg diff "set:added() and grep(GNU)"
2528 2528
2529 2529 - compare a revision and its parents::
2530 2530
2531 2531 hg diff -c 9353 # compare against first parent
2532 2532 hg diff -r 9353^:9353 # same using revset syntax
2533 2533 hg diff -r 9353^2:9353 # compare against the second parent
2534 2534
2535 2535 Returns 0 on success.
2536 2536 """
2537 2537
2538 2538 revs = opts.get('rev')
2539 2539 change = opts.get('change')
2540 2540 stat = opts.get('stat')
2541 2541 reverse = opts.get('reverse')
2542 2542
2543 2543 if revs and change:
2544 2544 msg = _('cannot specify --rev and --change at the same time')
2545 2545 raise util.Abort(msg)
2546 2546 elif change:
2547 2547 node2 = scmutil.revsingle(repo, change, None).node()
2548 2548 node1 = repo[node2].p1().node()
2549 2549 else:
2550 2550 node1, node2 = scmutil.revpair(repo, revs)
2551 2551
2552 2552 if reverse:
2553 2553 node1, node2 = node2, node1
2554 2554
2555 2555 diffopts = patch.diffopts(ui, opts)
2556 2556 m = scmutil.match(repo[node2], pats, opts)
2557 2557 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2558 2558 listsubrepos=opts.get('subrepos'))
2559 2559
2560 2560 @command('^export',
2561 2561 [('o', 'output', '',
2562 2562 _('print output to file with formatted name'), _('FORMAT')),
2563 2563 ('', 'switch-parent', None, _('diff against the second parent')),
2564 2564 ('r', 'rev', [], _('revisions to export'), _('REV')),
2565 2565 ] + diffopts,
2566 2566 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2567 2567 def export(ui, repo, *changesets, **opts):
2568 2568 """dump the header and diffs for one or more changesets
2569 2569
2570 2570 Print the changeset header and diffs for one or more revisions.
2571 2571
2572 2572 The information shown in the changeset header is: author, date,
2573 2573 branch name (if non-default), changeset hash, parent(s) and commit
2574 2574 comment.
2575 2575
2576 2576 .. note::
2577 2577 export may generate unexpected diff output for merge
2578 2578 changesets, as it will compare the merge changeset against its
2579 2579 first parent only.
2580 2580
2581 2581 Output may be to a file, in which case the name of the file is
2582 2582 given using a format string. The formatting rules are as follows:
2583 2583
2584 2584 :``%%``: literal "%" character
2585 2585 :``%H``: changeset hash (40 hexadecimal digits)
2586 2586 :``%N``: number of patches being generated
2587 2587 :``%R``: changeset revision number
2588 2588 :``%b``: basename of the exporting repository
2589 2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2590 2590 :``%m``: first line of the commit message (only alphanumeric characters)
2591 2591 :``%n``: zero-padded sequence number, starting at 1
2592 2592 :``%r``: zero-padded changeset revision number
2593 2593
2594 2594 Without the -a/--text option, export will avoid generating diffs
2595 2595 of files it detects as binary. With -a, export will generate a
2596 2596 diff anyway, probably with undesirable results.
2597 2597
2598 2598 Use the -g/--git option to generate diffs in the git extended diff
2599 2599 format. See :hg:`help diffs` for more information.
2600 2600
2601 2601 With the --switch-parent option, the diff will be against the
2602 2602 second parent. It can be useful to review a merge.
2603 2603
2604 2604 .. container:: verbose
2605 2605
2606 2606 Examples:
2607 2607
2608 2608 - use export and import to transplant a bugfix to the current
2609 2609 branch::
2610 2610
2611 2611 hg export -r 9353 | hg import -
2612 2612
2613 2613 - export all the changesets between two revisions to a file with
2614 2614 rename information::
2615 2615
2616 2616 hg export --git -r 123:150 > changes.txt
2617 2617
2618 2618 - split outgoing changes into a series of patches with
2619 2619 descriptive names::
2620 2620
2621 2621 hg export -r "outgoing()" -o "%n-%m.patch"
2622 2622
2623 2623 Returns 0 on success.
2624 2624 """
2625 2625 changesets += tuple(opts.get('rev', []))
2626 2626 revs = scmutil.revrange(repo, changesets)
2627 2627 if not revs:
2628 2628 raise util.Abort(_("export requires at least one changeset"))
2629 2629 if len(revs) > 1:
2630 2630 ui.note(_('exporting patches:\n'))
2631 2631 else:
2632 2632 ui.note(_('exporting patch:\n'))
2633 2633 cmdutil.export(repo, revs, template=opts.get('output'),
2634 2634 switch_parent=opts.get('switch_parent'),
2635 2635 opts=patch.diffopts(ui, opts))
2636 2636
2637 2637 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2638 2638 def forget(ui, repo, *pats, **opts):
2639 2639 """forget the specified files on the next commit
2640 2640
2641 2641 Mark the specified files so they will no longer be tracked
2642 2642 after the next commit.
2643 2643
2644 2644 This only removes files from the current branch, not from the
2645 2645 entire project history, and it does not delete them from the
2646 2646 working directory.
2647 2647
2648 2648 To undo a forget before the next commit, see :hg:`add`.
2649 2649
2650 2650 .. container:: verbose
2651 2651
2652 2652 Examples:
2653 2653
2654 2654 - forget newly-added binary files::
2655 2655
2656 2656 hg forget "set:added() and binary()"
2657 2657
2658 2658 - forget files that would be excluded by .hgignore::
2659 2659
2660 2660 hg forget "set:hgignore()"
2661 2661
2662 2662 Returns 0 on success.
2663 2663 """
2664 2664
2665 2665 if not pats:
2666 2666 raise util.Abort(_('no files specified'))
2667 2667
2668 2668 m = scmutil.match(repo[None], pats, opts)
2669 2669 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2670 2670 return rejected and 1 or 0
2671 2671
2672 2672 @command(
2673 2673 'graft',
2674 2674 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2675 2675 ('c', 'continue', False, _('resume interrupted graft')),
2676 2676 ('e', 'edit', False, _('invoke editor on commit messages')),
2677 2677 ('', 'log', None, _('append graft info to log message')),
2678 2678 ('D', 'currentdate', False,
2679 2679 _('record the current date as commit date')),
2680 2680 ('U', 'currentuser', False,
2681 2681 _('record the current user as committer'), _('DATE'))]
2682 2682 + commitopts2 + mergetoolopts + dryrunopts,
2683 2683 _('[OPTION]... [-r] REV...'))
2684 2684 def graft(ui, repo, *revs, **opts):
2685 2685 '''copy changes from other branches onto the current branch
2686 2686
2687 2687 This command uses Mercurial's merge logic to copy individual
2688 2688 changes from other branches without merging branches in the
2689 2689 history graph. This is sometimes known as 'backporting' or
2690 2690 'cherry-picking'. By default, graft will copy user, date, and
2691 2691 description from the source changesets.
2692 2692
2693 2693 Changesets that are ancestors of the current revision, that have
2694 2694 already been grafted, or that are merges will be skipped.
2695 2695
2696 2696 If --log is specified, log messages will have a comment appended
2697 2697 of the form::
2698 2698
2699 2699 (grafted from CHANGESETHASH)
2700 2700
2701 2701 If a graft merge results in conflicts, the graft process is
2702 2702 interrupted so that the current merge can be manually resolved.
2703 2703 Once all conflicts are addressed, the graft process can be
2704 2704 continued with the -c/--continue option.
2705 2705
2706 2706 .. note::
2707 2707 The -c/--continue option does not reapply earlier options.
2708 2708
2709 2709 .. container:: verbose
2710 2710
2711 2711 Examples:
2712 2712
2713 2713 - copy a single change to the stable branch and edit its description::
2714 2714
2715 2715 hg update stable
2716 2716 hg graft --edit 9393
2717 2717
2718 2718 - graft a range of changesets with one exception, updating dates::
2719 2719
2720 2720 hg graft -D "2085::2093 and not 2091"
2721 2721
2722 2722 - continue a graft after resolving conflicts::
2723 2723
2724 2724 hg graft -c
2725 2725
2726 2726 - show the source of a grafted changeset::
2727 2727
2728 2728 hg log --debug -r tip
2729 2729
2730 2730 Returns 0 on successful completion.
2731 2731 '''
2732 2732
2733 2733 revs = list(revs)
2734 2734 revs.extend(opts['rev'])
2735 2735
2736 2736 if not opts.get('user') and opts.get('currentuser'):
2737 2737 opts['user'] = ui.username()
2738 2738 if not opts.get('date') and opts.get('currentdate'):
2739 2739 opts['date'] = "%d %d" % util.makedate()
2740 2740
2741 2741 editor = None
2742 2742 if opts.get('edit'):
2743 2743 editor = cmdutil.commitforceeditor
2744 2744
2745 2745 cont = False
2746 2746 if opts['continue']:
2747 2747 cont = True
2748 2748 if revs:
2749 2749 raise util.Abort(_("can't specify --continue and revisions"))
2750 2750 # read in unfinished revisions
2751 2751 try:
2752 2752 nodes = repo.opener.read('graftstate').splitlines()
2753 2753 revs = [repo[node].rev() for node in nodes]
2754 2754 except IOError, inst:
2755 2755 if inst.errno != errno.ENOENT:
2756 2756 raise
2757 2757 raise util.Abort(_("no graft state found, can't continue"))
2758 2758 else:
2759 2759 cmdutil.bailifchanged(repo)
2760 2760 if not revs:
2761 2761 raise util.Abort(_('no revisions specified'))
2762 2762 revs = scmutil.revrange(repo, revs)
2763 2763
2764 2764 # check for merges
2765 2765 for rev in repo.revs('%ld and merge()', revs):
2766 2766 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2767 2767 revs.remove(rev)
2768 2768 if not revs:
2769 2769 return -1
2770 2770
2771 2771 # check for ancestors of dest branch
2772 2772 for rev in repo.revs('::. and %ld', revs):
2773 2773 ui.warn(_('skipping ancestor revision %s\n') % rev)
2774 2774 revs.remove(rev)
2775 2775 if not revs:
2776 2776 return -1
2777 2777
2778 2778 # analyze revs for earlier grafts
2779 2779 ids = {}
2780 2780 for ctx in repo.set("%ld", revs):
2781 2781 ids[ctx.hex()] = ctx.rev()
2782 2782 n = ctx.extra().get('source')
2783 2783 if n:
2784 2784 ids[n] = ctx.rev()
2785 2785
2786 2786 # check ancestors for earlier grafts
2787 2787 ui.debug('scanning for duplicate grafts\n')
2788 2788 for ctx in repo.set("::. - ::%ld", revs):
2789 2789 n = ctx.extra().get('source')
2790 2790 if n in ids:
2791 2791 r = repo[n].rev()
2792 2792 if r in revs:
2793 2793 ui.warn(_('skipping already grafted revision %s\n') % r)
2794 2794 revs.remove(r)
2795 2795 elif ids[n] in revs:
2796 2796 ui.warn(_('skipping already grafted revision %s '
2797 2797 '(same origin %d)\n') % (ids[n], r))
2798 2798 revs.remove(ids[n])
2799 2799 elif ctx.hex() in ids:
2800 2800 r = ids[ctx.hex()]
2801 2801 ui.warn(_('skipping already grafted revision %s '
2802 2802 '(was grafted from %d)\n') % (r, ctx.rev()))
2803 2803 revs.remove(r)
2804 2804 if not revs:
2805 2805 return -1
2806 2806
2807 2807 wlock = repo.wlock()
2808 2808 try:
2809 2809 for pos, ctx in enumerate(repo.set("%ld", revs)):
2810 2810 current = repo['.']
2811 2811
2812 2812 ui.status(_('grafting revision %s\n') % ctx.rev())
2813 2813 if opts.get('dry_run'):
2814 2814 continue
2815 2815
2816 2816 # we don't merge the first commit when continuing
2817 2817 if not cont:
2818 2818 # perform the graft merge with p1(rev) as 'ancestor'
2819 2819 try:
2820 2820 # ui.forcemerge is an internal variable, do not document
2821 2821 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2822 2822 stats = mergemod.update(repo, ctx.node(), True, True, False,
2823 2823 ctx.p1().node())
2824 2824 finally:
2825 2825 repo.ui.setconfig('ui', 'forcemerge', '')
2826 2826 # report any conflicts
2827 2827 if stats and stats[3] > 0:
2828 2828 # write out state for --continue
2829 2829 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2830 2830 repo.opener.write('graftstate', ''.join(nodelines))
2831 2831 raise util.Abort(
2832 2832 _("unresolved conflicts, can't continue"),
2833 2833 hint=_('use hg resolve and hg graft --continue'))
2834 2834 else:
2835 2835 cont = False
2836 2836
2837 2837 # drop the second merge parent
2838 2838 repo.setparents(current.node(), nullid)
2839 2839 repo.dirstate.write()
2840 2840 # fix up dirstate for copies and renames
2841 2841 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2842 2842
2843 2843 # commit
2844 2844 source = ctx.extra().get('source')
2845 2845 if not source:
2846 2846 source = ctx.hex()
2847 2847 extra = {'source': source}
2848 2848 user = ctx.user()
2849 2849 if opts.get('user'):
2850 2850 user = opts['user']
2851 2851 date = ctx.date()
2852 2852 if opts.get('date'):
2853 2853 date = opts['date']
2854 2854 message = ctx.description()
2855 2855 if opts.get('log'):
2856 2856 message += '\n(grafted from %s)' % ctx.hex()
2857 2857 node = repo.commit(text=message, user=user,
2858 2858 date=date, extra=extra, editor=editor)
2859 2859 if node is None:
2860 2860 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2861 2861 finally:
2862 2862 wlock.release()
2863 2863
2864 2864 # remove state when we complete successfully
2865 2865 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2866 2866 util.unlinkpath(repo.join('graftstate'))
2867 2867
2868 2868 return 0
2869 2869
2870 2870 @command('grep',
2871 2871 [('0', 'print0', None, _('end fields with NUL')),
2872 2872 ('', 'all', None, _('print all revisions that match')),
2873 2873 ('a', 'text', None, _('treat all files as text')),
2874 2874 ('f', 'follow', None,
2875 2875 _('follow changeset history,'
2876 2876 ' or file history across copies and renames')),
2877 2877 ('i', 'ignore-case', None, _('ignore case when matching')),
2878 2878 ('l', 'files-with-matches', None,
2879 2879 _('print only filenames and revisions that match')),
2880 2880 ('n', 'line-number', None, _('print matching line numbers')),
2881 2881 ('r', 'rev', [],
2882 2882 _('only search files changed within revision range'), _('REV')),
2883 2883 ('u', 'user', None, _('list the author (long with -v)')),
2884 2884 ('d', 'date', None, _('list the date (short with -q)')),
2885 2885 ] + walkopts,
2886 2886 _('[OPTION]... PATTERN [FILE]...'))
2887 2887 def grep(ui, repo, pattern, *pats, **opts):
2888 2888 """search for a pattern in specified files and revisions
2889 2889
2890 2890 Search revisions of files for a regular expression.
2891 2891
2892 2892 This command behaves differently than Unix grep. It only accepts
2893 2893 Python/Perl regexps. It searches repository history, not the
2894 2894 working directory. It always prints the revision number in which a
2895 2895 match appears.
2896 2896
2897 2897 By default, grep only prints output for the first revision of a
2898 2898 file in which it finds a match. To get it to print every revision
2899 2899 that contains a change in match status ("-" for a match that
2900 2900 becomes a non-match, or "+" for a non-match that becomes a match),
2901 2901 use the --all flag.
2902 2902
2903 2903 Returns 0 if a match is found, 1 otherwise.
2904 2904 """
2905 2905 reflags = re.M
2906 2906 if opts.get('ignore_case'):
2907 2907 reflags |= re.I
2908 2908 try:
2909 2909 regexp = re.compile(pattern, reflags)
2910 2910 except re.error, inst:
2911 2911 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2912 2912 return 1
2913 2913 sep, eol = ':', '\n'
2914 2914 if opts.get('print0'):
2915 2915 sep = eol = '\0'
2916 2916
2917 2917 getfile = util.lrucachefunc(repo.file)
2918 2918
2919 2919 def matchlines(body):
2920 2920 begin = 0
2921 2921 linenum = 0
2922 2922 while True:
2923 2923 match = regexp.search(body, begin)
2924 2924 if not match:
2925 2925 break
2926 2926 mstart, mend = match.span()
2927 2927 linenum += body.count('\n', begin, mstart) + 1
2928 2928 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2929 2929 begin = body.find('\n', mend) + 1 or len(body) + 1
2930 2930 lend = begin - 1
2931 2931 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2932 2932
2933 2933 class linestate(object):
2934 2934 def __init__(self, line, linenum, colstart, colend):
2935 2935 self.line = line
2936 2936 self.linenum = linenum
2937 2937 self.colstart = colstart
2938 2938 self.colend = colend
2939 2939
2940 2940 def __hash__(self):
2941 2941 return hash((self.linenum, self.line))
2942 2942
2943 2943 def __eq__(self, other):
2944 2944 return self.line == other.line
2945 2945
2946 2946 matches = {}
2947 2947 copies = {}
2948 2948 def grepbody(fn, rev, body):
2949 2949 matches[rev].setdefault(fn, [])
2950 2950 m = matches[rev][fn]
2951 2951 for lnum, cstart, cend, line in matchlines(body):
2952 2952 s = linestate(line, lnum, cstart, cend)
2953 2953 m.append(s)
2954 2954
2955 2955 def difflinestates(a, b):
2956 2956 sm = difflib.SequenceMatcher(None, a, b)
2957 2957 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2958 2958 if tag == 'insert':
2959 2959 for i in xrange(blo, bhi):
2960 2960 yield ('+', b[i])
2961 2961 elif tag == 'delete':
2962 2962 for i in xrange(alo, ahi):
2963 2963 yield ('-', a[i])
2964 2964 elif tag == 'replace':
2965 2965 for i in xrange(alo, ahi):
2966 2966 yield ('-', a[i])
2967 2967 for i in xrange(blo, bhi):
2968 2968 yield ('+', b[i])
2969 2969
2970 2970 def display(fn, ctx, pstates, states):
2971 2971 rev = ctx.rev()
2972 2972 datefunc = ui.quiet and util.shortdate or util.datestr
2973 2973 found = False
2974 2974 filerevmatches = {}
2975 2975 def binary():
2976 2976 flog = getfile(fn)
2977 2977 return util.binary(flog.read(ctx.filenode(fn)))
2978 2978
2979 2979 if opts.get('all'):
2980 2980 iter = difflinestates(pstates, states)
2981 2981 else:
2982 2982 iter = [('', l) for l in states]
2983 2983 for change, l in iter:
2984 2984 cols = [fn, str(rev)]
2985 2985 before, match, after = None, None, None
2986 2986 if opts.get('line_number'):
2987 2987 cols.append(str(l.linenum))
2988 2988 if opts.get('all'):
2989 2989 cols.append(change)
2990 2990 if opts.get('user'):
2991 2991 cols.append(ui.shortuser(ctx.user()))
2992 2992 if opts.get('date'):
2993 2993 cols.append(datefunc(ctx.date()))
2994 2994 if opts.get('files_with_matches'):
2995 2995 c = (fn, rev)
2996 2996 if c in filerevmatches:
2997 2997 continue
2998 2998 filerevmatches[c] = 1
2999 2999 else:
3000 3000 before = l.line[:l.colstart]
3001 3001 match = l.line[l.colstart:l.colend]
3002 3002 after = l.line[l.colend:]
3003 3003 ui.write(sep.join(cols))
3004 3004 if before is not None:
3005 3005 if not opts.get('text') and binary():
3006 3006 ui.write(sep + " Binary file matches")
3007 3007 else:
3008 3008 ui.write(sep + before)
3009 3009 ui.write(match, label='grep.match')
3010 3010 ui.write(after)
3011 3011 ui.write(eol)
3012 3012 found = True
3013 3013 return found
3014 3014
3015 3015 skip = {}
3016 3016 revfiles = {}
3017 3017 matchfn = scmutil.match(repo[None], pats, opts)
3018 3018 found = False
3019 3019 follow = opts.get('follow')
3020 3020
3021 3021 def prep(ctx, fns):
3022 3022 rev = ctx.rev()
3023 3023 pctx = ctx.p1()
3024 3024 parent = pctx.rev()
3025 3025 matches.setdefault(rev, {})
3026 3026 matches.setdefault(parent, {})
3027 3027 files = revfiles.setdefault(rev, [])
3028 3028 for fn in fns:
3029 3029 flog = getfile(fn)
3030 3030 try:
3031 3031 fnode = ctx.filenode(fn)
3032 3032 except error.LookupError:
3033 3033 continue
3034 3034
3035 3035 copied = flog.renamed(fnode)
3036 3036 copy = follow and copied and copied[0]
3037 3037 if copy:
3038 3038 copies.setdefault(rev, {})[fn] = copy
3039 3039 if fn in skip:
3040 3040 if copy:
3041 3041 skip[copy] = True
3042 3042 continue
3043 3043 files.append(fn)
3044 3044
3045 3045 if fn not in matches[rev]:
3046 3046 grepbody(fn, rev, flog.read(fnode))
3047 3047
3048 3048 pfn = copy or fn
3049 3049 if pfn not in matches[parent]:
3050 3050 try:
3051 3051 fnode = pctx.filenode(pfn)
3052 3052 grepbody(pfn, parent, flog.read(fnode))
3053 3053 except error.LookupError:
3054 3054 pass
3055 3055
3056 3056 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3057 3057 rev = ctx.rev()
3058 3058 parent = ctx.p1().rev()
3059 3059 for fn in sorted(revfiles.get(rev, [])):
3060 3060 states = matches[rev][fn]
3061 3061 copy = copies.get(rev, {}).get(fn)
3062 3062 if fn in skip:
3063 3063 if copy:
3064 3064 skip[copy] = True
3065 3065 continue
3066 3066 pstates = matches.get(parent, {}).get(copy or fn, [])
3067 3067 if pstates or states:
3068 3068 r = display(fn, ctx, pstates, states)
3069 3069 found = found or r
3070 3070 if r and not opts.get('all'):
3071 3071 skip[fn] = True
3072 3072 if copy:
3073 3073 skip[copy] = True
3074 3074 del matches[rev]
3075 3075 del revfiles[rev]
3076 3076
3077 3077 return not found
3078 3078
3079 3079 @command('heads',
3080 3080 [('r', 'rev', '',
3081 3081 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3082 3082 ('t', 'topo', False, _('show topological heads only')),
3083 3083 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3084 3084 ('c', 'closed', False, _('show normal and closed branch heads')),
3085 3085 ] + templateopts,
3086 3086 _('[-ct] [-r STARTREV] [REV]...'))
3087 3087 def heads(ui, repo, *branchrevs, **opts):
3088 3088 """show current repository heads or show branch heads
3089 3089
3090 3090 With no arguments, show all repository branch heads.
3091 3091
3092 3092 Repository "heads" are changesets with no child changesets. They are
3093 3093 where development generally takes place and are the usual targets
3094 3094 for update and merge operations. Branch heads are changesets that have
3095 3095 no child changeset on the same branch.
3096 3096
3097 3097 If one or more REVs are given, only branch heads on the branches
3098 3098 associated with the specified changesets are shown. This means
3099 3099 that you can use :hg:`heads foo` to see the heads on a branch
3100 3100 named ``foo``.
3101 3101
3102 3102 If -c/--closed is specified, also show branch heads marked closed
3103 3103 (see :hg:`commit --close-branch`).
3104 3104
3105 3105 If STARTREV is specified, only those heads that are descendants of
3106 3106 STARTREV will be displayed.
3107 3107
3108 3108 If -t/--topo is specified, named branch mechanics will be ignored and only
3109 3109 changesets without children will be shown.
3110 3110
3111 3111 Returns 0 if matching heads are found, 1 if not.
3112 3112 """
3113 3113
3114 3114 start = None
3115 3115 if 'rev' in opts:
3116 3116 start = scmutil.revsingle(repo, opts['rev'], None).node()
3117 3117
3118 3118 if opts.get('topo'):
3119 3119 heads = [repo[h] for h in repo.heads(start)]
3120 3120 else:
3121 3121 heads = []
3122 3122 for branch in repo.branchmap():
3123 3123 heads += repo.branchheads(branch, start, opts.get('closed'))
3124 3124 heads = [repo[h] for h in heads]
3125 3125
3126 3126 if branchrevs:
3127 3127 branches = set(repo[br].branch() for br in branchrevs)
3128 3128 heads = [h for h in heads if h.branch() in branches]
3129 3129
3130 3130 if opts.get('active') and branchrevs:
3131 3131 dagheads = repo.heads(start)
3132 3132 heads = [h for h in heads if h.node() in dagheads]
3133 3133
3134 3134 if branchrevs:
3135 3135 haveheads = set(h.branch() for h in heads)
3136 3136 if branches - haveheads:
3137 3137 headless = ', '.join(b for b in branches - haveheads)
3138 3138 msg = _('no open branch heads found on branches %s')
3139 3139 if opts.get('rev'):
3140 3140 msg += _(' (started at %s)') % opts['rev']
3141 3141 ui.warn((msg + '\n') % headless)
3142 3142
3143 3143 if not heads:
3144 3144 return 1
3145 3145
3146 3146 heads = sorted(heads, key=lambda x: -x.rev())
3147 3147 displayer = cmdutil.show_changeset(ui, repo, opts)
3148 3148 for ctx in heads:
3149 3149 displayer.show(ctx)
3150 3150 displayer.close()
3151 3151
3152 3152 @command('help',
3153 3153 [('e', 'extension', None, _('show only help for extensions')),
3154 3154 ('c', 'command', None, _('show only help for commands')),
3155 3155 ('k', 'keyword', '', _('show topics matching keyword')),
3156 3156 ],
3157 3157 _('[-ec] [TOPIC]'))
3158 3158 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3159 3159 """show help for a given topic or a help overview
3160 3160
3161 3161 With no arguments, print a list of commands with short help messages.
3162 3162
3163 3163 Given a topic, extension, or command name, print help for that
3164 3164 topic.
3165 3165
3166 3166 Returns 0 if successful.
3167 3167 """
3168 3168
3169 3169 textwidth = min(ui.termwidth(), 80) - 2
3170 3170
3171 3171 def helpcmd(name):
3172 3172 try:
3173 3173 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3174 3174 except error.AmbiguousCommand, inst:
3175 3175 # py3k fix: except vars can't be used outside the scope of the
3176 3176 # except block, nor can be used inside a lambda. python issue4617
3177 3177 prefix = inst.args[0]
3178 3178 select = lambda c: c.lstrip('^').startswith(prefix)
3179 3179 rst = helplist(select)
3180 3180 return rst
3181 3181
3182 3182 rst = []
3183 3183
3184 3184 # check if it's an invalid alias and display its error if it is
3185 3185 if getattr(entry[0], 'badalias', False):
3186 3186 if not unknowncmd:
3187 3187 ui.pushbuffer()
3188 3188 entry[0](ui)
3189 3189 rst.append(ui.popbuffer())
3190 3190 return rst
3191 3191
3192 3192 # synopsis
3193 3193 if len(entry) > 2:
3194 3194 if entry[2].startswith('hg'):
3195 3195 rst.append("%s\n" % entry[2])
3196 3196 else:
3197 3197 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3198 3198 else:
3199 3199 rst.append('hg %s\n' % aliases[0])
3200 3200 # aliases
3201 3201 if full and not ui.quiet and len(aliases) > 1:
3202 3202 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3203 3203 rst.append('\n')
3204 3204
3205 3205 # description
3206 3206 doc = gettext(entry[0].__doc__)
3207 3207 if not doc:
3208 3208 doc = _("(no help text available)")
3209 3209 if util.safehasattr(entry[0], 'definition'): # aliased command
3210 3210 if entry[0].definition.startswith('!'): # shell alias
3211 3211 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3212 3212 else:
3213 3213 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3214 3214 doc = doc.splitlines(True)
3215 3215 if ui.quiet or not full:
3216 3216 rst.append(doc[0])
3217 3217 else:
3218 3218 rst.extend(doc)
3219 3219 rst.append('\n')
3220 3220
3221 3221 # check if this command shadows a non-trivial (multi-line)
3222 3222 # extension help text
3223 3223 try:
3224 3224 mod = extensions.find(name)
3225 3225 doc = gettext(mod.__doc__) or ''
3226 3226 if '\n' in doc.strip():
3227 3227 msg = _('use "hg help -e %s" to show help for '
3228 3228 'the %s extension') % (name, name)
3229 3229 rst.append('\n%s\n' % msg)
3230 3230 except KeyError:
3231 3231 pass
3232 3232
3233 3233 # options
3234 3234 if not ui.quiet and entry[1]:
3235 3235 rst.append('\n%s\n\n' % _("options:"))
3236 3236 rst.append(help.optrst(entry[1], ui.verbose))
3237 3237
3238 3238 if ui.verbose:
3239 3239 rst.append('\n%s\n\n' % _("global options:"))
3240 3240 rst.append(help.optrst(globalopts, ui.verbose))
3241 3241
3242 3242 if not ui.verbose:
3243 3243 if not full:
3244 3244 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3245 3245 % name)
3246 3246 elif not ui.quiet:
3247 3247 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3248 3248 % name)
3249 3249 return rst
3250 3250
3251 3251
3252 3252 def helplist(select=None):
3253 3253 # list of commands
3254 3254 if name == "shortlist":
3255 3255 header = _('basic commands:\n\n')
3256 3256 else:
3257 3257 header = _('list of commands:\n\n')
3258 3258
3259 3259 h = {}
3260 3260 cmds = {}
3261 3261 for c, e in table.iteritems():
3262 3262 f = c.split("|", 1)[0]
3263 3263 if select and not select(f):
3264 3264 continue
3265 3265 if (not select and name != 'shortlist' and
3266 3266 e[0].__module__ != __name__):
3267 3267 continue
3268 3268 if name == "shortlist" and not f.startswith("^"):
3269 3269 continue
3270 3270 f = f.lstrip("^")
3271 3271 if not ui.debugflag and f.startswith("debug"):
3272 3272 continue
3273 3273 doc = e[0].__doc__
3274 3274 if doc and 'DEPRECATED' in doc and not ui.verbose:
3275 3275 continue
3276 3276 doc = gettext(doc)
3277 3277 if not doc:
3278 3278 doc = _("(no help text available)")
3279 3279 h[f] = doc.splitlines()[0].rstrip()
3280 3280 cmds[f] = c.lstrip("^")
3281 3281
3282 3282 rst = []
3283 3283 if not h:
3284 3284 if not ui.quiet:
3285 3285 rst.append(_('no commands defined\n'))
3286 3286 return rst
3287 3287
3288 3288 if not ui.quiet:
3289 3289 rst.append(header)
3290 3290 fns = sorted(h)
3291 3291 for f in fns:
3292 3292 if ui.verbose:
3293 3293 commands = cmds[f].replace("|",", ")
3294 3294 rst.append(" :%s: %s\n" % (commands, h[f]))
3295 3295 else:
3296 3296 rst.append(' :%s: %s\n' % (f, h[f]))
3297 3297
3298 3298 if not name:
3299 3299 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3300 3300 if exts:
3301 3301 rst.append('\n')
3302 3302 rst.extend(exts)
3303 3303
3304 3304 rst.append(_("\nadditional help topics:\n\n"))
3305 3305 topics = []
3306 3306 for names, header, doc in help.helptable:
3307 3307 topics.append((names[0], header))
3308 3308 for t, desc in topics:
3309 3309 rst.append(" :%s: %s\n" % (t, desc))
3310 3310
3311 3311 optlist = []
3312 3312 if not ui.quiet:
3313 3313 if ui.verbose:
3314 3314 optlist.append((_("global options:"), globalopts))
3315 3315 if name == 'shortlist':
3316 3316 optlist.append((_('use "hg help" for the full list '
3317 3317 'of commands'), ()))
3318 3318 else:
3319 3319 if name == 'shortlist':
3320 3320 msg = _('use "hg help" for the full list of commands '
3321 3321 'or "hg -v" for details')
3322 3322 elif name and not full:
3323 3323 msg = _('use "hg help %s" to show the full help '
3324 3324 'text') % name
3325 3325 else:
3326 3326 msg = _('use "hg -v help%s" to show builtin aliases and '
3327 3327 'global options') % (name and " " + name or "")
3328 3328 optlist.append((msg, ()))
3329 3329
3330 3330 if optlist:
3331 3331 for title, options in optlist:
3332 3332 rst.append('\n%s\n' % title)
3333 3333 if options:
3334 3334 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3335 3335 return rst
3336 3336
3337 3337 def helptopic(name):
3338 3338 for names, header, doc in help.helptable:
3339 3339 if name in names:
3340 3340 break
3341 3341 else:
3342 3342 raise error.UnknownCommand(name)
3343 3343
3344 3344 rst = ["%s\n\n" % header]
3345 3345 # description
3346 3346 if not doc:
3347 3347 rst.append(" %s\n" % _("(no help text available)"))
3348 3348 if util.safehasattr(doc, '__call__'):
3349 3349 rst += [" %s\n" % l for l in doc().splitlines()]
3350 3350
3351 3351 try:
3352 3352 cmdutil.findcmd(name, table)
3353 3353 rst.append(_('\nuse "hg help -c %s" to see help for '
3354 3354 'the %s command\n') % (name, name))
3355 3355 except error.UnknownCommand:
3356 3356 pass
3357 3357 return rst
3358 3358
3359 3359 def helpext(name):
3360 3360 try:
3361 3361 mod = extensions.find(name)
3362 3362 doc = gettext(mod.__doc__) or _('no help text available')
3363 3363 except KeyError:
3364 3364 mod = None
3365 3365 doc = extensions.disabledext(name)
3366 3366 if not doc:
3367 3367 raise error.UnknownCommand(name)
3368 3368
3369 3369 if '\n' not in doc:
3370 3370 head, tail = doc, ""
3371 3371 else:
3372 3372 head, tail = doc.split('\n', 1)
3373 3373 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3374 3374 if tail:
3375 3375 rst.extend(tail.splitlines(True))
3376 3376 rst.append('\n')
3377 3377
3378 3378 if mod:
3379 3379 try:
3380 3380 ct = mod.cmdtable
3381 3381 except AttributeError:
3382 3382 ct = {}
3383 3383 modcmds = set([c.split('|', 1)[0] for c in ct])
3384 3384 rst.extend(helplist(modcmds.__contains__))
3385 3385 else:
3386 3386 rst.append(_('use "hg help extensions" for information on enabling '
3387 3387 'extensions\n'))
3388 3388 return rst
3389 3389
3390 3390 def helpextcmd(name):
3391 3391 cmd, ext, mod = extensions.disabledcmd(ui, name,
3392 3392 ui.configbool('ui', 'strict'))
3393 3393 doc = gettext(mod.__doc__).splitlines()[0]
3394 3394
3395 3395 rst = help.listexts(_("'%s' is provided by the following "
3396 3396 "extension:") % cmd, {ext: doc}, indent=4)
3397 3397 rst.append('\n')
3398 3398 rst.append(_('use "hg help extensions" for information on enabling '
3399 3399 'extensions\n'))
3400 3400 return rst
3401 3401
3402 3402
3403 3403 rst = []
3404 3404 kw = opts.get('keyword')
3405 3405 if kw:
3406 3406 matches = help.topicmatch(kw)
3407 3407 for t, title in (('topics', _('Topics')),
3408 3408 ('commands', _('Commands')),
3409 3409 ('extensions', _('Extensions')),
3410 3410 ('extensioncommands', _('Extension Commands'))):
3411 3411 if matches[t]:
3412 3412 rst.append('%s:\n\n' % title)
3413 3413 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3414 3414 rst.append('\n')
3415 3415 elif name and name != 'shortlist':
3416 3416 i = None
3417 3417 if unknowncmd:
3418 3418 queries = (helpextcmd,)
3419 3419 elif opts.get('extension'):
3420 3420 queries = (helpext,)
3421 3421 elif opts.get('command'):
3422 3422 queries = (helpcmd,)
3423 3423 else:
3424 3424 queries = (helptopic, helpcmd, helpext, helpextcmd)
3425 3425 for f in queries:
3426 3426 try:
3427 3427 rst = f(name)
3428 3428 i = None
3429 3429 break
3430 3430 except error.UnknownCommand, inst:
3431 3431 i = inst
3432 3432 if i:
3433 3433 raise i
3434 3434 else:
3435 3435 # program name
3436 3436 if not ui.quiet:
3437 3437 rst = [_("Mercurial Distributed SCM\n"), '\n']
3438 3438 rst.extend(helplist())
3439 3439
3440 3440 keep = ui.verbose and ['verbose'] or []
3441 3441 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3442 3442 ui.write(formatted)
3443 3443
3444 3444
3445 3445 @command('identify|id',
3446 3446 [('r', 'rev', '',
3447 3447 _('identify the specified revision'), _('REV')),
3448 3448 ('n', 'num', None, _('show local revision number')),
3449 3449 ('i', 'id', None, _('show global revision id')),
3450 3450 ('b', 'branch', None, _('show branch')),
3451 3451 ('t', 'tags', None, _('show tags')),
3452 3452 ('B', 'bookmarks', None, _('show bookmarks')),
3453 3453 ] + remoteopts,
3454 3454 _('[-nibtB] [-r REV] [SOURCE]'))
3455 3455 def identify(ui, repo, source=None, rev=None,
3456 3456 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3457 3457 """identify the working copy or specified revision
3458 3458
3459 3459 Print a summary identifying the repository state at REV using one or
3460 3460 two parent hash identifiers, followed by a "+" if the working
3461 3461 directory has uncommitted changes, the branch name (if not default),
3462 3462 a list of tags, and a list of bookmarks.
3463 3463
3464 3464 When REV is not given, print a summary of the current state of the
3465 3465 repository.
3466 3466
3467 3467 Specifying a path to a repository root or Mercurial bundle will
3468 3468 cause lookup to operate on that repository/bundle.
3469 3469
3470 3470 .. container:: verbose
3471 3471
3472 3472 Examples:
3473 3473
3474 3474 - generate a build identifier for the working directory::
3475 3475
3476 3476 hg id --id > build-id.dat
3477 3477
3478 3478 - find the revision corresponding to a tag::
3479 3479
3480 3480 hg id -n -r 1.3
3481 3481
3482 3482 - check the most recent revision of a remote repository::
3483 3483
3484 3484 hg id -r tip http://selenic.com/hg/
3485 3485
3486 3486 Returns 0 if successful.
3487 3487 """
3488 3488
3489 3489 if not repo and not source:
3490 3490 raise util.Abort(_("there is no Mercurial repository here "
3491 3491 "(.hg not found)"))
3492 3492
3493 3493 hexfunc = ui.debugflag and hex or short
3494 3494 default = not (num or id or branch or tags or bookmarks)
3495 3495 output = []
3496 3496 revs = []
3497 3497
3498 3498 if source:
3499 3499 source, branches = hg.parseurl(ui.expandpath(source))
3500 3500 peer = hg.peer(ui, opts, source)
3501 3501 repo = peer.local()
3502 3502 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3503 3503
3504 3504 if not repo:
3505 3505 if num or branch or tags:
3506 3506 raise util.Abort(
3507 3507 _("can't query remote revision number, branch, or tags"))
3508 3508 if not rev and revs:
3509 3509 rev = revs[0]
3510 3510 if not rev:
3511 3511 rev = "tip"
3512 3512
3513 3513 remoterev = peer.lookup(rev)
3514 3514 if default or id:
3515 3515 output = [hexfunc(remoterev)]
3516 3516
3517 3517 def getbms():
3518 3518 bms = []
3519 3519
3520 3520 if 'bookmarks' in peer.listkeys('namespaces'):
3521 3521 hexremoterev = hex(remoterev)
3522 3522 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3523 3523 if bmr == hexremoterev]
3524 3524
3525 3525 return bms
3526 3526
3527 3527 if bookmarks:
3528 3528 output.extend(getbms())
3529 3529 elif default and not ui.quiet:
3530 3530 # multiple bookmarks for a single parent separated by '/'
3531 3531 bm = '/'.join(getbms())
3532 3532 if bm:
3533 3533 output.append(bm)
3534 3534 else:
3535 3535 if not rev:
3536 3536 ctx = repo[None]
3537 3537 parents = ctx.parents()
3538 3538 changed = ""
3539 3539 if default or id or num:
3540 3540 if (util.any(repo.status())
3541 3541 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3542 3542 changed = '+'
3543 3543 if default or id:
3544 3544 output = ["%s%s" %
3545 3545 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3546 3546 if num:
3547 3547 output.append("%s%s" %
3548 3548 ('+'.join([str(p.rev()) for p in parents]), changed))
3549 3549 else:
3550 3550 ctx = scmutil.revsingle(repo, rev)
3551 3551 if default or id:
3552 3552 output = [hexfunc(ctx.node())]
3553 3553 if num:
3554 3554 output.append(str(ctx.rev()))
3555 3555
3556 3556 if default and not ui.quiet:
3557 3557 b = ctx.branch()
3558 3558 if b != 'default':
3559 3559 output.append("(%s)" % b)
3560 3560
3561 3561 # multiple tags for a single parent separated by '/'
3562 3562 t = '/'.join(ctx.tags())
3563 3563 if t:
3564 3564 output.append(t)
3565 3565
3566 3566 # multiple bookmarks for a single parent separated by '/'
3567 3567 bm = '/'.join(ctx.bookmarks())
3568 3568 if bm:
3569 3569 output.append(bm)
3570 3570 else:
3571 3571 if branch:
3572 3572 output.append(ctx.branch())
3573 3573
3574 3574 if tags:
3575 3575 output.extend(ctx.tags())
3576 3576
3577 3577 if bookmarks:
3578 3578 output.extend(ctx.bookmarks())
3579 3579
3580 3580 ui.write("%s\n" % ' '.join(output))
3581 3581
3582 3582 @command('import|patch',
3583 3583 [('p', 'strip', 1,
3584 3584 _('directory strip option for patch. This has the same '
3585 3585 'meaning as the corresponding patch option'), _('NUM')),
3586 3586 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3587 3587 ('e', 'edit', False, _('invoke editor on commit messages')),
3588 3588 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3589 3589 ('', 'no-commit', None,
3590 3590 _("don't commit, just update the working directory")),
3591 3591 ('', 'bypass', None,
3592 3592 _("apply patch without touching the working directory")),
3593 3593 ('', 'exact', None,
3594 3594 _('apply patch to the nodes from which it was generated')),
3595 3595 ('', 'import-branch', None,
3596 3596 _('use any branch information in patch (implied by --exact)'))] +
3597 3597 commitopts + commitopts2 + similarityopts,
3598 3598 _('[OPTION]... PATCH...'))
3599 3599 def import_(ui, repo, patch1=None, *patches, **opts):
3600 3600 """import an ordered set of patches
3601 3601
3602 3602 Import a list of patches and commit them individually (unless
3603 3603 --no-commit is specified).
3604 3604
3605 3605 If there are outstanding changes in the working directory, import
3606 3606 will abort unless given the -f/--force flag.
3607 3607
3608 3608 You can import a patch straight from a mail message. Even patches
3609 3609 as attachments work (to use the body part, it must have type
3610 3610 text/plain or text/x-patch). From and Subject headers of email
3611 3611 message are used as default committer and commit message. All
3612 3612 text/plain body parts before first diff are added to commit
3613 3613 message.
3614 3614
3615 3615 If the imported patch was generated by :hg:`export`, user and
3616 3616 description from patch override values from message headers and
3617 3617 body. Values given on command line with -m/--message and -u/--user
3618 3618 override these.
3619 3619
3620 3620 If --exact is specified, import will set the working directory to
3621 3621 the parent of each patch before applying it, and will abort if the
3622 3622 resulting changeset has a different ID than the one recorded in
3623 3623 the patch. This may happen due to character set problems or other
3624 3624 deficiencies in the text patch format.
3625 3625
3626 3626 Use --bypass to apply and commit patches directly to the
3627 3627 repository, not touching the working directory. Without --exact,
3628 3628 patches will be applied on top of the working directory parent
3629 3629 revision.
3630 3630
3631 3631 With -s/--similarity, hg will attempt to discover renames and
3632 3632 copies in the patch in the same way as :hg:`addremove`.
3633 3633
3634 3634 To read a patch from standard input, use "-" as the patch name. If
3635 3635 a URL is specified, the patch will be downloaded from it.
3636 3636 See :hg:`help dates` for a list of formats valid for -d/--date.
3637 3637
3638 3638 .. container:: verbose
3639 3639
3640 3640 Examples:
3641 3641
3642 3642 - import a traditional patch from a website and detect renames::
3643 3643
3644 3644 hg import -s 80 http://example.com/bugfix.patch
3645 3645
3646 3646 - import a changeset from an hgweb server::
3647 3647
3648 3648 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3649 3649
3650 3650 - import all the patches in an Unix-style mbox::
3651 3651
3652 3652 hg import incoming-patches.mbox
3653 3653
3654 3654 - attempt to exactly restore an exported changeset (not always
3655 3655 possible)::
3656 3656
3657 3657 hg import --exact proposed-fix.patch
3658 3658
3659 3659 Returns 0 on success.
3660 3660 """
3661 3661
3662 3662 if not patch1:
3663 3663 raise util.Abort(_('need at least one patch to import'))
3664 3664
3665 3665 patches = (patch1,) + patches
3666 3666
3667 3667 date = opts.get('date')
3668 3668 if date:
3669 3669 opts['date'] = util.parsedate(date)
3670 3670
3671 3671 editor = cmdutil.commiteditor
3672 3672 if opts.get('edit'):
3673 3673 editor = cmdutil.commitforceeditor
3674 3674
3675 3675 update = not opts.get('bypass')
3676 3676 if not update and opts.get('no_commit'):
3677 3677 raise util.Abort(_('cannot use --no-commit with --bypass'))
3678 3678 try:
3679 3679 sim = float(opts.get('similarity') or 0)
3680 3680 except ValueError:
3681 3681 raise util.Abort(_('similarity must be a number'))
3682 3682 if sim < 0 or sim > 100:
3683 3683 raise util.Abort(_('similarity must be between 0 and 100'))
3684 3684 if sim and not update:
3685 3685 raise util.Abort(_('cannot use --similarity with --bypass'))
3686 3686
3687 3687 if (opts.get('exact') or not opts.get('force')) and update:
3688 3688 cmdutil.bailifchanged(repo)
3689 3689
3690 3690 base = opts["base"]
3691 3691 strip = opts["strip"]
3692 3692 wlock = lock = tr = None
3693 3693 msgs = []
3694 3694
3695 3695 def checkexact(repo, n, nodeid):
3696 3696 if opts.get('exact') and hex(n) != nodeid:
3697 3697 repo.rollback()
3698 3698 raise util.Abort(_('patch is damaged or loses information'))
3699 3699
3700 3700 def tryone(ui, hunk, parents):
3701 3701 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3702 3702 patch.extract(ui, hunk)
3703 3703
3704 3704 if not tmpname:
3705 3705 return (None, None)
3706 3706 msg = _('applied to working directory')
3707 3707
3708 3708 try:
3709 3709 cmdline_message = cmdutil.logmessage(ui, opts)
3710 3710 if cmdline_message:
3711 3711 # pickup the cmdline msg
3712 3712 message = cmdline_message
3713 3713 elif message:
3714 3714 # pickup the patch msg
3715 3715 message = message.strip()
3716 3716 else:
3717 3717 # launch the editor
3718 3718 message = None
3719 3719 ui.debug('message:\n%s\n' % message)
3720 3720
3721 3721 if len(parents) == 1:
3722 3722 parents.append(repo[nullid])
3723 3723 if opts.get('exact'):
3724 3724 if not nodeid or not p1:
3725 3725 raise util.Abort(_('not a Mercurial patch'))
3726 3726 p1 = repo[p1]
3727 3727 p2 = repo[p2 or nullid]
3728 3728 elif p2:
3729 3729 try:
3730 3730 p1 = repo[p1]
3731 3731 p2 = repo[p2]
3732 3732 # Without any options, consider p2 only if the
3733 3733 # patch is being applied on top of the recorded
3734 3734 # first parent.
3735 3735 if p1 != parents[0]:
3736 3736 p1 = parents[0]
3737 3737 p2 = repo[nullid]
3738 3738 except error.RepoError:
3739 3739 p1, p2 = parents
3740 3740 else:
3741 3741 p1, p2 = parents
3742 3742
3743 3743 n = None
3744 3744 if update:
3745 3745 if p1 != parents[0]:
3746 3746 hg.clean(repo, p1.node())
3747 3747 if p2 != parents[1]:
3748 3748 repo.setparents(p1.node(), p2.node())
3749 3749
3750 3750 if opts.get('exact') or opts.get('import_branch'):
3751 3751 repo.dirstate.setbranch(branch or 'default')
3752 3752
3753 3753 files = set()
3754 3754 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3755 3755 eolmode=None, similarity=sim / 100.0)
3756 3756 files = list(files)
3757 3757 if opts.get('no_commit'):
3758 3758 if message:
3759 3759 msgs.append(message)
3760 3760 else:
3761 3761 if opts.get('exact') or p2:
3762 3762 # If you got here, you either use --force and know what
3763 3763 # you are doing or used --exact or a merge patch while
3764 3764 # being updated to its first parent.
3765 3765 m = None
3766 3766 else:
3767 3767 m = scmutil.matchfiles(repo, files or [])
3768 3768 n = repo.commit(message, opts.get('user') or user,
3769 3769 opts.get('date') or date, match=m,
3770 3770 editor=editor)
3771 3771 checkexact(repo, n, nodeid)
3772 3772 else:
3773 3773 if opts.get('exact') or opts.get('import_branch'):
3774 3774 branch = branch or 'default'
3775 3775 else:
3776 3776 branch = p1.branch()
3777 3777 store = patch.filestore()
3778 3778 try:
3779 3779 files = set()
3780 3780 try:
3781 3781 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3782 3782 files, eolmode=None)
3783 3783 except patch.PatchError, e:
3784 3784 raise util.Abort(str(e))
3785 3785 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3786 3786 message,
3787 3787 opts.get('user') or user,
3788 3788 opts.get('date') or date,
3789 3789 branch, files, store,
3790 3790 editor=cmdutil.commiteditor)
3791 3791 repo.savecommitmessage(memctx.description())
3792 3792 n = memctx.commit()
3793 3793 checkexact(repo, n, nodeid)
3794 3794 finally:
3795 3795 store.close()
3796 3796 if n:
3797 3797 # i18n: refers to a short changeset id
3798 3798 msg = _('created %s') % short(n)
3799 3799 return (msg, n)
3800 3800 finally:
3801 3801 os.unlink(tmpname)
3802 3802
3803 3803 try:
3804 3804 try:
3805 3805 wlock = repo.wlock()
3806 3806 if not opts.get('no_commit'):
3807 3807 lock = repo.lock()
3808 3808 tr = repo.transaction('import')
3809 3809 parents = repo.parents()
3810 3810 for patchurl in patches:
3811 3811 if patchurl == '-':
3812 3812 ui.status(_('applying patch from stdin\n'))
3813 3813 patchfile = ui.fin
3814 3814 patchurl = 'stdin' # for error message
3815 3815 else:
3816 3816 patchurl = os.path.join(base, patchurl)
3817 3817 ui.status(_('applying %s\n') % patchurl)
3818 3818 patchfile = url.open(ui, patchurl)
3819 3819
3820 3820 haspatch = False
3821 3821 for hunk in patch.split(patchfile):
3822 3822 (msg, node) = tryone(ui, hunk, parents)
3823 3823 if msg:
3824 3824 haspatch = True
3825 3825 ui.note(msg + '\n')
3826 3826 if update or opts.get('exact'):
3827 3827 parents = repo.parents()
3828 3828 else:
3829 3829 parents = [repo[node]]
3830 3830
3831 3831 if not haspatch:
3832 3832 raise util.Abort(_('%s: no diffs found') % patchurl)
3833 3833
3834 3834 if tr:
3835 3835 tr.close()
3836 3836 if msgs:
3837 3837 repo.savecommitmessage('\n* * *\n'.join(msgs))
3838 3838 except: # re-raises
3839 3839 # wlock.release() indirectly calls dirstate.write(): since
3840 3840 # we're crashing, we do not want to change the working dir
3841 3841 # parent after all, so make sure it writes nothing
3842 3842 repo.dirstate.invalidate()
3843 3843 raise
3844 3844 finally:
3845 3845 if tr:
3846 3846 tr.release()
3847 3847 release(lock, wlock)
3848 3848
3849 3849 @command('incoming|in',
3850 3850 [('f', 'force', None,
3851 3851 _('run even if remote repository is unrelated')),
3852 3852 ('n', 'newest-first', None, _('show newest record first')),
3853 3853 ('', 'bundle', '',
3854 3854 _('file to store the bundles into'), _('FILE')),
3855 3855 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3856 3856 ('B', 'bookmarks', False, _("compare bookmarks")),
3857 3857 ('b', 'branch', [],
3858 3858 _('a specific branch you would like to pull'), _('BRANCH')),
3859 3859 ] + logopts + remoteopts + subrepoopts,
3860 3860 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3861 3861 def incoming(ui, repo, source="default", **opts):
3862 3862 """show new changesets found in source
3863 3863
3864 3864 Show new changesets found in the specified path/URL or the default
3865 3865 pull location. These are the changesets that would have been pulled
3866 3866 if a pull at the time you issued this command.
3867 3867
3868 3868 For remote repository, using --bundle avoids downloading the
3869 3869 changesets twice if the incoming is followed by a pull.
3870 3870
3871 3871 See pull for valid source format details.
3872 3872
3873 3873 Returns 0 if there are incoming changes, 1 otherwise.
3874 3874 """
3875 3875 if opts.get('graph'):
3876 3876 cmdutil.checkunsupportedgraphflags([], opts)
3877 3877 def display(other, chlist, displayer):
3878 3878 revdag = cmdutil.graphrevs(other, chlist, opts)
3879 3879 showparents = [ctx.node() for ctx in repo[None].parents()]
3880 3880 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3881 3881 graphmod.asciiedges)
3882 3882
3883 3883 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3884 3884 return 0
3885 3885
3886 3886 if opts.get('bundle') and opts.get('subrepos'):
3887 3887 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3888 3888
3889 3889 if opts.get('bookmarks'):
3890 3890 source, branches = hg.parseurl(ui.expandpath(source),
3891 3891 opts.get('branch'))
3892 3892 other = hg.peer(repo, opts, source)
3893 3893 if 'bookmarks' not in other.listkeys('namespaces'):
3894 3894 ui.warn(_("remote doesn't support bookmarks\n"))
3895 3895 return 0
3896 3896 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3897 3897 return bookmarks.diff(ui, repo, other)
3898 3898
3899 3899 repo._subtoppath = ui.expandpath(source)
3900 3900 try:
3901 3901 return hg.incoming(ui, repo, source, opts)
3902 3902 finally:
3903 3903 del repo._subtoppath
3904 3904
3905 3905
3906 3906 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3907 3907 def init(ui, dest=".", **opts):
3908 3908 """create a new repository in the given directory
3909 3909
3910 3910 Initialize a new repository in the given directory. If the given
3911 3911 directory does not exist, it will be created.
3912 3912
3913 3913 If no directory is given, the current directory is used.
3914 3914
3915 3915 It is possible to specify an ``ssh://`` URL as the destination.
3916 3916 See :hg:`help urls` for more information.
3917 3917
3918 3918 Returns 0 on success.
3919 3919 """
3920 3920 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3921 3921
3922 3922 @command('locate',
3923 3923 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3924 3924 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3925 3925 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3926 3926 ] + walkopts,
3927 3927 _('[OPTION]... [PATTERN]...'))
3928 3928 def locate(ui, repo, *pats, **opts):
3929 3929 """locate files matching specific patterns
3930 3930
3931 3931 Print files under Mercurial control in the working directory whose
3932 3932 names match the given patterns.
3933 3933
3934 3934 By default, this command searches all directories in the working
3935 3935 directory. To search just the current directory and its
3936 3936 subdirectories, use "--include .".
3937 3937
3938 3938 If no patterns are given to match, this command prints the names
3939 3939 of all files under Mercurial control in the working directory.
3940 3940
3941 3941 If you want to feed the output of this command into the "xargs"
3942 3942 command, use the -0 option to both this command and "xargs". This
3943 3943 will avoid the problem of "xargs" treating single filenames that
3944 3944 contain whitespace as multiple filenames.
3945 3945
3946 3946 Returns 0 if a match is found, 1 otherwise.
3947 3947 """
3948 3948 end = opts.get('print0') and '\0' or '\n'
3949 3949 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3950 3950
3951 3951 ret = 1
3952 3952 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3953 3953 m.bad = lambda x, y: False
3954 3954 for abs in repo[rev].walk(m):
3955 3955 if not rev and abs not in repo.dirstate:
3956 3956 continue
3957 3957 if opts.get('fullpath'):
3958 3958 ui.write(repo.wjoin(abs), end)
3959 3959 else:
3960 3960 ui.write(((pats and m.rel(abs)) or abs), end)
3961 3961 ret = 0
3962 3962
3963 3963 return ret
3964 3964
3965 3965 @command('^log|history',
3966 3966 [('f', 'follow', None,
3967 3967 _('follow changeset history, or file history across copies and renames')),
3968 3968 ('', 'follow-first', None,
3969 3969 _('only follow the first parent of merge changesets (DEPRECATED)')),
3970 3970 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3971 3971 ('C', 'copies', None, _('show copied files')),
3972 3972 ('k', 'keyword', [],
3973 3973 _('do case-insensitive search for a given text'), _('TEXT')),
3974 3974 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3975 3975 ('', 'removed', None, _('include revisions where files were removed')),
3976 3976 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3977 3977 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3978 3978 ('', 'only-branch', [],
3979 3979 _('show only changesets within the given named branch (DEPRECATED)'),
3980 3980 _('BRANCH')),
3981 3981 ('b', 'branch', [],
3982 3982 _('show changesets within the given named branch'), _('BRANCH')),
3983 3983 ('P', 'prune', [],
3984 3984 _('do not display revision or any of its ancestors'), _('REV')),
3985 3985 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3986 3986 ] + logopts + walkopts,
3987 3987 _('[OPTION]... [FILE]'))
3988 3988 def log(ui, repo, *pats, **opts):
3989 3989 """show revision history of entire repository or files
3990 3990
3991 3991 Print the revision history of the specified files or the entire
3992 3992 project.
3993 3993
3994 3994 If no revision range is specified, the default is ``tip:0`` unless
3995 3995 --follow is set, in which case the working directory parent is
3996 3996 used as the starting revision.
3997 3997
3998 3998 File history is shown without following rename or copy history of
3999 3999 files. Use -f/--follow with a filename to follow history across
4000 4000 renames and copies. --follow without a filename will only show
4001 4001 ancestors or descendants of the starting revision.
4002 4002
4003 4003 By default this command prints revision number and changeset id,
4004 4004 tags, non-trivial parents, user, date and time, and a summary for
4005 4005 each commit. When the -v/--verbose switch is used, the list of
4006 4006 changed files and full commit message are shown.
4007 4007
4008 4008 .. note::
4009 4009 log -p/--patch may generate unexpected diff output for merge
4010 4010 changesets, as it will only compare the merge changeset against
4011 4011 its first parent. Also, only files different from BOTH parents
4012 4012 will appear in files:.
4013 4013
4014 4014 .. note::
4015 4015 for performance reasons, log FILE may omit duplicate changes
4016 4016 made on branches and will not show deletions. To see all
4017 4017 changes including duplicates and deletions, use the --removed
4018 4018 switch.
4019 4019
4020 4020 .. container:: verbose
4021 4021
4022 4022 Some examples:
4023 4023
4024 4024 - changesets with full descriptions and file lists::
4025 4025
4026 4026 hg log -v
4027 4027
4028 4028 - changesets ancestral to the working directory::
4029 4029
4030 4030 hg log -f
4031 4031
4032 4032 - last 10 commits on the current branch::
4033 4033
4034 4034 hg log -l 10 -b .
4035 4035
4036 4036 - changesets showing all modifications of a file, including removals::
4037 4037
4038 4038 hg log --removed file.c
4039 4039
4040 4040 - all changesets that touch a directory, with diffs, excluding merges::
4041 4041
4042 4042 hg log -Mp lib/
4043 4043
4044 4044 - all revision numbers that match a keyword::
4045 4045
4046 4046 hg log -k bug --template "{rev}\\n"
4047 4047
4048 4048 - check if a given changeset is included is a tagged release::
4049 4049
4050 4050 hg log -r "a21ccf and ancestor(1.9)"
4051 4051
4052 4052 - find all changesets by some user in a date range::
4053 4053
4054 4054 hg log -k alice -d "may 2008 to jul 2008"
4055 4055
4056 4056 - summary of all changesets after the last tag::
4057 4057
4058 4058 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4059 4059
4060 4060 See :hg:`help dates` for a list of formats valid for -d/--date.
4061 4061
4062 4062 See :hg:`help revisions` and :hg:`help revsets` for more about
4063 4063 specifying revisions.
4064 4064
4065 4065 See :hg:`help templates` for more about pre-packaged styles and
4066 4066 specifying custom templates.
4067 4067
4068 4068 Returns 0 on success.
4069 4069 """
4070 4070 if opts.get('graph'):
4071 4071 return cmdutil.graphlog(ui, repo, *pats, **opts)
4072 4072
4073 4073 matchfn = scmutil.match(repo[None], pats, opts)
4074 4074 limit = cmdutil.loglimit(opts)
4075 4075 count = 0
4076 4076
4077 4077 getrenamed, endrev = None, None
4078 4078 if opts.get('copies'):
4079 4079 if opts.get('rev'):
4080 4080 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4081 4081 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4082 4082
4083 4083 df = False
4084 4084 if opts.get("date"):
4085 4085 df = util.matchdate(opts["date"])
4086 4086
4087 4087 branches = opts.get('branch', []) + opts.get('only_branch', [])
4088 4088 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4089 4089
4090 4090 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4091 4091 def prep(ctx, fns):
4092 4092 rev = ctx.rev()
4093 4093 parents = [p for p in repo.changelog.parentrevs(rev)
4094 4094 if p != nullrev]
4095 4095 if opts.get('no_merges') and len(parents) == 2:
4096 4096 return
4097 4097 if opts.get('only_merges') and len(parents) != 2:
4098 4098 return
4099 4099 if opts.get('branch') and ctx.branch() not in opts['branch']:
4100 4100 return
4101 4101 if not opts.get('hidden') and ctx.hidden():
4102 4102 return
4103 4103 if df and not df(ctx.date()[0]):
4104 4104 return
4105 4105
4106 4106 lower = encoding.lower
4107 4107 if opts.get('user'):
4108 4108 luser = lower(ctx.user())
4109 4109 for k in [lower(x) for x in opts['user']]:
4110 4110 if (k in luser):
4111 4111 break
4112 4112 else:
4113 4113 return
4114 4114 if opts.get('keyword'):
4115 4115 luser = lower(ctx.user())
4116 4116 ldesc = lower(ctx.description())
4117 4117 lfiles = lower(" ".join(ctx.files()))
4118 4118 for k in [lower(x) for x in opts['keyword']]:
4119 4119 if (k in luser or k in ldesc or k in lfiles):
4120 4120 break
4121 4121 else:
4122 4122 return
4123 4123
4124 4124 copies = None
4125 4125 if getrenamed is not None and rev:
4126 4126 copies = []
4127 4127 for fn in ctx.files():
4128 4128 rename = getrenamed(fn, rev)
4129 4129 if rename:
4130 4130 copies.append((fn, rename[0]))
4131 4131
4132 4132 revmatchfn = None
4133 4133 if opts.get('patch') or opts.get('stat'):
4134 4134 if opts.get('follow') or opts.get('follow_first'):
4135 4135 # note: this might be wrong when following through merges
4136 4136 revmatchfn = scmutil.match(repo[None], fns, default='path')
4137 4137 else:
4138 4138 revmatchfn = matchfn
4139 4139
4140 4140 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4141 4141
4142 4142 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4143 4143 if count == limit:
4144 4144 break
4145 4145 if displayer.flush(ctx.rev()):
4146 4146 count += 1
4147 4147 displayer.close()
4148 4148
4149 4149 @command('manifest',
4150 4150 [('r', 'rev', '', _('revision to display'), _('REV')),
4151 4151 ('', 'all', False, _("list files from all revisions"))],
4152 4152 _('[-r REV]'))
4153 4153 def manifest(ui, repo, node=None, rev=None, **opts):
4154 4154 """output the current or given revision of the project manifest
4155 4155
4156 4156 Print a list of version controlled files for the given revision.
4157 4157 If no revision is given, the first parent of the working directory
4158 4158 is used, or the null revision if no revision is checked out.
4159 4159
4160 4160 With -v, print file permissions, symlink and executable bits.
4161 4161 With --debug, print file revision hashes.
4162 4162
4163 4163 If option --all is specified, the list of all files from all revisions
4164 4164 is printed. This includes deleted and renamed files.
4165 4165
4166 4166 Returns 0 on success.
4167 4167 """
4168 4168 if opts.get('all'):
4169 4169 if rev or node:
4170 4170 raise util.Abort(_("can't specify a revision with --all"))
4171 4171
4172 4172 res = []
4173 4173 prefix = "data/"
4174 4174 suffix = ".i"
4175 4175 plen = len(prefix)
4176 4176 slen = len(suffix)
4177 4177 lock = repo.lock()
4178 4178 try:
4179 4179 for fn, b, size in repo.store.datafiles():
4180 4180 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4181 4181 res.append(fn[plen:-slen])
4182 4182 finally:
4183 4183 lock.release()
4184 4184 for f in res:
4185 4185 ui.write("%s\n" % f)
4186 4186 return
4187 4187
4188 4188 if rev and node:
4189 4189 raise util.Abort(_("please specify just one revision"))
4190 4190
4191 4191 if not node:
4192 4192 node = rev
4193 4193
4194 4194 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4195 4195 ctx = scmutil.revsingle(repo, node)
4196 4196 for f in ctx:
4197 4197 if ui.debugflag:
4198 4198 ui.write("%40s " % hex(ctx.manifest()[f]))
4199 4199 if ui.verbose:
4200 4200 ui.write(decor[ctx.flags(f)])
4201 4201 ui.write("%s\n" % f)
4202 4202
4203 4203 @command('^merge',
4204 4204 [('f', 'force', None, _('force a merge with outstanding changes')),
4205 4205 ('r', 'rev', '', _('revision to merge'), _('REV')),
4206 4206 ('P', 'preview', None,
4207 4207 _('review revisions to merge (no merge is performed)'))
4208 4208 ] + mergetoolopts,
4209 4209 _('[-P] [-f] [[-r] REV]'))
4210 4210 def merge(ui, repo, node=None, **opts):
4211 4211 """merge working directory with another revision
4212 4212
4213 4213 The current working directory is updated with all changes made in
4214 4214 the requested revision since the last common predecessor revision.
4215 4215
4216 4216 Files that changed between either parent are marked as changed for
4217 4217 the next commit and a commit must be performed before any further
4218 4218 updates to the repository are allowed. The next commit will have
4219 4219 two parents.
4220 4220
4221 4221 ``--tool`` can be used to specify the merge tool used for file
4222 4222 merges. It overrides the HGMERGE environment variable and your
4223 4223 configuration files. See :hg:`help merge-tools` for options.
4224 4224
4225 4225 If no revision is specified, the working directory's parent is a
4226 4226 head revision, and the current branch contains exactly one other
4227 4227 head, the other head is merged with by default. Otherwise, an
4228 4228 explicit revision with which to merge with must be provided.
4229 4229
4230 4230 :hg:`resolve` must be used to resolve unresolved files.
4231 4231
4232 4232 To undo an uncommitted merge, use :hg:`update --clean .` which
4233 4233 will check out a clean copy of the original merge parent, losing
4234 4234 all changes.
4235 4235
4236 4236 Returns 0 on success, 1 if there are unresolved files.
4237 4237 """
4238 4238
4239 4239 if opts.get('rev') and node:
4240 4240 raise util.Abort(_("please specify just one revision"))
4241 4241 if not node:
4242 4242 node = opts.get('rev')
4243 4243
4244 4244 if node:
4245 4245 node = scmutil.revsingle(repo, node).node()
4246 4246
4247 4247 if not node and repo._bookmarkcurrent:
4248 4248 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4249 4249 curhead = repo[repo._bookmarkcurrent]
4250 4250 if len(bmheads) == 2:
4251 4251 if curhead == bmheads[0]:
4252 4252 node = bmheads[1]
4253 4253 else:
4254 4254 node = bmheads[0]
4255 4255 elif len(bmheads) > 2:
4256 4256 raise util.Abort(_("multiple matching bookmarks to merge - "
4257 4257 "please merge with an explicit rev or bookmark"),
4258 4258 hint=_("run 'hg heads' to see all heads"))
4259 4259 elif len(bmheads) <= 1:
4260 4260 raise util.Abort(_("no matching bookmark to merge - "
4261 4261 "please merge with an explicit rev or bookmark"),
4262 4262 hint=_("run 'hg heads' to see all heads"))
4263 4263
4264 4264 if not node and not repo._bookmarkcurrent:
4265 4265 branch = repo[None].branch()
4266 4266 bheads = repo.branchheads(branch)
4267 4267 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4268 4268
4269 4269 if len(nbhs) > 2:
4270 4270 raise util.Abort(_("branch '%s' has %d heads - "
4271 4271 "please merge with an explicit rev")
4272 4272 % (branch, len(bheads)),
4273 4273 hint=_("run 'hg heads .' to see heads"))
4274 4274
4275 4275 parent = repo.dirstate.p1()
4276 4276 if len(nbhs) <= 1:
4277 4277 if len(bheads) > 1:
4278 4278 raise util.Abort(_("heads are bookmarked - "
4279 4279 "please merge with an explicit rev"),
4280 4280 hint=_("run 'hg heads' to see all heads"))
4281 4281 if len(repo.heads()) > 1:
4282 4282 raise util.Abort(_("branch '%s' has one head - "
4283 4283 "please merge with an explicit rev")
4284 4284 % branch,
4285 4285 hint=_("run 'hg heads' to see all heads"))
4286 4286 msg, hint = _('nothing to merge'), None
4287 4287 if parent != repo.lookup(branch):
4288 4288 hint = _("use 'hg update' instead")
4289 4289 raise util.Abort(msg, hint=hint)
4290 4290
4291 4291 if parent not in bheads:
4292 4292 raise util.Abort(_('working directory not at a head revision'),
4293 4293 hint=_("use 'hg update' or merge with an "
4294 4294 "explicit revision"))
4295 4295 if parent == nbhs[0]:
4296 4296 node = nbhs[-1]
4297 4297 else:
4298 4298 node = nbhs[0]
4299 4299
4300 4300 if opts.get('preview'):
4301 4301 # find nodes that are ancestors of p2 but not of p1
4302 4302 p1 = repo.lookup('.')
4303 4303 p2 = repo.lookup(node)
4304 4304 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4305 4305
4306 4306 displayer = cmdutil.show_changeset(ui, repo, opts)
4307 4307 for node in nodes:
4308 4308 displayer.show(repo[node])
4309 4309 displayer.close()
4310 4310 return 0
4311 4311
4312 4312 try:
4313 4313 # ui.forcemerge is an internal variable, do not document
4314 4314 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4315 4315 return hg.merge(repo, node, force=opts.get('force'))
4316 4316 finally:
4317 4317 ui.setconfig('ui', 'forcemerge', '')
4318 4318
4319 4319 @command('outgoing|out',
4320 4320 [('f', 'force', None, _('run even when the destination is unrelated')),
4321 4321 ('r', 'rev', [],
4322 4322 _('a changeset intended to be included in the destination'), _('REV')),
4323 4323 ('n', 'newest-first', None, _('show newest record first')),
4324 4324 ('B', 'bookmarks', False, _('compare bookmarks')),
4325 4325 ('b', 'branch', [], _('a specific branch you would like to push'),
4326 4326 _('BRANCH')),
4327 4327 ] + logopts + remoteopts + subrepoopts,
4328 4328 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4329 4329 def outgoing(ui, repo, dest=None, **opts):
4330 4330 """show changesets not found in the destination
4331 4331
4332 4332 Show changesets not found in the specified destination repository
4333 4333 or the default push location. These are the changesets that would
4334 4334 be pushed if a push was requested.
4335 4335
4336 4336 See pull for details of valid destination formats.
4337 4337
4338 4338 Returns 0 if there are outgoing changes, 1 otherwise.
4339 4339 """
4340 4340 if opts.get('graph'):
4341 4341 cmdutil.checkunsupportedgraphflags([], opts)
4342 4342 o = hg._outgoing(ui, repo, dest, opts)
4343 4343 if o is None:
4344 4344 return
4345 4345
4346 4346 revdag = cmdutil.graphrevs(repo, o, opts)
4347 4347 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4348 4348 showparents = [ctx.node() for ctx in repo[None].parents()]
4349 4349 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4350 4350 graphmod.asciiedges)
4351 4351 return 0
4352 4352
4353 4353 if opts.get('bookmarks'):
4354 4354 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4355 4355 dest, branches = hg.parseurl(dest, opts.get('branch'))
4356 4356 other = hg.peer(repo, opts, dest)
4357 4357 if 'bookmarks' not in other.listkeys('namespaces'):
4358 4358 ui.warn(_("remote doesn't support bookmarks\n"))
4359 4359 return 0
4360 4360 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4361 4361 return bookmarks.diff(ui, other, repo)
4362 4362
4363 4363 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4364 4364 try:
4365 4365 return hg.outgoing(ui, repo, dest, opts)
4366 4366 finally:
4367 4367 del repo._subtoppath
4368 4368
4369 4369 @command('parents',
4370 4370 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4371 4371 ] + templateopts,
4372 4372 _('[-r REV] [FILE]'))
4373 4373 def parents(ui, repo, file_=None, **opts):
4374 4374 """show the parents of the working directory or revision
4375 4375
4376 4376 Print the working directory's parent revisions. If a revision is
4377 4377 given via -r/--rev, the parent of that revision will be printed.
4378 4378 If a file argument is given, the revision in which the file was
4379 4379 last changed (before the working directory revision or the
4380 4380 argument to --rev if given) is printed.
4381 4381
4382 4382 Returns 0 on success.
4383 4383 """
4384 4384
4385 4385 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4386 4386
4387 4387 if file_:
4388 4388 m = scmutil.match(ctx, (file_,), opts)
4389 4389 if m.anypats() or len(m.files()) != 1:
4390 4390 raise util.Abort(_('can only specify an explicit filename'))
4391 4391 file_ = m.files()[0]
4392 4392 filenodes = []
4393 4393 for cp in ctx.parents():
4394 4394 if not cp:
4395 4395 continue
4396 4396 try:
4397 4397 filenodes.append(cp.filenode(file_))
4398 4398 except error.LookupError:
4399 4399 pass
4400 4400 if not filenodes:
4401 4401 raise util.Abort(_("'%s' not found in manifest!") % file_)
4402 4402 fl = repo.file(file_)
4403 4403 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4404 4404 else:
4405 4405 p = [cp.node() for cp in ctx.parents()]
4406 4406
4407 4407 displayer = cmdutil.show_changeset(ui, repo, opts)
4408 4408 for n in p:
4409 4409 if n != nullid:
4410 4410 displayer.show(repo[n])
4411 4411 displayer.close()
4412 4412
4413 4413 @command('paths', [], _('[NAME]'))
4414 4414 def paths(ui, repo, search=None):
4415 4415 """show aliases for remote repositories
4416 4416
4417 4417 Show definition of symbolic path name NAME. If no name is given,
4418 4418 show definition of all available names.
4419 4419
4420 4420 Option -q/--quiet suppresses all output when searching for NAME
4421 4421 and shows only the path names when listing all definitions.
4422 4422
4423 4423 Path names are defined in the [paths] section of your
4424 4424 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4425 4425 repository, ``.hg/hgrc`` is used, too.
4426 4426
4427 4427 The path names ``default`` and ``default-push`` have a special
4428 4428 meaning. When performing a push or pull operation, they are used
4429 4429 as fallbacks if no location is specified on the command-line.
4430 4430 When ``default-push`` is set, it will be used for push and
4431 4431 ``default`` will be used for pull; otherwise ``default`` is used
4432 4432 as the fallback for both. When cloning a repository, the clone
4433 4433 source is written as ``default`` in ``.hg/hgrc``. Note that
4434 4434 ``default`` and ``default-push`` apply to all inbound (e.g.
4435 4435 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4436 4436 :hg:`bundle`) operations.
4437 4437
4438 4438 See :hg:`help urls` for more information.
4439 4439
4440 4440 Returns 0 on success.
4441 4441 """
4442 4442 if search:
4443 4443 for name, path in ui.configitems("paths"):
4444 4444 if name == search:
4445 4445 ui.status("%s\n" % util.hidepassword(path))
4446 4446 return
4447 4447 if not ui.quiet:
4448 4448 ui.warn(_("not found!\n"))
4449 4449 return 1
4450 4450 else:
4451 4451 for name, path in ui.configitems("paths"):
4452 4452 if ui.quiet:
4453 4453 ui.write("%s\n" % name)
4454 4454 else:
4455 4455 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4456 4456
4457 4457 @command('^phase',
4458 4458 [('p', 'public', False, _('set changeset phase to public')),
4459 4459 ('d', 'draft', False, _('set changeset phase to draft')),
4460 4460 ('s', 'secret', False, _('set changeset phase to secret')),
4461 4461 ('f', 'force', False, _('allow to move boundary backward')),
4462 4462 ('r', 'rev', [], _('target revision'), _('REV')),
4463 4463 ],
4464 4464 _('[-p|-d|-s] [-f] [-r] REV...'))
4465 4465 def phase(ui, repo, *revs, **opts):
4466 4466 """set or show the current phase name
4467 4467
4468 4468 With no argument, show the phase name of specified revisions.
4469 4469
4470 4470 With one of -p/--public, -d/--draft or -s/--secret, change the
4471 4471 phase value of the specified revisions.
4472 4472
4473 4473 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4474 4474 lower phase to an higher phase. Phases are ordered as follows::
4475 4475
4476 4476 public < draft < secret
4477 4477
4478 4478 Return 0 on success, 1 if no phases were changed or some could not
4479 4479 be changed.
4480 4480 """
4481 4481 # search for a unique phase argument
4482 4482 targetphase = None
4483 4483 for idx, name in enumerate(phases.phasenames):
4484 4484 if opts[name]:
4485 4485 if targetphase is not None:
4486 4486 raise util.Abort(_('only one phase can be specified'))
4487 4487 targetphase = idx
4488 4488
4489 4489 # look for specified revision
4490 4490 revs = list(revs)
4491 4491 revs.extend(opts['rev'])
4492 4492 if not revs:
4493 4493 raise util.Abort(_('no revisions specified'))
4494 4494
4495 4495 revs = scmutil.revrange(repo, revs)
4496 4496
4497 4497 lock = None
4498 4498 ret = 0
4499 4499 if targetphase is None:
4500 4500 # display
4501 4501 for r in revs:
4502 4502 ctx = repo[r]
4503 4503 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4504 4504 else:
4505 4505 lock = repo.lock()
4506 4506 try:
4507 4507 # set phase
4508 4508 if not revs:
4509 4509 raise util.Abort(_('empty revision set'))
4510 4510 nodes = [repo[r].node() for r in revs]
4511 4511 olddata = repo._phasecache.getphaserevs(repo)[:]
4512 4512 phases.advanceboundary(repo, targetphase, nodes)
4513 4513 if opts['force']:
4514 4514 phases.retractboundary(repo, targetphase, nodes)
4515 4515 finally:
4516 4516 lock.release()
4517 4517 newdata = repo._phasecache.getphaserevs(repo)
4518 4518 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4519 4519 rejected = [n for n in nodes
4520 4520 if newdata[repo[n].rev()] < targetphase]
4521 4521 if rejected:
4522 4522 ui.warn(_('cannot move %i changesets to a more permissive '
4523 4523 'phase, use --force\n') % len(rejected))
4524 4524 ret = 1
4525 4525 if changes:
4526 4526 msg = _('phase changed for %i changesets\n') % changes
4527 4527 if ret:
4528 4528 ui.status(msg)
4529 4529 else:
4530 4530 ui.note(msg)
4531 4531 else:
4532 4532 ui.warn(_('no phases changed\n'))
4533 4533 ret = 1
4534 4534 return ret
4535 4535
4536 4536 def postincoming(ui, repo, modheads, optupdate, checkout):
4537 4537 if modheads == 0:
4538 4538 return
4539 4539 if optupdate:
4540 4540 movemarkfrom = repo['.'].node()
4541 4541 try:
4542 4542 ret = hg.update(repo, checkout)
4543 4543 except util.Abort, inst:
4544 4544 ui.warn(_("not updating: %s\n") % str(inst))
4545 4545 return 0
4546 4546 if not ret and not checkout:
4547 4547 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4548 4548 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4549 4549 return ret
4550 4550 if modheads > 1:
4551 4551 currentbranchheads = len(repo.branchheads())
4552 4552 if currentbranchheads == modheads:
4553 4553 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4554 4554 elif currentbranchheads > 1:
4555 4555 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4556 4556 "merge)\n"))
4557 4557 else:
4558 4558 ui.status(_("(run 'hg heads' to see heads)\n"))
4559 4559 else:
4560 4560 ui.status(_("(run 'hg update' to get a working copy)\n"))
4561 4561
4562 4562 @command('^pull',
4563 4563 [('u', 'update', None,
4564 4564 _('update to new branch head if changesets were pulled')),
4565 4565 ('f', 'force', None, _('run even when remote repository is unrelated')),
4566 4566 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4567 4567 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4568 4568 ('b', 'branch', [], _('a specific branch you would like to pull'),
4569 4569 _('BRANCH')),
4570 4570 ] + remoteopts,
4571 4571 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4572 4572 def pull(ui, repo, source="default", **opts):
4573 4573 """pull changes from the specified source
4574 4574
4575 4575 Pull changes from a remote repository to a local one.
4576 4576
4577 4577 This finds all changes from the repository at the specified path
4578 4578 or URL and adds them to a local repository (the current one unless
4579 4579 -R is specified). By default, this does not update the copy of the
4580 4580 project in the working directory.
4581 4581
4582 4582 Use :hg:`incoming` if you want to see what would have been added
4583 4583 by a pull at the time you issued this command. If you then decide
4584 4584 to add those changes to the repository, you should use :hg:`pull
4585 4585 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4586 4586
4587 4587 If SOURCE is omitted, the 'default' path will be used.
4588 4588 See :hg:`help urls` for more information.
4589 4589
4590 4590 Returns 0 on success, 1 if an update had unresolved files.
4591 4591 """
4592 4592 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4593 4593 other = hg.peer(repo, opts, source)
4594 4594 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4595 4595 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4596 4596
4597 4597 if opts.get('bookmark'):
4598 4598 if not revs:
4599 4599 revs = []
4600 4600 rb = other.listkeys('bookmarks')
4601 4601 for b in opts['bookmark']:
4602 4602 if b not in rb:
4603 4603 raise util.Abort(_('remote bookmark %s not found!') % b)
4604 4604 revs.append(rb[b])
4605 4605
4606 4606 if revs:
4607 4607 try:
4608 4608 revs = [other.lookup(rev) for rev in revs]
4609 4609 except error.CapabilityError:
4610 4610 err = _("other repository doesn't support revision lookup, "
4611 4611 "so a rev cannot be specified.")
4612 4612 raise util.Abort(err)
4613 4613
4614 4614 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4615 4615 bookmarks.updatefromremote(ui, repo, other, source)
4616 4616 if checkout:
4617 4617 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4618 4618 repo._subtoppath = source
4619 4619 try:
4620 4620 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4621 4621
4622 4622 finally:
4623 4623 del repo._subtoppath
4624 4624
4625 4625 # update specified bookmarks
4626 4626 if opts.get('bookmark'):
4627 4627 for b in opts['bookmark']:
4628 4628 # explicit pull overrides local bookmark if any
4629 4629 ui.status(_("importing bookmark %s\n") % b)
4630 4630 repo._bookmarks[b] = repo[rb[b]].node()
4631 4631 bookmarks.write(repo)
4632 4632
4633 4633 return ret
4634 4634
4635 4635 @command('^push',
4636 4636 [('f', 'force', None, _('force push')),
4637 4637 ('r', 'rev', [],
4638 4638 _('a changeset intended to be included in the destination'),
4639 4639 _('REV')),
4640 4640 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4641 4641 ('b', 'branch', [],
4642 4642 _('a specific branch you would like to push'), _('BRANCH')),
4643 4643 ('', 'new-branch', False, _('allow pushing a new branch')),
4644 4644 ] + remoteopts,
4645 4645 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4646 4646 def push(ui, repo, dest=None, **opts):
4647 4647 """push changes to the specified destination
4648 4648
4649 4649 Push changesets from the local repository to the specified
4650 4650 destination.
4651 4651
4652 4652 This operation is symmetrical to pull: it is identical to a pull
4653 4653 in the destination repository from the current one.
4654 4654
4655 4655 By default, push will not allow creation of new heads at the
4656 4656 destination, since multiple heads would make it unclear which head
4657 4657 to use. In this situation, it is recommended to pull and merge
4658 4658 before pushing.
4659 4659
4660 4660 Use --new-branch if you want to allow push to create a new named
4661 4661 branch that is not present at the destination. This allows you to
4662 4662 only create a new branch without forcing other changes.
4663 4663
4664 4664 Use -f/--force to override the default behavior and push all
4665 4665 changesets on all branches.
4666 4666
4667 4667 If -r/--rev is used, the specified revision and all its ancestors
4668 4668 will be pushed to the remote repository.
4669 4669
4670 4670 If -B/--bookmark is used, the specified bookmarked revision, its
4671 4671 ancestors, and the bookmark will be pushed to the remote
4672 4672 repository.
4673 4673
4674 4674 Please see :hg:`help urls` for important details about ``ssh://``
4675 4675 URLs. If DESTINATION is omitted, a default path will be used.
4676 4676
4677 4677 Returns 0 if push was successful, 1 if nothing to push.
4678 4678 """
4679 4679
4680 4680 if opts.get('bookmark'):
4681 4681 for b in opts['bookmark']:
4682 4682 # translate -B options to -r so changesets get pushed
4683 4683 if b in repo._bookmarks:
4684 4684 opts.setdefault('rev', []).append(b)
4685 4685 else:
4686 4686 # if we try to push a deleted bookmark, translate it to null
4687 4687 # this lets simultaneous -r, -b options continue working
4688 4688 opts.setdefault('rev', []).append("null")
4689 4689
4690 4690 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4691 4691 dest, branches = hg.parseurl(dest, opts.get('branch'))
4692 4692 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4693 4693 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4694 4694 other = hg.peer(repo, opts, dest)
4695 4695 if revs:
4696 4696 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4697 4697
4698 4698 repo._subtoppath = dest
4699 4699 try:
4700 4700 # push subrepos depth-first for coherent ordering
4701 4701 c = repo['']
4702 4702 subs = c.substate # only repos that are committed
4703 4703 for s in sorted(subs):
4704 4704 if c.sub(s).push(opts) == 0:
4705 4705 return False
4706 4706 finally:
4707 4707 del repo._subtoppath
4708 4708 result = repo.push(other, opts.get('force'), revs=revs,
4709 4709 newbranch=opts.get('new_branch'))
4710 4710
4711 4711 result = not result
4712 4712
4713 4713 if opts.get('bookmark'):
4714 4714 rb = other.listkeys('bookmarks')
4715 4715 for b in opts['bookmark']:
4716 4716 # explicit push overrides remote bookmark if any
4717 4717 if b in repo._bookmarks:
4718 4718 ui.status(_("exporting bookmark %s\n") % b)
4719 4719 new = repo[b].hex()
4720 4720 elif b in rb:
4721 4721 ui.status(_("deleting remote bookmark %s\n") % b)
4722 4722 new = '' # delete
4723 4723 else:
4724 4724 ui.warn(_('bookmark %s does not exist on the local '
4725 4725 'or remote repository!\n') % b)
4726 4726 return 2
4727 4727 old = rb.get(b, '')
4728 4728 r = other.pushkey('bookmarks', b, old, new)
4729 4729 if not r:
4730 4730 ui.warn(_('updating bookmark %s failed!\n') % b)
4731 4731 if not result:
4732 4732 result = 2
4733 4733
4734 4734 return result
4735 4735
4736 4736 @command('recover', [])
4737 4737 def recover(ui, repo):
4738 4738 """roll back an interrupted transaction
4739 4739
4740 4740 Recover from an interrupted commit or pull.
4741 4741
4742 4742 This command tries to fix the repository status after an
4743 4743 interrupted operation. It should only be necessary when Mercurial
4744 4744 suggests it.
4745 4745
4746 4746 Returns 0 if successful, 1 if nothing to recover or verify fails.
4747 4747 """
4748 4748 if repo.recover():
4749 4749 return hg.verify(repo)
4750 4750 return 1
4751 4751
4752 4752 @command('^remove|rm',
4753 4753 [('A', 'after', None, _('record delete for missing files')),
4754 4754 ('f', 'force', None,
4755 4755 _('remove (and delete) file even if added or modified')),
4756 4756 ] + walkopts,
4757 4757 _('[OPTION]... FILE...'))
4758 4758 def remove(ui, repo, *pats, **opts):
4759 4759 """remove the specified files on the next commit
4760 4760
4761 4761 Schedule the indicated files for removal from the current branch.
4762 4762
4763 4763 This command schedules the files to be removed at the next commit.
4764 4764 To undo a remove before that, see :hg:`revert`. To undo added
4765 4765 files, see :hg:`forget`.
4766 4766
4767 4767 .. container:: verbose
4768 4768
4769 4769 -A/--after can be used to remove only files that have already
4770 4770 been deleted, -f/--force can be used to force deletion, and -Af
4771 4771 can be used to remove files from the next revision without
4772 4772 deleting them from the working directory.
4773 4773
4774 4774 The following table details the behavior of remove for different
4775 4775 file states (columns) and option combinations (rows). The file
4776 4776 states are Added [A], Clean [C], Modified [M] and Missing [!]
4777 4777 (as reported by :hg:`status`). The actions are Warn, Remove
4778 4778 (from branch) and Delete (from disk):
4779 4779
4780 4780 ======= == == == ==
4781 4781 A C M !
4782 4782 ======= == == == ==
4783 4783 none W RD W R
4784 4784 -f R RD RD R
4785 4785 -A W W W R
4786 4786 -Af R R R R
4787 4787 ======= == == == ==
4788 4788
4789 4789 Note that remove never deletes files in Added [A] state from the
4790 4790 working directory, not even if option --force is specified.
4791 4791
4792 4792 Returns 0 on success, 1 if any warnings encountered.
4793 4793 """
4794 4794
4795 4795 ret = 0
4796 4796 after, force = opts.get('after'), opts.get('force')
4797 4797 if not pats and not after:
4798 4798 raise util.Abort(_('no files specified'))
4799 4799
4800 4800 m = scmutil.match(repo[None], pats, opts)
4801 4801 s = repo.status(match=m, clean=True)
4802 4802 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4803 4803
4804 4804 for f in m.files():
4805 4805 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4806 4806 if os.path.exists(m.rel(f)):
4807 4807 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4808 4808 ret = 1
4809 4809
4810 4810 if force:
4811 4811 list = modified + deleted + clean + added
4812 4812 elif after:
4813 4813 list = deleted
4814 4814 for f in modified + added + clean:
4815 4815 ui.warn(_('not removing %s: file still exists (use -f'
4816 4816 ' to force removal)\n') % m.rel(f))
4817 4817 ret = 1
4818 4818 else:
4819 4819 list = deleted + clean
4820 4820 for f in modified:
4821 4821 ui.warn(_('not removing %s: file is modified (use -f'
4822 4822 ' to force removal)\n') % m.rel(f))
4823 4823 ret = 1
4824 4824 for f in added:
4825 4825 ui.warn(_('not removing %s: file has been marked for add'
4826 4826 ' (use forget to undo)\n') % m.rel(f))
4827 4827 ret = 1
4828 4828
4829 4829 for f in sorted(list):
4830 4830 if ui.verbose or not m.exact(f):
4831 4831 ui.status(_('removing %s\n') % m.rel(f))
4832 4832
4833 4833 wlock = repo.wlock()
4834 4834 try:
4835 4835 if not after:
4836 4836 for f in list:
4837 4837 if f in added:
4838 4838 continue # we never unlink added files on remove
4839 4839 try:
4840 4840 util.unlinkpath(repo.wjoin(f))
4841 4841 except OSError, inst:
4842 4842 if inst.errno != errno.ENOENT:
4843 4843 raise
4844 4844 repo[None].forget(list)
4845 4845 finally:
4846 4846 wlock.release()
4847 4847
4848 4848 return ret
4849 4849
4850 4850 @command('rename|move|mv',
4851 4851 [('A', 'after', None, _('record a rename that has already occurred')),
4852 4852 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4853 4853 ] + walkopts + dryrunopts,
4854 4854 _('[OPTION]... SOURCE... DEST'))
4855 4855 def rename(ui, repo, *pats, **opts):
4856 4856 """rename files; equivalent of copy + remove
4857 4857
4858 4858 Mark dest as copies of sources; mark sources for deletion. If dest
4859 4859 is a directory, copies are put in that directory. If dest is a
4860 4860 file, there can only be one source.
4861 4861
4862 4862 By default, this command copies the contents of files as they
4863 4863 exist in the working directory. If invoked with -A/--after, the
4864 4864 operation is recorded, but no copying is performed.
4865 4865
4866 4866 This command takes effect at the next commit. To undo a rename
4867 4867 before that, see :hg:`revert`.
4868 4868
4869 4869 Returns 0 on success, 1 if errors are encountered.
4870 4870 """
4871 4871 wlock = repo.wlock(False)
4872 4872 try:
4873 4873 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4874 4874 finally:
4875 4875 wlock.release()
4876 4876
4877 4877 @command('resolve',
4878 4878 [('a', 'all', None, _('select all unresolved files')),
4879 4879 ('l', 'list', None, _('list state of files needing merge')),
4880 4880 ('m', 'mark', None, _('mark files as resolved')),
4881 4881 ('u', 'unmark', None, _('mark files as unresolved')),
4882 4882 ('n', 'no-status', None, _('hide status prefix'))]
4883 4883 + mergetoolopts + walkopts,
4884 4884 _('[OPTION]... [FILE]...'))
4885 4885 def resolve(ui, repo, *pats, **opts):
4886 4886 """redo merges or set/view the merge status of files
4887 4887
4888 4888 Merges with unresolved conflicts are often the result of
4889 4889 non-interactive merging using the ``internal:merge`` configuration
4890 4890 setting, or a command-line merge tool like ``diff3``. The resolve
4891 4891 command is used to manage the files involved in a merge, after
4892 4892 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4893 4893 working directory must have two parents). See :hg:`help
4894 4894 merge-tools` for information on configuring merge tools.
4895 4895
4896 4896 The resolve command can be used in the following ways:
4897 4897
4898 4898 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4899 4899 files, discarding any previous merge attempts. Re-merging is not
4900 4900 performed for files already marked as resolved. Use ``--all/-a``
4901 4901 to select all unresolved files. ``--tool`` can be used to specify
4902 4902 the merge tool used for the given files. It overrides the HGMERGE
4903 4903 environment variable and your configuration files. Previous file
4904 4904 contents are saved with a ``.orig`` suffix.
4905 4905
4906 4906 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4907 4907 (e.g. after having manually fixed-up the files). The default is
4908 4908 to mark all unresolved files.
4909 4909
4910 4910 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4911 4911 default is to mark all resolved files.
4912 4912
4913 4913 - :hg:`resolve -l`: list files which had or still have conflicts.
4914 4914 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4915 4915
4916 4916 Note that Mercurial will not let you commit files with unresolved
4917 4917 merge conflicts. You must use :hg:`resolve -m ...` before you can
4918 4918 commit after a conflicting merge.
4919 4919
4920 4920 Returns 0 on success, 1 if any files fail a resolve attempt.
4921 4921 """
4922 4922
4923 4923 all, mark, unmark, show, nostatus = \
4924 4924 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4925 4925
4926 4926 if (show and (mark or unmark)) or (mark and unmark):
4927 4927 raise util.Abort(_("too many options specified"))
4928 4928 if pats and all:
4929 4929 raise util.Abort(_("can't specify --all and patterns"))
4930 4930 if not (all or pats or show or mark or unmark):
4931 4931 raise util.Abort(_('no files or directories specified; '
4932 4932 'use --all to remerge all files'))
4933 4933
4934 4934 ms = mergemod.mergestate(repo)
4935 4935 m = scmutil.match(repo[None], pats, opts)
4936 4936 ret = 0
4937 4937
4938 4938 for f in ms:
4939 4939 if m(f):
4940 4940 if show:
4941 4941 if nostatus:
4942 4942 ui.write("%s\n" % f)
4943 4943 else:
4944 4944 ui.write("%s %s\n" % (ms[f].upper(), f),
4945 4945 label='resolve.' +
4946 4946 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4947 4947 elif mark:
4948 4948 ms.mark(f, "r")
4949 4949 elif unmark:
4950 4950 ms.mark(f, "u")
4951 4951 else:
4952 4952 wctx = repo[None]
4953 4953 mctx = wctx.parents()[-1]
4954 4954
4955 4955 # backup pre-resolve (merge uses .orig for its own purposes)
4956 4956 a = repo.wjoin(f)
4957 4957 util.copyfile(a, a + ".resolve")
4958 4958
4959 4959 try:
4960 4960 # resolve file
4961 4961 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4962 4962 if ms.resolve(f, wctx, mctx):
4963 4963 ret = 1
4964 4964 finally:
4965 4965 ui.setconfig('ui', 'forcemerge', '')
4966 4966
4967 4967 # replace filemerge's .orig file with our resolve file
4968 4968 util.rename(a + ".resolve", a + ".orig")
4969 4969
4970 4970 ms.commit()
4971 4971 return ret
4972 4972
4973 4973 @command('revert',
4974 4974 [('a', 'all', None, _('revert all changes when no arguments given')),
4975 4975 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4976 4976 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4977 4977 ('C', 'no-backup', None, _('do not save backup copies of files')),
4978 4978 ] + walkopts + dryrunopts,
4979 4979 _('[OPTION]... [-r REV] [NAME]...'))
4980 4980 def revert(ui, repo, *pats, **opts):
4981 4981 """restore files to their checkout state
4982 4982
4983 4983 .. note::
4984 4984
4985 4985 To check out earlier revisions, you should use :hg:`update REV`.
4986 4986 To cancel an uncommitted merge (and lose your changes), use
4987 4987 :hg:`update --clean .`.
4988 4988
4989 4989 With no revision specified, revert the specified files or directories
4990 4990 to the contents they had in the parent of the working directory.
4991 4991 This restores the contents of files to an unmodified
4992 4992 state and unschedules adds, removes, copies, and renames. If the
4993 4993 working directory has two parents, you must explicitly specify a
4994 4994 revision.
4995 4995
4996 4996 Using the -r/--rev or -d/--date options, revert the given files or
4997 4997 directories to their states as of a specific revision. Because
4998 4998 revert does not change the working directory parents, this will
4999 4999 cause these files to appear modified. This can be helpful to "back
5000 5000 out" some or all of an earlier change. See :hg:`backout` for a
5001 5001 related method.
5002 5002
5003 5003 Modified files are saved with a .orig suffix before reverting.
5004 5004 To disable these backups, use --no-backup.
5005 5005
5006 5006 See :hg:`help dates` for a list of formats valid for -d/--date.
5007 5007
5008 5008 Returns 0 on success.
5009 5009 """
5010 5010
5011 5011 if opts.get("date"):
5012 5012 if opts.get("rev"):
5013 5013 raise util.Abort(_("you can't specify a revision and a date"))
5014 5014 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5015 5015
5016 5016 parent, p2 = repo.dirstate.parents()
5017 5017 if not opts.get('rev') and p2 != nullid:
5018 5018 # revert after merge is a trap for new users (issue2915)
5019 5019 raise util.Abort(_('uncommitted merge with no revision specified'),
5020 5020 hint=_('use "hg update" or see "hg help revert"'))
5021 5021
5022 5022 ctx = scmutil.revsingle(repo, opts.get('rev'))
5023 5023
5024 5024 if not pats and not opts.get('all'):
5025 5025 msg = _("no files or directories specified")
5026 5026 if p2 != nullid:
5027 5027 hint = _("uncommitted merge, use --all to discard all changes,"
5028 5028 " or 'hg update -C .' to abort the merge")
5029 5029 raise util.Abort(msg, hint=hint)
5030 5030 dirty = util.any(repo.status())
5031 5031 node = ctx.node()
5032 5032 if node != parent:
5033 5033 if dirty:
5034 5034 hint = _("uncommitted changes, use --all to discard all"
5035 5035 " changes, or 'hg update %s' to update") % ctx.rev()
5036 5036 else:
5037 5037 hint = _("use --all to revert all files,"
5038 5038 " or 'hg update %s' to update") % ctx.rev()
5039 5039 elif dirty:
5040 5040 hint = _("uncommitted changes, use --all to discard all changes")
5041 5041 else:
5042 5042 hint = _("use --all to revert all files")
5043 5043 raise util.Abort(msg, hint=hint)
5044 5044
5045 5045 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5046 5046
5047 5047 @command('rollback', dryrunopts +
5048 5048 [('f', 'force', False, _('ignore safety measures'))])
5049 5049 def rollback(ui, repo, **opts):
5050 5050 """roll back the last transaction (dangerous)
5051 5051
5052 5052 This command should be used with care. There is only one level of
5053 5053 rollback, and there is no way to undo a rollback. It will also
5054 5054 restore the dirstate at the time of the last transaction, losing
5055 5055 any dirstate changes since that time. This command does not alter
5056 5056 the working directory.
5057 5057
5058 5058 Transactions are used to encapsulate the effects of all commands
5059 5059 that create new changesets or propagate existing changesets into a
5060 5060 repository.
5061 5061
5062 5062 .. container:: verbose
5063 5063
5064 5064 For example, the following commands are transactional, and their
5065 5065 effects can be rolled back:
5066 5066
5067 5067 - commit
5068 5068 - import
5069 5069 - pull
5070 5070 - push (with this repository as the destination)
5071 5071 - unbundle
5072 5072
5073 5073 To avoid permanent data loss, rollback will refuse to rollback a
5074 5074 commit transaction if it isn't checked out. Use --force to
5075 5075 override this protection.
5076 5076
5077 5077 This command is not intended for use on public repositories. Once
5078 5078 changes are visible for pull by other users, rolling a transaction
5079 5079 back locally is ineffective (someone else may already have pulled
5080 5080 the changes). Furthermore, a race is possible with readers of the
5081 5081 repository; for example an in-progress pull from the repository
5082 5082 may fail if a rollback is performed.
5083 5083
5084 5084 Returns 0 on success, 1 if no rollback data is available.
5085 5085 """
5086 5086 return repo.rollback(dryrun=opts.get('dry_run'),
5087 5087 force=opts.get('force'))
5088 5088
5089 5089 @command('root', [])
5090 5090 def root(ui, repo):
5091 5091 """print the root (top) of the current working directory
5092 5092
5093 5093 Print the root directory of the current repository.
5094 5094
5095 5095 Returns 0 on success.
5096 5096 """
5097 5097 ui.write(repo.root + "\n")
5098 5098
5099 5099 @command('^serve',
5100 5100 [('A', 'accesslog', '', _('name of access log file to write to'),
5101 5101 _('FILE')),
5102 5102 ('d', 'daemon', None, _('run server in background')),
5103 5103 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5104 5104 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5105 5105 # use string type, then we can check if something was passed
5106 5106 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5107 5107 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5108 5108 _('ADDR')),
5109 5109 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5110 5110 _('PREFIX')),
5111 5111 ('n', 'name', '',
5112 5112 _('name to show in web pages (default: working directory)'), _('NAME')),
5113 5113 ('', 'web-conf', '',
5114 5114 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5115 5115 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5116 5116 _('FILE')),
5117 5117 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5118 5118 ('', 'stdio', None, _('for remote clients')),
5119 5119 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5120 5120 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5121 5121 ('', 'style', '', _('template style to use'), _('STYLE')),
5122 5122 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5123 5123 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5124 5124 _('[OPTION]...'))
5125 5125 def serve(ui, repo, **opts):
5126 5126 """start stand-alone webserver
5127 5127
5128 5128 Start a local HTTP repository browser and pull server. You can use
5129 5129 this for ad-hoc sharing and browsing of repositories. It is
5130 5130 recommended to use a real web server to serve a repository for
5131 5131 longer periods of time.
5132 5132
5133 5133 Please note that the server does not implement access control.
5134 5134 This means that, by default, anybody can read from the server and
5135 5135 nobody can write to it by default. Set the ``web.allow_push``
5136 5136 option to ``*`` to allow everybody to push to the server. You
5137 5137 should use a real web server if you need to authenticate users.
5138 5138
5139 5139 By default, the server logs accesses to stdout and errors to
5140 5140 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5141 5141 files.
5142 5142
5143 5143 To have the server choose a free port number to listen on, specify
5144 5144 a port number of 0; in this case, the server will print the port
5145 5145 number it uses.
5146 5146
5147 5147 Returns 0 on success.
5148 5148 """
5149 5149
5150 5150 if opts["stdio"] and opts["cmdserver"]:
5151 5151 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5152 5152
5153 5153 def checkrepo():
5154 5154 if repo is None:
5155 5155 raise error.RepoError(_("there is no Mercurial repository here"
5156 5156 " (.hg not found)"))
5157 5157
5158 5158 if opts["stdio"]:
5159 5159 checkrepo()
5160 5160 s = sshserver.sshserver(ui, repo)
5161 5161 s.serve_forever()
5162 5162
5163 5163 if opts["cmdserver"]:
5164 5164 checkrepo()
5165 5165 s = commandserver.server(ui, repo, opts["cmdserver"])
5166 5166 return s.serve()
5167 5167
5168 5168 # this way we can check if something was given in the command-line
5169 5169 if opts.get('port'):
5170 5170 opts['port'] = util.getport(opts.get('port'))
5171 5171
5172 5172 baseui = repo and repo.baseui or ui
5173 5173 optlist = ("name templates style address port prefix ipv6"
5174 5174 " accesslog errorlog certificate encoding")
5175 5175 for o in optlist.split():
5176 5176 val = opts.get(o, '')
5177 5177 if val in (None, ''): # should check against default options instead
5178 5178 continue
5179 5179 baseui.setconfig("web", o, val)
5180 5180 if repo and repo.ui != baseui:
5181 5181 repo.ui.setconfig("web", o, val)
5182 5182
5183 5183 o = opts.get('web_conf') or opts.get('webdir_conf')
5184 5184 if not o:
5185 5185 if not repo:
5186 5186 raise error.RepoError(_("there is no Mercurial repository"
5187 5187 " here (.hg not found)"))
5188 5188 o = repo.root
5189 5189
5190 5190 app = hgweb.hgweb(o, baseui=ui)
5191 5191
5192 5192 class service(object):
5193 5193 def init(self):
5194 5194 util.setsignalhandler()
5195 5195 self.httpd = hgweb.server.create_server(ui, app)
5196 5196
5197 5197 if opts['port'] and not ui.verbose:
5198 5198 return
5199 5199
5200 5200 if self.httpd.prefix:
5201 5201 prefix = self.httpd.prefix.strip('/') + '/'
5202 5202 else:
5203 5203 prefix = ''
5204 5204
5205 5205 port = ':%d' % self.httpd.port
5206 5206 if port == ':80':
5207 5207 port = ''
5208 5208
5209 5209 bindaddr = self.httpd.addr
5210 5210 if bindaddr == '0.0.0.0':
5211 5211 bindaddr = '*'
5212 5212 elif ':' in bindaddr: # IPv6
5213 5213 bindaddr = '[%s]' % bindaddr
5214 5214
5215 5215 fqaddr = self.httpd.fqaddr
5216 5216 if ':' in fqaddr:
5217 5217 fqaddr = '[%s]' % fqaddr
5218 5218 if opts['port']:
5219 5219 write = ui.status
5220 5220 else:
5221 5221 write = ui.write
5222 5222 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5223 5223 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5224 5224
5225 5225 def run(self):
5226 5226 self.httpd.serve_forever()
5227 5227
5228 5228 service = service()
5229 5229
5230 5230 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5231 5231
5232 5232 @command('showconfig|debugconfig',
5233 5233 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5234 5234 _('[-u] [NAME]...'))
5235 5235 def showconfig(ui, repo, *values, **opts):
5236 5236 """show combined config settings from all hgrc files
5237 5237
5238 5238 With no arguments, print names and values of all config items.
5239 5239
5240 5240 With one argument of the form section.name, print just the value
5241 5241 of that config item.
5242 5242
5243 5243 With multiple arguments, print names and values of all config
5244 5244 items with matching section names.
5245 5245
5246 5246 With --debug, the source (filename and line number) is printed
5247 5247 for each config item.
5248 5248
5249 5249 Returns 0 on success.
5250 5250 """
5251 5251
5252 5252 for f in scmutil.rcpath():
5253 5253 ui.debug('read config from: %s\n' % f)
5254 5254 untrusted = bool(opts.get('untrusted'))
5255 5255 if values:
5256 5256 sections = [v for v in values if '.' not in v]
5257 5257 items = [v for v in values if '.' in v]
5258 5258 if len(items) > 1 or items and sections:
5259 5259 raise util.Abort(_('only one config item permitted'))
5260 5260 for section, name, value in ui.walkconfig(untrusted=untrusted):
5261 5261 value = str(value).replace('\n', '\\n')
5262 5262 sectname = section + '.' + name
5263 5263 if values:
5264 5264 for v in values:
5265 5265 if v == section:
5266 5266 ui.debug('%s: ' %
5267 5267 ui.configsource(section, name, untrusted))
5268 5268 ui.write('%s=%s\n' % (sectname, value))
5269 5269 elif v == sectname:
5270 5270 ui.debug('%s: ' %
5271 5271 ui.configsource(section, name, untrusted))
5272 5272 ui.write(value, '\n')
5273 5273 else:
5274 5274 ui.debug('%s: ' %
5275 5275 ui.configsource(section, name, untrusted))
5276 5276 ui.write('%s=%s\n' % (sectname, value))
5277 5277
5278 5278 @command('^status|st',
5279 5279 [('A', 'all', None, _('show status of all files')),
5280 5280 ('m', 'modified', None, _('show only modified files')),
5281 5281 ('a', 'added', None, _('show only added files')),
5282 5282 ('r', 'removed', None, _('show only removed files')),
5283 5283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5284 5284 ('c', 'clean', None, _('show only files without changes')),
5285 5285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5286 5286 ('i', 'ignored', None, _('show only ignored files')),
5287 5287 ('n', 'no-status', None, _('hide status prefix')),
5288 5288 ('C', 'copies', None, _('show source of copied files')),
5289 5289 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5290 5290 ('', 'rev', [], _('show difference from revision'), _('REV')),
5291 5291 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5292 5292 ] + walkopts + subrepoopts,
5293 5293 _('[OPTION]... [FILE]...'))
5294 5294 def status(ui, repo, *pats, **opts):
5295 5295 """show changed files in the working directory
5296 5296
5297 5297 Show status of files in the repository. If names are given, only
5298 5298 files that match are shown. Files that are clean or ignored or
5299 5299 the source of a copy/move operation, are not listed unless
5300 5300 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5301 5301 Unless options described with "show only ..." are given, the
5302 5302 options -mardu are used.
5303 5303
5304 5304 Option -q/--quiet hides untracked (unknown and ignored) files
5305 5305 unless explicitly requested with -u/--unknown or -i/--ignored.
5306 5306
5307 5307 .. note::
5308 5308 status may appear to disagree with diff if permissions have
5309 5309 changed or a merge has occurred. The standard diff format does
5310 5310 not report permission changes and diff only reports changes
5311 5311 relative to one merge parent.
5312 5312
5313 5313 If one revision is given, it is used as the base revision.
5314 5314 If two revisions are given, the differences between them are
5315 5315 shown. The --change option can also be used as a shortcut to list
5316 5316 the changed files of a revision from its first parent.
5317 5317
5318 5318 The codes used to show the status of files are::
5319 5319
5320 5320 M = modified
5321 5321 A = added
5322 5322 R = removed
5323 5323 C = clean
5324 5324 ! = missing (deleted by non-hg command, but still tracked)
5325 5325 ? = not tracked
5326 5326 I = ignored
5327 5327 = origin of the previous file listed as A (added)
5328 5328
5329 5329 .. container:: verbose
5330 5330
5331 5331 Examples:
5332 5332
5333 5333 - show changes in the working directory relative to a
5334 5334 changeset::
5335 5335
5336 5336 hg status --rev 9353
5337 5337
5338 5338 - show all changes including copies in an existing changeset::
5339 5339
5340 5340 hg status --copies --change 9353
5341 5341
5342 5342 - get a NUL separated list of added files, suitable for xargs::
5343 5343
5344 5344 hg status -an0
5345 5345
5346 5346 Returns 0 on success.
5347 5347 """
5348 5348
5349 5349 revs = opts.get('rev')
5350 5350 change = opts.get('change')
5351 5351
5352 5352 if revs and change:
5353 5353 msg = _('cannot specify --rev and --change at the same time')
5354 5354 raise util.Abort(msg)
5355 5355 elif change:
5356 5356 node2 = scmutil.revsingle(repo, change, None).node()
5357 5357 node1 = repo[node2].p1().node()
5358 5358 else:
5359 5359 node1, node2 = scmutil.revpair(repo, revs)
5360 5360
5361 5361 cwd = (pats and repo.getcwd()) or ''
5362 5362 end = opts.get('print0') and '\0' or '\n'
5363 5363 copy = {}
5364 5364 states = 'modified added removed deleted unknown ignored clean'.split()
5365 5365 show = [k for k in states if opts.get(k)]
5366 5366 if opts.get('all'):
5367 5367 show += ui.quiet and (states[:4] + ['clean']) or states
5368 5368 if not show:
5369 5369 show = ui.quiet and states[:4] or states[:5]
5370 5370
5371 5371 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5372 5372 'ignored' in show, 'clean' in show, 'unknown' in show,
5373 5373 opts.get('subrepos'))
5374 5374 changestates = zip(states, 'MAR!?IC', stat)
5375 5375
5376 5376 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5377 5377 copy = copies.pathcopies(repo[node1], repo[node2])
5378 5378
5379 5379 fm = ui.formatter('status', opts)
5380 5380 format = '%s %s' + end
5381 5381 if opts.get('no_status'):
5382 5382 format = '%.0s%s' + end
5383 5383
5384 5384 for state, char, files in changestates:
5385 5385 if state in show:
5386 5386 label = 'status.' + state
5387 5387 for f in files:
5388 5388 fm.startitem()
5389 5389 fm.write("status path", format, char,
5390 5390 repo.pathto(f, cwd), label=label)
5391 5391 if f in copy:
5392 5392 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5393 5393 label='status.copied')
5394 5394 fm.end()
5395 5395
5396 5396 @command('^summary|sum',
5397 5397 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5398 5398 def summary(ui, repo, **opts):
5399 5399 """summarize working directory state
5400 5400
5401 5401 This generates a brief summary of the working directory state,
5402 5402 including parents, branch, commit status, and available updates.
5403 5403
5404 5404 With the --remote option, this will check the default paths for
5405 5405 incoming and outgoing changes. This can be time-consuming.
5406 5406
5407 5407 Returns 0 on success.
5408 5408 """
5409 5409
5410 5410 ctx = repo[None]
5411 5411 parents = ctx.parents()
5412 5412 pnode = parents[0].node()
5413 5413 marks = []
5414 5414
5415 5415 for p in parents:
5416 5416 # label with log.changeset (instead of log.parent) since this
5417 5417 # shows a working directory parent *changeset*:
5418 5418 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5419 5419 label='log.changeset')
5420 5420 ui.write(' '.join(p.tags()), label='log.tag')
5421 5421 if p.bookmarks():
5422 5422 marks.extend(p.bookmarks())
5423 5423 if p.rev() == -1:
5424 5424 if not len(repo):
5425 5425 ui.write(_(' (empty repository)'))
5426 5426 else:
5427 5427 ui.write(_(' (no revision checked out)'))
5428 5428 ui.write('\n')
5429 5429 if p.description():
5430 5430 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5431 5431 label='log.summary')
5432 5432
5433 5433 branch = ctx.branch()
5434 5434 bheads = repo.branchheads(branch)
5435 5435 m = _('branch: %s\n') % branch
5436 5436 if branch != 'default':
5437 5437 ui.write(m, label='log.branch')
5438 5438 else:
5439 5439 ui.status(m, label='log.branch')
5440 5440
5441 5441 if marks:
5442 5442 current = repo._bookmarkcurrent
5443 5443 ui.write(_('bookmarks:'), label='log.bookmark')
5444 5444 if current is not None:
5445 5445 try:
5446 5446 marks.remove(current)
5447 5447 ui.write(' *' + current, label='bookmarks.current')
5448 5448 except ValueError:
5449 5449 # current bookmark not in parent ctx marks
5450 5450 pass
5451 5451 for m in marks:
5452 5452 ui.write(' ' + m, label='log.bookmark')
5453 5453 ui.write('\n', label='log.bookmark')
5454 5454
5455 5455 st = list(repo.status(unknown=True))[:6]
5456 5456
5457 5457 c = repo.dirstate.copies()
5458 5458 copied, renamed = [], []
5459 5459 for d, s in c.iteritems():
5460 5460 if s in st[2]:
5461 5461 st[2].remove(s)
5462 5462 renamed.append(d)
5463 5463 else:
5464 5464 copied.append(d)
5465 5465 if d in st[1]:
5466 5466 st[1].remove(d)
5467 5467 st.insert(3, renamed)
5468 5468 st.insert(4, copied)
5469 5469
5470 5470 ms = mergemod.mergestate(repo)
5471 5471 st.append([f for f in ms if ms[f] == 'u'])
5472 5472
5473 5473 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5474 5474 st.append(subs)
5475 5475
5476 5476 labels = [ui.label(_('%d modified'), 'status.modified'),
5477 5477 ui.label(_('%d added'), 'status.added'),
5478 5478 ui.label(_('%d removed'), 'status.removed'),
5479 5479 ui.label(_('%d renamed'), 'status.copied'),
5480 5480 ui.label(_('%d copied'), 'status.copied'),
5481 5481 ui.label(_('%d deleted'), 'status.deleted'),
5482 5482 ui.label(_('%d unknown'), 'status.unknown'),
5483 5483 ui.label(_('%d ignored'), 'status.ignored'),
5484 5484 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5485 5485 ui.label(_('%d subrepos'), 'status.modified')]
5486 5486 t = []
5487 5487 for s, l in zip(st, labels):
5488 5488 if s:
5489 5489 t.append(l % len(s))
5490 5490
5491 5491 t = ', '.join(t)
5492 5492 cleanworkdir = False
5493 5493
5494 5494 if len(parents) > 1:
5495 5495 t += _(' (merge)')
5496 5496 elif branch != parents[0].branch():
5497 5497 t += _(' (new branch)')
5498 5498 elif (parents[0].closesbranch() and
5499 5499 pnode in repo.branchheads(branch, closed=True)):
5500 5500 t += _(' (head closed)')
5501 5501 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5502 5502 t += _(' (clean)')
5503 5503 cleanworkdir = True
5504 5504 elif pnode not in bheads:
5505 5505 t += _(' (new branch head)')
5506 5506
5507 5507 if cleanworkdir:
5508 5508 ui.status(_('commit: %s\n') % t.strip())
5509 5509 else:
5510 5510 ui.write(_('commit: %s\n') % t.strip())
5511 5511
5512 5512 # all ancestors of branch heads - all ancestors of parent = new csets
5513 5513 new = [0] * len(repo)
5514 5514 cl = repo.changelog
5515 5515 for a in [cl.rev(n) for n in bheads]:
5516 5516 new[a] = 1
5517 5517 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5518 5518 new[a] = 1
5519 5519 for a in [p.rev() for p in parents]:
5520 5520 if a >= 0:
5521 5521 new[a] = 0
5522 5522 for a in cl.ancestors([p.rev() for p in parents]):
5523 5523 new[a] = 0
5524 5524 new = sum(new)
5525 5525
5526 5526 if new == 0:
5527 5527 ui.status(_('update: (current)\n'))
5528 5528 elif pnode not in bheads:
5529 5529 ui.write(_('update: %d new changesets (update)\n') % new)
5530 5530 else:
5531 5531 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5532 5532 (new, len(bheads)))
5533 5533
5534 5534 if opts.get('remote'):
5535 5535 t = []
5536 5536 source, branches = hg.parseurl(ui.expandpath('default'))
5537 5537 other = hg.peer(repo, {}, source)
5538 5538 revs, checkout = hg.addbranchrevs(repo, other, branches,
5539 5539 opts.get('rev'))
5540 5540 ui.debug('comparing with %s\n' % util.hidepassword(source))
5541 5541 repo.ui.pushbuffer()
5542 5542 commoninc = discovery.findcommonincoming(repo, other)
5543 5543 _common, incoming, _rheads = commoninc
5544 5544 repo.ui.popbuffer()
5545 5545 if incoming:
5546 5546 t.append(_('1 or more incoming'))
5547 5547
5548 5548 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5549 5549 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5550 5550 if source != dest:
5551 5551 other = hg.peer(repo, {}, dest)
5552 5552 commoninc = None
5553 5553 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5554 5554 repo.ui.pushbuffer()
5555 5555 outgoing = discovery.findcommonoutgoing(repo, other,
5556 5556 commoninc=commoninc)
5557 5557 repo.ui.popbuffer()
5558 5558 o = outgoing.missing
5559 5559 if o:
5560 5560 t.append(_('%d outgoing') % len(o))
5561 5561 if 'bookmarks' in other.listkeys('namespaces'):
5562 5562 lmarks = repo.listkeys('bookmarks')
5563 5563 rmarks = other.listkeys('bookmarks')
5564 5564 diff = set(rmarks) - set(lmarks)
5565 5565 if len(diff) > 0:
5566 5566 t.append(_('%d incoming bookmarks') % len(diff))
5567 5567 diff = set(lmarks) - set(rmarks)
5568 5568 if len(diff) > 0:
5569 5569 t.append(_('%d outgoing bookmarks') % len(diff))
5570 5570
5571 5571 if t:
5572 5572 ui.write(_('remote: %s\n') % (', '.join(t)))
5573 5573 else:
5574 5574 ui.status(_('remote: (synced)\n'))
5575 5575
5576 5576 @command('tag',
5577 5577 [('f', 'force', None, _('force tag')),
5578 5578 ('l', 'local', None, _('make the tag local')),
5579 5579 ('r', 'rev', '', _('revision to tag'), _('REV')),
5580 5580 ('', 'remove', None, _('remove a tag')),
5581 5581 # -l/--local is already there, commitopts cannot be used
5582 5582 ('e', 'edit', None, _('edit commit message')),
5583 5583 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5584 5584 ] + commitopts2,
5585 5585 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5586 5586 def tag(ui, repo, name1, *names, **opts):
5587 5587 """add one or more tags for the current or given revision
5588 5588
5589 5589 Name a particular revision using <name>.
5590 5590
5591 5591 Tags are used to name particular revisions of the repository and are
5592 5592 very useful to compare different revisions, to go back to significant
5593 5593 earlier versions or to mark branch points as releases, etc. Changing
5594 5594 an existing tag is normally disallowed; use -f/--force to override.
5595 5595
5596 5596 If no revision is given, the parent of the working directory is
5597 5597 used, or tip if no revision is checked out.
5598 5598
5599 5599 To facilitate version control, distribution, and merging of tags,
5600 5600 they are stored as a file named ".hgtags" which is managed similarly
5601 5601 to other project files and can be hand-edited if necessary. This
5602 5602 also means that tagging creates a new commit. The file
5603 5603 ".hg/localtags" is used for local tags (not shared among
5604 5604 repositories).
5605 5605
5606 5606 Tag commits are usually made at the head of a branch. If the parent
5607 5607 of the working directory is not a branch head, :hg:`tag` aborts; use
5608 5608 -f/--force to force the tag commit to be based on a non-head
5609 5609 changeset.
5610 5610
5611 5611 See :hg:`help dates` for a list of formats valid for -d/--date.
5612 5612
5613 5613 Since tag names have priority over branch names during revision
5614 5614 lookup, using an existing branch name as a tag name is discouraged.
5615 5615
5616 5616 Returns 0 on success.
5617 5617 """
5618 5618 wlock = lock = None
5619 5619 try:
5620 5620 wlock = repo.wlock()
5621 5621 lock = repo.lock()
5622 5622 rev_ = "."
5623 5623 names = [t.strip() for t in (name1,) + names]
5624 5624 if len(names) != len(set(names)):
5625 5625 raise util.Abort(_('tag names must be unique'))
5626 5626 for n in names:
5627 5627 if n in ['tip', '.', 'null']:
5628 5628 raise util.Abort(_("the name '%s' is reserved") % n)
5629 5629 if not n:
5630 5630 raise util.Abort(_('tag names cannot consist entirely of '
5631 5631 'whitespace'))
5632 5632 if opts.get('rev') and opts.get('remove'):
5633 5633 raise util.Abort(_("--rev and --remove are incompatible"))
5634 5634 if opts.get('rev'):
5635 5635 rev_ = opts['rev']
5636 5636 message = opts.get('message')
5637 5637 if opts.get('remove'):
5638 5638 expectedtype = opts.get('local') and 'local' or 'global'
5639 5639 for n in names:
5640 5640 if not repo.tagtype(n):
5641 5641 raise util.Abort(_("tag '%s' does not exist") % n)
5642 5642 if repo.tagtype(n) != expectedtype:
5643 5643 if expectedtype == 'global':
5644 5644 raise util.Abort(_("tag '%s' is not a global tag") % n)
5645 5645 else:
5646 5646 raise util.Abort(_("tag '%s' is not a local tag") % n)
5647 5647 rev_ = nullid
5648 5648 if not message:
5649 5649 # we don't translate commit messages
5650 5650 message = 'Removed tag %s' % ', '.join(names)
5651 5651 elif not opts.get('force'):
5652 5652 for n in names:
5653 5653 if n in repo.tags():
5654 5654 raise util.Abort(_("tag '%s' already exists "
5655 5655 "(use -f to force)") % n)
5656 5656 if not opts.get('local'):
5657 5657 p1, p2 = repo.dirstate.parents()
5658 5658 if p2 != nullid:
5659 5659 raise util.Abort(_('uncommitted merge'))
5660 5660 bheads = repo.branchheads()
5661 5661 if not opts.get('force') and bheads and p1 not in bheads:
5662 5662 raise util.Abort(_('not at a branch head (use -f to force)'))
5663 5663 r = scmutil.revsingle(repo, rev_).node()
5664 5664
5665 5665 if not message:
5666 5666 # we don't translate commit messages
5667 5667 message = ('Added tag %s for changeset %s' %
5668 5668 (', '.join(names), short(r)))
5669 5669
5670 5670 date = opts.get('date')
5671 5671 if date:
5672 5672 date = util.parsedate(date)
5673 5673
5674 5674 if opts.get('edit'):
5675 5675 message = ui.edit(message, ui.username())
5676 5676
5677 5677 # don't allow tagging the null rev
5678 5678 if (not opts.get('remove') and
5679 5679 scmutil.revsingle(repo, rev_).rev() == nullrev):
5680 5680 raise util.Abort(_("null revision specified"))
5681 5681
5682 5682 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5683 5683 finally:
5684 5684 release(lock, wlock)
5685 5685
5686 5686 @command('tags', [], '')
5687 5687 def tags(ui, repo):
5688 5688 """list repository tags
5689 5689
5690 5690 This lists both regular and local tags. When the -v/--verbose
5691 5691 switch is used, a third column "local" is printed for local tags.
5692 5692
5693 5693 Returns 0 on success.
5694 5694 """
5695 5695
5696 5696 hexfunc = ui.debugflag and hex or short
5697 5697 tagtype = ""
5698 5698
5699 5699 for t, n in reversed(repo.tagslist()):
5700 5700 if ui.quiet:
5701 5701 ui.write("%s\n" % t, label='tags.normal')
5702 5702 continue
5703 5703
5704 5704 hn = hexfunc(n)
5705 5705 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5706 5706 rev = ui.label(r, 'log.changeset')
5707 5707 spaces = " " * (30 - encoding.colwidth(t))
5708 5708
5709 5709 tag = ui.label(t, 'tags.normal')
5710 5710 if ui.verbose:
5711 5711 if repo.tagtype(t) == 'local':
5712 5712 tagtype = " local"
5713 5713 tag = ui.label(t, 'tags.local')
5714 5714 else:
5715 5715 tagtype = ""
5716 5716 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5717 5717
5718 5718 @command('tip',
5719 5719 [('p', 'patch', None, _('show patch')),
5720 5720 ('g', 'git', None, _('use git extended diff format')),
5721 5721 ] + templateopts,
5722 5722 _('[-p] [-g]'))
5723 5723 def tip(ui, repo, **opts):
5724 5724 """show the tip revision
5725 5725
5726 5726 The tip revision (usually just called the tip) is the changeset
5727 5727 most recently added to the repository (and therefore the most
5728 5728 recently changed head).
5729 5729
5730 5730 If you have just made a commit, that commit will be the tip. If
5731 5731 you have just pulled changes from another repository, the tip of
5732 5732 that repository becomes the current tip. The "tip" tag is special
5733 5733 and cannot be renamed or assigned to a different changeset.
5734 5734
5735 5735 Returns 0 on success.
5736 5736 """
5737 5737 displayer = cmdutil.show_changeset(ui, repo, opts)
5738 5738 displayer.show(repo[len(repo) - 1])
5739 5739 displayer.close()
5740 5740
5741 5741 @command('unbundle',
5742 5742 [('u', 'update', None,
5743 5743 _('update to new branch head if changesets were unbundled'))],
5744 5744 _('[-u] FILE...'))
5745 5745 def unbundle(ui, repo, fname1, *fnames, **opts):
5746 5746 """apply one or more changegroup files
5747 5747
5748 5748 Apply one or more compressed changegroup files generated by the
5749 5749 bundle command.
5750 5750
5751 5751 Returns 0 on success, 1 if an update has unresolved files.
5752 5752 """
5753 5753 fnames = (fname1,) + fnames
5754 5754
5755 5755 lock = repo.lock()
5756 5756 wc = repo['.']
5757 5757 try:
5758 5758 for fname in fnames:
5759 5759 f = url.open(ui, fname)
5760 5760 gen = changegroup.readbundle(f, fname)
5761 5761 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5762 5762 finally:
5763 5763 lock.release()
5764 5764 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5765 5765 return postincoming(ui, repo, modheads, opts.get('update'), None)
5766 5766
5767 5767 @command('^update|up|checkout|co',
5768 5768 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5769 5769 ('c', 'check', None,
5770 5770 _('update across branches if no uncommitted changes')),
5771 5771 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5772 5772 ('r', 'rev', '', _('revision'), _('REV'))],
5773 5773 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5774 5774 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5775 5775 """update working directory (or switch revisions)
5776 5776
5777 5777 Update the repository's working directory to the specified
5778 5778 changeset. If no changeset is specified, update to the tip of the
5779 5779 current named branch and move the current bookmark (see :hg:`help
5780 5780 bookmarks`).
5781 5781
5782 5782 Update sets the working directory's parent revision to the specified
5783 5783 changeset (see :hg:`help parents`).
5784 5784
5785 5785 If the changeset is not a descendant or ancestor of the working
5786 5786 directory's parent, the update is aborted. With the -c/--check
5787 5787 option, the working directory is checked for uncommitted changes; if
5788 5788 none are found, the working directory is updated to the specified
5789 5789 changeset.
5790 5790
5791 5791 .. container:: verbose
5792 5792
5793 5793 The following rules apply when the working directory contains
5794 5794 uncommitted changes:
5795 5795
5796 5796 1. If neither -c/--check nor -C/--clean is specified, and if
5797 5797 the requested changeset is an ancestor or descendant of
5798 5798 the working directory's parent, the uncommitted changes
5799 5799 are merged into the requested changeset and the merged
5800 5800 result is left uncommitted. If the requested changeset is
5801 5801 not an ancestor or descendant (that is, it is on another
5802 5802 branch), the update is aborted and the uncommitted changes
5803 5803 are preserved.
5804 5804
5805 5805 2. With the -c/--check option, the update is aborted and the
5806 5806 uncommitted changes are preserved.
5807 5807
5808 5808 3. With the -C/--clean option, uncommitted changes are discarded and
5809 5809 the working directory is updated to the requested changeset.
5810 5810
5811 5811 To cancel an uncommitted merge (and lose your changes), use
5812 5812 :hg:`update --clean .`.
5813 5813
5814 5814 Use null as the changeset to remove the working directory (like
5815 5815 :hg:`clone -U`).
5816 5816
5817 5817 If you want to revert just one file to an older revision, use
5818 5818 :hg:`revert [-r REV] NAME`.
5819 5819
5820 5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5821 5821
5822 5822 Returns 0 on success, 1 if there are unresolved files.
5823 5823 """
5824 5824 if rev and node:
5825 5825 raise util.Abort(_("please specify just one revision"))
5826 5826
5827 5827 if rev is None or rev == '':
5828 5828 rev = node
5829 5829
5830 5830 # with no argument, we also move the current bookmark, if any
5831 5831 movemarkfrom = None
5832 5832 if rev is None or node == '':
5833 5833 movemarkfrom = repo['.'].node()
5834 5834
5835 5835 # if we defined a bookmark, we have to remember the original bookmark name
5836 5836 brev = rev
5837 5837 rev = scmutil.revsingle(repo, rev, rev).rev()
5838 5838
5839 5839 if check and clean:
5840 5840 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5841 5841
5842 5842 if date:
5843 5843 if rev is not None:
5844 5844 raise util.Abort(_("you can't specify a revision and a date"))
5845 5845 rev = cmdutil.finddate(ui, repo, date)
5846 5846
5847 5847 if check:
5848 5848 c = repo[None]
5849 5849 if c.dirty(merge=False, branch=False):
5850 5850 raise util.Abort(_("uncommitted local changes"))
5851 5851 if rev is None:
5852 5852 rev = repo[repo[None].branch()].rev()
5853 5853 mergemod._checkunknown(repo, repo[None], repo[rev])
5854 5854
5855 5855 if clean:
5856 5856 ret = hg.clean(repo, rev)
5857 5857 else:
5858 5858 ret = hg.update(repo, rev)
5859 5859
5860 5860 if not ret and movemarkfrom:
5861 5861 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5862 5862 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5863 5863 elif brev in repo._bookmarks:
5864 5864 bookmarks.setcurrent(repo, brev)
5865 5865 elif brev:
5866 5866 bookmarks.unsetcurrent(repo)
5867 5867
5868 5868 return ret
5869 5869
5870 5870 @command('verify', [])
5871 5871 def verify(ui, repo):
5872 5872 """verify the integrity of the repository
5873 5873
5874 5874 Verify the integrity of the current repository.
5875 5875
5876 5876 This will perform an extensive check of the repository's
5877 5877 integrity, validating the hashes and checksums of each entry in
5878 5878 the changelog, manifest, and tracked files, as well as the
5879 5879 integrity of their crosslinks and indices.
5880 5880
5881 5881 Returns 0 on success, 1 if errors are encountered.
5882 5882 """
5883 5883 return hg.verify(repo)
5884 5884
5885 5885 @command('version', [])
5886 5886 def version_(ui):
5887 5887 """output version and copyright information"""
5888 5888 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5889 5889 % util.version())
5890 5890 ui.status(_(
5891 5891 "(see http://mercurial.selenic.com for more information)\n"
5892 5892 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5893 5893 "This is free software; see the source for copying conditions. "
5894 5894 "There is NO\nwarranty; "
5895 5895 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5896 5896 ))
5897 5897
5898 5898 norepo = ("clone init version help debugcommands debugcomplete"
5899 5899 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5900 5900 " debugknown debuggetbundle debugbundle")
5901 5901 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5902 5902 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,183 +1,183
1 1 # config.py - configuration parsing for Mercurial
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import error, util
10 10 import os, errno
11 11
12 12 class sortdict(dict):
13 13 'a simple sorted dictionary'
14 14 def __init__(self, data=None):
15 15 self._list = []
16 16 if data:
17 17 self.update(data)
18 18 def copy(self):
19 19 return sortdict(self)
20 20 def __setitem__(self, key, val):
21 21 if key in self:
22 22 self._list.remove(key)
23 23 self._list.append(key)
24 24 dict.__setitem__(self, key, val)
25 25 def __iter__(self):
26 26 return self._list.__iter__()
27 27 def update(self, src):
28 28 for k in src:
29 29 self[k] = src[k]
30 30 def clear(self):
31 31 dict.clear(self)
32 32 self._list = []
33 33 def items(self):
34 34 return [(k, self[k]) for k in self._list]
35 35 def __delitem__(self, key):
36 36 dict.__delitem__(self, key)
37 37 self._list.remove(key)
38 38 def keys(self):
39 39 return self._list
40 40 def iterkeys(self):
41 41 return self._list.__iter__()
42 42
43 43 class config(object):
44 44 def __init__(self, data=None):
45 45 self._data = {}
46 46 self._source = {}
47 47 if data:
48 48 for k in data._data:
49 49 self._data[k] = data[k].copy()
50 50 self._source = data._source.copy()
51 51 def copy(self):
52 52 return config(self)
53 53 def __contains__(self, section):
54 54 return section in self._data
55 55 def __getitem__(self, section):
56 56 return self._data.get(section, {})
57 57 def __iter__(self):
58 58 for d in self.sections():
59 59 yield d
60 60 def update(self, src):
61 61 for s in src:
62 62 if s not in self:
63 63 self._data[s] = sortdict()
64 64 self._data[s].update(src._data[s])
65 65 self._source.update(src._source)
66 66 def get(self, section, item, default=None):
67 67 return self._data.get(section, {}).get(item, default)
68 68
69 69 def backup(self, section, item):
70 """return a tuple allowing restore to reinstall previous values
70 """return a tuple allowing restore to reinstall a previous value
71 71
72 The main reason we need it is because it handle the "no data" case.
72 The main reason we need it is because it handles the "no data" case.
73 73 """
74 74 try:
75 75 value = self._data[section][item]
76 76 source = self.source(section, item)
77 77 return (section, item, value, source)
78 78 except KeyError:
79 79 return (section, item)
80 80
81 81 def source(self, section, item):
82 82 return self._source.get((section, item), "")
83 83 def sections(self):
84 84 return sorted(self._data.keys())
85 85 def items(self, section):
86 86 return self._data.get(section, {}).items()
87 87 def set(self, section, item, value, source=""):
88 88 if section not in self:
89 89 self._data[section] = sortdict()
90 90 self._data[section][item] = value
91 91 self._source[(section, item)] = source
92 92
93 93 def restore(self, data):
94 94 """restore data returned by self.backup"""
95 95 if len(data) == 4:
96 96 # restore old data
97 97 section, item, value, source = data
98 98 self._data[section][item] = value
99 99 self._source[(section, item)] = source
100 100 else:
101 101 # no data before, remove everything
102 102 section, item = data
103 103 if section in self._data:
104 104 del self._data[section][item]
105 105 self._source.pop((section, item), None)
106 106
107 107 def parse(self, src, data, sections=None, remap=None, include=None):
108 108 sectionre = util.compilere(r'\[([^\[]+)\]')
109 109 itemre = util.compilere(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
110 110 contre = util.compilere(r'\s+(\S|\S.*\S)\s*$')
111 111 emptyre = util.compilere(r'(;|#|\s*$)')
112 112 commentre = util.compilere(r'(;|#)')
113 113 unsetre = util.compilere(r'%unset\s+(\S+)')
114 114 includere = util.compilere(r'%include\s+(\S|\S.*\S)\s*$')
115 115 section = ""
116 116 item = None
117 117 line = 0
118 118 cont = False
119 119
120 120 for l in data.splitlines(True):
121 121 line += 1
122 122 if line == 1 and l.startswith('\xef\xbb\xbf'):
123 123 # Someone set us up the BOM
124 124 l = l[3:]
125 125 if cont:
126 126 if commentre.match(l):
127 127 continue
128 128 m = contre.match(l)
129 129 if m:
130 130 if sections and section not in sections:
131 131 continue
132 132 v = self.get(section, item) + "\n" + m.group(1)
133 133 self.set(section, item, v, "%s:%d" % (src, line))
134 134 continue
135 135 item = None
136 136 cont = False
137 137 m = includere.match(l)
138 138 if m:
139 139 inc = util.expandpath(m.group(1))
140 140 base = os.path.dirname(src)
141 141 inc = os.path.normpath(os.path.join(base, inc))
142 142 if include:
143 143 try:
144 144 include(inc, remap=remap, sections=sections)
145 145 except IOError, inst:
146 146 if inst.errno != errno.ENOENT:
147 147 raise error.ParseError(_("cannot include %s (%s)")
148 148 % (inc, inst.strerror),
149 149 "%s:%s" % (src, line))
150 150 continue
151 151 if emptyre.match(l):
152 152 continue
153 153 m = sectionre.match(l)
154 154 if m:
155 155 section = m.group(1)
156 156 if remap:
157 157 section = remap.get(section, section)
158 158 if section not in self:
159 159 self._data[section] = sortdict()
160 160 continue
161 161 m = itemre.match(l)
162 162 if m:
163 163 item = m.group(1)
164 164 cont = True
165 165 if sections and section not in sections:
166 166 continue
167 167 self.set(section, item, m.group(2), "%s:%d" % (src, line))
168 168 continue
169 169 m = unsetre.match(l)
170 170 if m:
171 171 name = m.group(1)
172 172 if sections and section not in sections:
173 173 continue
174 174 if self.get(section, name) is not None:
175 175 del self._data[section][name]
176 176 continue
177 177
178 178 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
179 179
180 180 def read(self, path, fp=None, sections=None, remap=None):
181 181 if not fp:
182 182 fp = util.posixfile(path)
183 183 self.parse(path, fp.read(), sections, remap, self.read)
@@ -1,479 +1,479
1 1 # dagparser.py - parser and generator for concise description of DAGs
2 2 #
3 3 # Copyright 2010 Peter Arrenbrecht <peter@arrenbrecht.ch>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re, string
9 9 import util
10 10 from i18n import _
11 11
12 12 def parsedag(desc):
13 13 '''parses a DAG from a concise textual description; generates events
14 14
15 15 "+n" is a linear run of n nodes based on the current default parent
16 16 "." is a single node based on the current default parent
17 17 "$" resets the default parent to -1 (implied at the start);
18 18 otherwise the default parent is always the last node created
19 19 "<p" sets the default parent to the backref p
20 20 "*p" is a fork at parent p, where p is a backref
21 21 "*p1/p2/.../pn" is a merge of parents p1..pn, where the pi are backrefs
22 22 "/p2/.../pn" is a merge of the preceding node and p2..pn
23 23 ":name" defines a label for the preceding node; labels can be redefined
24 24 "@text" emits an annotation event for text
25 25 "!command" emits an action event for the current node
26 26 "!!my command\n" is like "!", but to the end of the line
27 27 "#...\n" is a comment up to the end of the line
28 28
29 29 Whitespace between the above elements is ignored.
30 30
31 31 A backref is either
32 32 * a number n, which references the node curr-n, where curr is the current
33 33 node, or
34 34 * the name of a label you placed earlier using ":name", or
35 35 * empty to denote the default parent.
36 36
37 37 All string valued-elements are either strictly alphanumeric, or must
38 38 be enclosed in double quotes ("..."), with "\" as escape character.
39 39
40 40 Generates sequence of
41 41
42 42 ('n', (id, [parentids])) for node creation
43 43 ('l', (id, labelname)) for labels on nodes
44 44 ('a', text) for annotations
45 45 ('c', command) for actions (!)
46 46 ('C', command) for line actions (!!)
47 47
48 48 Examples
49 49 --------
50 50
51 51 Example of a complex graph (output not shown for brevity):
52 52
53 53 >>> len(list(parsedag("""
54 54 ...
55 55 ... +3 # 3 nodes in linear run
56 56 ... :forkhere # a label for the last of the 3 nodes from above
57 57 ... +5 # 5 more nodes on one branch
58 58 ... :mergethis # label again
59 ... <forkhere # set default parent to labelled fork node
59 ... <forkhere # set default parent to labeled fork node
60 60 ... +10 # 10 more nodes on a parallel branch
61 61 ... @stable # following nodes will be annotated as "stable"
62 62 ... +5 # 5 nodes in stable
63 63 ... !addfile # custom command; could trigger new file in next node
64 64 ... +2 # two more nodes
65 ... /mergethis # merge last node with labelled node
65 ... /mergethis # merge last node with labeled node
66 66 ... +4 # 4 more nodes descending from merge node
67 67 ...
68 68 ... """)))
69 69 34
70 70
71 71 Empty list:
72 72
73 73 >>> list(parsedag(""))
74 74 []
75 75
76 76 A simple linear run:
77 77
78 78 >>> list(parsedag("+3"))
79 79 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
80 80
81 81 Some non-standard ways to define such runs:
82 82
83 83 >>> list(parsedag("+1+2"))
84 84 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
85 85
86 86 >>> list(parsedag("+1*1*"))
87 87 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
88 88
89 89 >>> list(parsedag("*"))
90 90 [('n', (0, [-1]))]
91 91
92 92 >>> list(parsedag("..."))
93 93 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
94 94
95 95 A fork and a join, using numeric back references:
96 96
97 97 >>> list(parsedag("+2*2*/2"))
98 98 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
99 99
100 100 >>> list(parsedag("+2<2+1/2"))
101 101 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
102 102
103 103 Placing a label:
104 104
105 105 >>> list(parsedag("+1 :mylabel +1"))
106 106 [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
107 107
108 108 An empty label (silly, really):
109 109
110 110 >>> list(parsedag("+1:+1"))
111 111 [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
112 112
113 113 Fork and join, but with labels instead of numeric back references:
114 114
115 115 >>> list(parsedag("+1:f +1:p2 *f */p2"))
116 116 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
117 117 ('n', (2, [0])), ('n', (3, [2, 1]))]
118 118
119 119 >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
120 120 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
121 121 ('n', (2, [0])), ('n', (3, [2, 1]))]
122 122
123 123 Restarting from the root:
124 124
125 125 >>> list(parsedag("+1 $ +1"))
126 126 [('n', (0, [-1])), ('n', (1, [-1]))]
127 127
128 128 Annotations, which are meant to introduce sticky state for subsequent nodes:
129 129
130 130 >>> list(parsedag("+1 @ann +1"))
131 131 [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
132 132
133 133 >>> list(parsedag('+1 @"my annotation" +1'))
134 134 [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
135 135
136 136 Commands, which are meant to operate on the most recently created node:
137 137
138 138 >>> list(parsedag("+1 !cmd +1"))
139 139 [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
140 140
141 141 >>> list(parsedag('+1 !"my command" +1'))
142 142 [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
143 143
144 144 >>> list(parsedag('+1 !!my command line\\n +1'))
145 145 [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
146 146
147 147 Comments, which extend to the end of the line:
148 148
149 149 >>> list(parsedag('+1 # comment\\n+1'))
150 150 [('n', (0, [-1])), ('n', (1, [0]))]
151 151
152 152 Error:
153 153
154 154 >>> try: list(parsedag('+1 bad'))
155 155 ... except Exception, e: print e
156 156 invalid character in dag description: bad...
157 157
158 158 '''
159 159 if not desc:
160 160 return
161 161
162 162 wordchars = string.ascii_letters + string.digits
163 163
164 164 labels = {}
165 165 p1 = -1
166 166 r = 0
167 167
168 168 def resolve(ref):
169 169 if not ref:
170 170 return p1
171 171 elif ref[0] in string.digits:
172 172 return r - int(ref)
173 173 else:
174 174 return labels[ref]
175 175
176 176 chiter = (c for c in desc)
177 177
178 178 def nextch():
179 179 try:
180 180 return chiter.next()
181 181 except StopIteration:
182 182 return '\0'
183 183
184 184 def nextrun(c, allow):
185 185 s = ''
186 186 while c in allow:
187 187 s += c
188 188 c = nextch()
189 189 return c, s
190 190
191 191 def nextdelimited(c, limit, escape):
192 192 s = ''
193 193 while c != limit:
194 194 if c == escape:
195 195 c = nextch()
196 196 s += c
197 197 c = nextch()
198 198 return nextch(), s
199 199
200 200 def nextstring(c):
201 201 if c == '"':
202 202 return nextdelimited(nextch(), '"', '\\')
203 203 else:
204 204 return nextrun(c, wordchars)
205 205
206 206 c = nextch()
207 207 while c != '\0':
208 208 while c in string.whitespace:
209 209 c = nextch()
210 210 if c == '.':
211 211 yield 'n', (r, [p1])
212 212 p1 = r
213 213 r += 1
214 214 c = nextch()
215 215 elif c == '+':
216 216 c, digs = nextrun(nextch(), string.digits)
217 217 n = int(digs)
218 218 for i in xrange(0, n):
219 219 yield 'n', (r, [p1])
220 220 p1 = r
221 221 r += 1
222 222 elif c in '*/':
223 223 if c == '*':
224 224 c = nextch()
225 225 c, pref = nextstring(c)
226 226 prefs = [pref]
227 227 while c == '/':
228 228 c, pref = nextstring(nextch())
229 229 prefs.append(pref)
230 230 ps = [resolve(ref) for ref in prefs]
231 231 yield 'n', (r, ps)
232 232 p1 = r
233 233 r += 1
234 234 elif c == '<':
235 235 c, ref = nextstring(nextch())
236 236 p1 = resolve(ref)
237 237 elif c == ':':
238 238 c, name = nextstring(nextch())
239 239 labels[name] = p1
240 240 yield 'l', (p1, name)
241 241 elif c == '@':
242 242 c, text = nextstring(nextch())
243 243 yield 'a', text
244 244 elif c == '!':
245 245 c = nextch()
246 246 if c == '!':
247 247 cmd = ''
248 248 c = nextch()
249 249 while c not in '\n\r\0':
250 250 cmd += c
251 251 c = nextch()
252 252 yield 'C', cmd
253 253 else:
254 254 c, cmd = nextstring(c)
255 255 yield 'c', cmd
256 256 elif c == '#':
257 257 while c not in '\n\r\0':
258 258 c = nextch()
259 259 elif c == '$':
260 260 p1 = -1
261 261 c = nextch()
262 262 elif c == '\0':
263 263 return # in case it was preceded by whitespace
264 264 else:
265 265 s = ''
266 266 i = 0
267 267 while c != '\0' and i < 10:
268 268 s += c
269 269 i += 1
270 270 c = nextch()
271 271 raise util.Abort(_('invalid character in dag description: '
272 272 '%s...') % s)
273 273
274 274 def dagtextlines(events,
275 275 addspaces=True,
276 276 wraplabels=False,
277 277 wrapannotations=False,
278 278 wrapcommands=False,
279 279 wrapnonlinear=False,
280 280 usedots=False,
281 281 maxlinewidth=70):
282 282 '''generates single lines for dagtext()'''
283 283
284 284 def wrapstring(text):
285 285 if re.match("^[0-9a-z]*$", text):
286 286 return text
287 287 return '"' + text.replace('\\', '\\\\').replace('"', '\"') + '"'
288 288
289 289 def gen():
290 290 labels = {}
291 291 run = 0
292 292 wantr = 0
293 293 needroot = False
294 294 for kind, data in events:
295 295 if kind == 'n':
296 296 r, ps = data
297 297
298 298 # sanity check
299 299 if r != wantr:
300 300 raise util.Abort(_("expected id %i, got %i") % (wantr, r))
301 301 if not ps:
302 302 ps = [-1]
303 303 else:
304 304 for p in ps:
305 305 if p >= r:
306 306 raise util.Abort(_("parent id %i is larger than "
307 307 "current id %i") % (p, r))
308 308 wantr += 1
309 309
310 310 # new root?
311 311 p1 = r - 1
312 312 if len(ps) == 1 and ps[0] == -1:
313 313 if needroot:
314 314 if run:
315 315 yield '+' + str(run)
316 316 run = 0
317 317 if wrapnonlinear:
318 318 yield '\n'
319 319 yield '$'
320 320 p1 = -1
321 321 else:
322 322 needroot = True
323 323 if len(ps) == 1 and ps[0] == p1:
324 324 if usedots:
325 325 yield "."
326 326 else:
327 327 run += 1
328 328 else:
329 329 if run:
330 330 yield '+' + str(run)
331 331 run = 0
332 332 if wrapnonlinear:
333 333 yield '\n'
334 334 prefs = []
335 335 for p in ps:
336 336 if p == p1:
337 337 prefs.append('')
338 338 elif p in labels:
339 339 prefs.append(labels[p])
340 340 else:
341 341 prefs.append(str(r - p))
342 342 yield '*' + '/'.join(prefs)
343 343 else:
344 344 if run:
345 345 yield '+' + str(run)
346 346 run = 0
347 347 if kind == 'l':
348 348 rid, name = data
349 349 labels[rid] = name
350 350 yield ':' + name
351 351 if wraplabels:
352 352 yield '\n'
353 353 elif kind == 'c':
354 354 yield '!' + wrapstring(data)
355 355 if wrapcommands:
356 356 yield '\n'
357 357 elif kind == 'C':
358 358 yield '!!' + data
359 359 yield '\n'
360 360 elif kind == 'a':
361 361 if wrapannotations:
362 362 yield '\n'
363 363 yield '@' + wrapstring(data)
364 364 elif kind == '#':
365 365 yield '#' + data
366 366 yield '\n'
367 367 else:
368 368 raise util.Abort(_("invalid event type in dag: %s")
369 369 % str((type, data)))
370 370 if run:
371 371 yield '+' + str(run)
372 372
373 373 line = ''
374 374 for part in gen():
375 375 if part == '\n':
376 376 if line:
377 377 yield line
378 378 line = ''
379 379 else:
380 380 if len(line) + len(part) >= maxlinewidth:
381 381 yield line
382 382 line = ''
383 383 elif addspaces and line and part != '.':
384 384 line += ' '
385 385 line += part
386 386 if line:
387 387 yield line
388 388
389 389 def dagtext(dag,
390 390 addspaces=True,
391 391 wraplabels=False,
392 392 wrapannotations=False,
393 393 wrapcommands=False,
394 394 wrapnonlinear=False,
395 395 usedots=False,
396 396 maxlinewidth=70):
397 397 '''generates lines of a textual representation for a dag event stream
398 398
399 399 events should generate what parsedag() does, so:
400 400
401 401 ('n', (id, [parentids])) for node creation
402 402 ('l', (id, labelname)) for labels on nodes
403 403 ('a', text) for annotations
404 404 ('c', text) for commands
405 405 ('C', text) for line commands ('!!')
406 406 ('#', text) for comment lines
407 407
408 408 Parent nodes must come before child nodes.
409 409
410 410 Examples
411 411 --------
412 412
413 413 Linear run:
414 414
415 415 >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
416 416 '+2'
417 417
418 418 Two roots:
419 419
420 420 >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
421 421 '+1 $ +1'
422 422
423 423 Fork and join:
424 424
425 425 >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
426 426 ... ('n', (3, [2, 1]))])
427 427 '+2 *2 */2'
428 428
429 429 Fork and join with labels:
430 430
431 431 >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
432 432 ... ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
433 433 '+1 :f +1 :p2 *f */p2'
434 434
435 435 Annotations:
436 436
437 437 >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
438 438 '+1 @ann +1'
439 439
440 440 >>> dagtext([('n', (0, [-1])),
441 441 ... ('a', 'my annotation'),
442 442 ... ('n', (1, [0]))])
443 443 '+1 @"my annotation" +1'
444 444
445 445 Commands:
446 446
447 447 >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
448 448 '+1 !cmd +1'
449 449
450 450 >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
451 451 '+1 !"my command" +1'
452 452
453 453 >>> dagtext([('n', (0, [-1])),
454 454 ... ('C', 'my command line'),
455 455 ... ('n', (1, [0]))])
456 456 '+1 !!my command line\\n+1'
457 457
458 458 Comments:
459 459
460 460 >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
461 461 '+1 # comment\\n+1'
462 462
463 463 >>> dagtext([])
464 464 ''
465 465
466 466 Combining parsedag and dagtext:
467 467
468 468 >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
469 469 '+1 :f +1 :p2 *f */p2'
470 470
471 471 '''
472 472 return "\n".join(dagtextlines(dag,
473 473 addspaces,
474 474 wraplabels,
475 475 wrapannotations,
476 476 wrapcommands,
477 477 wrapnonlinear,
478 478 usedots,
479 479 maxlinewidth))
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now