##// END OF EJS Templates
Merge with crew.
Thomas Arendsen Hein -
r2531:7a90e0c7 merge default
parent child Browse files
Show More
@@ -0,0 +1,69 b''
1 # hgweb/wsgicgi.py - CGI->WSGI translator
2 #
3 # Copyright 2006 Eric Hopper <hopper@omnifarious.org>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7 #
8 # This was originally copied from the public domain code at
9 # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
10
11 import os, sys
12
13 def launch(application):
14
15 environ = dict(os.environ.items())
16 environ['wsgi.input'] = sys.stdin
17 environ['wsgi.errors'] = sys.stderr
18 environ['wsgi.version'] = (1,0)
19 environ['wsgi.multithread'] = False
20 environ['wsgi.multiprocess'] = True
21 environ['wsgi.run_once'] = True
22
23 if environ.get('HTTPS','off') in ('on','1'):
24 environ['wsgi.url_scheme'] = 'https'
25 else:
26 environ['wsgi.url_scheme'] = 'http'
27
28 headers_set = []
29 headers_sent = []
30
31 def write(data):
32 if not headers_set:
33 raise AssertionError("write() before start_response()")
34
35 elif not headers_sent:
36 # Before the first output, send the stored headers
37 status, response_headers = headers_sent[:] = headers_set
38 sys.stdout.write('Status: %s\r\n' % status)
39 for header in response_headers:
40 sys.stdout.write('%s: %s\r\n' % header)
41 sys.stdout.write('\r\n')
42
43 sys.stdout.write(data)
44 sys.stdout.flush()
45
46 def start_response(status,response_headers,exc_info=None):
47 if exc_info:
48 try:
49 if headers_sent:
50 # Re-raise original exception if headers sent
51 raise exc_info[0], exc_info[1], exc_info[2]
52 finally:
53 exc_info = None # avoid dangling circular ref
54 elif headers_set:
55 raise AssertionError("Headers already set!")
56
57 headers_set[:] = [status,response_headers]
58 return write
59
60 result = application(environ, start_response)
61 try:
62 for data in result:
63 if data: # don't send headers until body appears
64 write(data)
65 if not headers_sent:
66 write('') # send headers now if body was empty
67 finally:
68 if hasattr(result,'close'):
69 result.close()
@@ -0,0 +1,81 b''
1 #!/bin/sh
2
3 hg init a
4 echo line 1 > a/a
5 hg --cwd a ci -d '0 0' -Ama
6
7 echo line 2 >> a/a
8 hg --cwd a ci -u someone -d '1 0' -m'second change'
9
10 echo % import exported patch
11 hg clone -r0 a b
12 hg --cwd a export tip > tip.patch
13 hg --cwd b import ../tip.patch
14 echo % message should be same
15 hg --cwd b tip | grep 'second change'
16 echo % committer should be same
17 hg --cwd b tip | grep someone
18 rm -rf b
19
20 echo % import of plain diff should fail without message
21 hg clone -r0 a b
22 hg --cwd a diff -r0:1 > tip.patch
23 hg --cwd b import ../tip.patch
24 rm -rf b
25
26 echo % import of plain diff should be ok with message
27 hg clone -r0 a b
28 hg --cwd a diff -r0:1 > tip.patch
29 hg --cwd b import -mpatch ../tip.patch
30 rm -rf b
31
32 echo % import from stdin
33 hg clone -r0 a b
34 hg --cwd a export tip | hg --cwd b import -
35 rm -rf b
36
37 echo % override commit message
38 hg clone -r0 a b
39 hg --cwd a export tip | hg --cwd b import -m 'override' -
40 hg --cwd b tip | grep override
41 rm -rf b
42
43 cat > mkmsg.py <<EOF
44 import email.Message, sys
45 msg = email.Message.Message()
46 msg.set_payload('email commit message\n' + open('tip.patch').read())
47 msg['Subject'] = 'email patch'
48 msg['From'] = 'email patcher'
49 sys.stdout.write(msg.as_string())
50 EOF
51
52 echo % plain diff in email, subject, message body
53 hg clone -r0 a b
54 hg --cwd a diff -r0:1 > tip.patch
55 python mkmsg.py > msg.patch
56 hg --cwd b import ../msg.patch
57 hg --cwd b tip | grep email
58 rm -rf b
59
60 echo % plain diff in email, no subject, message body
61 hg clone -r0 a b
62 grep -v '^Subject:' msg.patch | hg --cwd b import -
63 rm -rf b
64
65 echo % plain diff in email, subject, no message body
66 hg clone -r0 a b
67 grep -v '^email ' msg.patch | hg --cwd b import -
68 rm -rf b
69
70 echo % plain diff in email, no subject, no message body, should fail
71 hg clone -r0 a b
72 grep -v '^\(Subject\|email\)' msg.patch | hg --cwd b import -
73 rm -rf b
74
75 echo % hg export in email, should use patch header
76 hg clone -r0 a b
77 hg --cwd a export tip > tip.patch
78 python mkmsg.py | hg --cwd b import -
79 hg --cwd b tip | grep second
80 rm -rf b
81
@@ -0,0 +1,103 b''
1 adding a
2 % import exported patch
3 requesting all changes
4 adding changesets
5 adding manifests
6 adding file changes
7 added 1 changesets with 1 changes to 1 files
8 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
9 applying ../tip.patch
10 patching file a
11 % message should be same
12 summary: second change
13 % committer should be same
14 user: someone
15 % import of plain diff should fail without message
16 requesting all changes
17 adding changesets
18 adding manifests
19 adding file changes
20 added 1 changesets with 1 changes to 1 files
21 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 applying ../tip.patch
23 patching file a
24 transaction abort!
25 rollback completed
26 % import of plain diff should be ok with message
27 requesting all changes
28 adding changesets
29 adding manifests
30 adding file changes
31 added 1 changesets with 1 changes to 1 files
32 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 applying ../tip.patch
34 patching file a
35 % import from stdin
36 requesting all changes
37 adding changesets
38 adding manifests
39 adding file changes
40 added 1 changesets with 1 changes to 1 files
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 applying patch from stdin
43 patching file a
44 % override commit message
45 requesting all changes
46 adding changesets
47 adding manifests
48 adding file changes
49 added 1 changesets with 1 changes to 1 files
50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 applying patch from stdin
52 patching file a
53 summary: override
54 % plain diff in email, subject, message body
55 requesting all changes
56 adding changesets
57 adding manifests
58 adding file changes
59 added 1 changesets with 1 changes to 1 files
60 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 applying ../msg.patch
62 patching file a
63 user: email patcher
64 summary: email patch
65 % plain diff in email, no subject, message body
66 requesting all changes
67 adding changesets
68 adding manifests
69 adding file changes
70 added 1 changesets with 1 changes to 1 files
71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 applying patch from stdin
73 patching file a
74 % plain diff in email, subject, no message body
75 requesting all changes
76 adding changesets
77 adding manifests
78 adding file changes
79 added 1 changesets with 1 changes to 1 files
80 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 applying patch from stdin
82 patching file a
83 % plain diff in email, no subject, no message body, should fail
84 requesting all changes
85 adding changesets
86 adding manifests
87 adding file changes
88 added 1 changesets with 1 changes to 1 files
89 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 applying patch from stdin
91 patching file a
92 transaction abort!
93 rollback completed
94 % hg export in email, should use patch header
95 requesting all changes
96 adding changesets
97 adding manifests
98 adding file changes
99 added 1 changesets with 1 changes to 1 files
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 applying patch from stdin
102 patching file a
103 summary: second change
@@ -0,0 +1,16 b''
1 #!/bin/sh
2
3 hg init
4 echo "test-parse-date" > a
5 hg add a
6 hg ci -d "2006-02-01 13:00:30" -m "rev 0"
7 echo "hi!" >> a
8 hg ci -d "2006-02-01 13:00:30 -0500" -m "rev 1"
9 hg tag -d "2006-04-15 13:30" "Hi"
10 hg backout --merge -d "2006-04-15 13:30 +0200" -m "rev 3" 1
11 hg ci -d "1150000000 14400" -m "rev 4 (merge)"
12 echo "fail" >> a
13 hg ci -d "should fail" -m "fail"
14 hg ci -d "100000000000000000 1400" -m "fail"
15 hg ci -d "100000 1400000" -m "fail"
16 hg log --template '{date|date}\n'
@@ -0,0 +1,19 b''
1 reverting a
2 changeset 3:107ce1ee2b43 backs out changeset 1:25a1420a55f8
3 merging with changeset 2:99a1acecff55
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 (branch merge, don't forget to commit)
6 abort: invalid date: 'should fail'
7 transaction abort!
8 rollback completed
9 abort: date exceeds 32 bits: 100000000000000000
10 transaction abort!
11 rollback completed
12 abort: impossible time zone offset: 1400000
13 transaction abort!
14 rollback completed
15 Sun Jun 11 00:26:40 2006 -0400
16 Sat Apr 15 13:30:00 2006 +0200
17 Sat Apr 15 13:30:00 2006 +0000
18 Wed Feb 01 13:00:30 2006 -0500
19 Wed Feb 01 13:00:30 2006 +0000
@@ -1,38 +1,35 b''
1 1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2 2 <html>
3 3 <head>
4 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5 5 <meta http-equiv="Content-Style-Type" content="text/css">
6 6 <title></title>
7 7 <style type="text/css">
8 8 p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
9 9 p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
10 10 p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
11 11 p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #000fed}
12 12 span.s1 {text-decoration: underline}
13 13 span.s2 {font: 12.0px Courier}
14 14 </style>
15 15 </head>
16 16 <body>
17 17 <p class="p1"><b>Before you install</b></p>
18 18 <p class="p2"><br></p>
19 19 <p class="p3">This is <i>not</i> a stand-alone version of Mercurial.</p>
20 20 <p class="p2"><br></p>
21 <p class="p3">To use it, you must have the β€œofficial unofficial” MacPython 2.4.1 installed.</p>
21 <p class="p3">To use it, you must have the Universal MacPython 2.4.3 from <a href="http://www.python.org">www.python.org</a> installed.</p>
22 22 <p class="p2"><br></p>
23 <p class="p3">You can download MacPython 2.4.1 from here:</p>
24 <p class="p4"><span class="s1"><a href="http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg">http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg</a></span></p>
25 <p class="p2"><br></p>
26 <p class="p3">For more information on MacPython, go here:</p>
27 <p class="p4"><span class="s1"><a href="http://undefined.org/python/">http://undefined.org/python</a></span></p>
23 <p class="p3">You can download MacPython 2.4.3 from here:</p>
24 <p class="p4"><span class="s1"><a href="http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg">http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg</a></span></p>
28 25 <p class="p2"><br></p>
29 26 <p class="p1"><b>After you install</b></p>
30 27 <p class="p2"><br></p>
31 28 <p class="p3">This package installs the <span class="s2">hg</span> executable in <span class="s2">/usr/local/bin</span>. This directory may not be in your shell's search path. Don't forget to check.</p>
32 29 <p class="p2"><br></p>
33 30 <p class="p1"><b>Reporting problems</b></p>
34 31 <p class="p2"><br></p>
35 32 <p class="p3">If you run into any problems, please file a bug online:</p>
36 33 <p class="p3"><a href="http://www.selenic.com/mercurial/bts">http://www.selenic.com/mercurial/bts</a></p>
37 34 </body>
38 35 </html>
@@ -1,17 +1,17 b''
1 1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2 2 <html>
3 3 <head>
4 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5 5 <meta http-equiv="Content-Style-Type" content="text/css">
6 6 <title></title>
7 7 <style type="text/css">
8 8 p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
9 9 p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
10 10 </style>
11 11 </head>
12 12 <body>
13 13 <p class="p1">This is a prepackaged release of <a href="http://www.selenic.com/mercurial">Mercurial</a> for Mac OS X.</p>
14 14 <p class="p2"><br></p>
15 <p class="p1">It is based on Mercurial 0.8.</p>
15 <p class="p1">It is based on Mercurial 0.9.</p>
16 16 </body>
17 17 </html>
@@ -1,1174 +1,1179 b''
1 1 ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
2 2
3 3 ;; Copyright (C) 2005 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 version 2 of the GNU General Public
9 9 ;; License as published by the Free Software Foundation.
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, write to the Free Software Foundation, Inc.,
19 19 ;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 20
21 21 ;;; Commentary:
22 22
23 23 ;; mercurial.el builds upon Emacs's VC mode to provide flexible
24 24 ;; integration with the Mercurial distributed SCM tool.
25 25
26 26 ;; To get going as quickly as possible, load mercurial.el into Emacs and
27 27 ;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
28 28 ;; usage overview.
29 29
30 30 ;; Much of the inspiration for mercurial.el comes from Rajesh
31 31 ;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
32 32 ;; job for the commercial Perforce SCM product. In fact, substantial
33 33 ;; chunks of code are adapted from p4.el.
34 34
35 35 ;; This code has been developed under XEmacs 21.5, and may not work as
36 36 ;; well under GNU Emacs (albeit tested under 21.4). Patches to
37 37 ;; enhance the portability of this code, fix bugs, and add features
38 38 ;; are most welcome. You can clone a Mercurial repository for this
39 39 ;; package from http://www.serpentine.com/hg/hg-emacs
40 40
41 41 ;; Please send problem reports and suggestions to bos@serpentine.com.
42 42
43 43
44 44 ;;; Code:
45 45
46 46 (require 'advice)
47 47 (require 'cl)
48 48 (require 'diff-mode)
49 49 (require 'easymenu)
50 50 (require 'executable)
51 51 (require 'vc)
52 52
53 53
54 54 ;;; XEmacs has view-less, while GNU Emacs has view. Joy.
55 55
56 56 (condition-case nil
57 57 (require 'view-less)
58 58 (error nil))
59 59 (condition-case nil
60 60 (require 'view)
61 61 (error nil))
62 62
63 63
64 64 ;;; Variables accessible through the custom system.
65 65
66 66 (defgroup mercurial nil
67 67 "Mercurial distributed SCM."
68 68 :group 'tools)
69 69
70 70 (defcustom hg-binary
71 71 (or (executable-find "hg")
72 72 (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
73 73 (when (file-executable-p path)
74 74 (return path))))
75 75 "The path to Mercurial's hg executable."
76 76 :type '(file :must-match t)
77 77 :group 'mercurial)
78 78
79 79 (defcustom hg-mode-hook nil
80 80 "Hook run when a buffer enters hg-mode."
81 81 :type 'sexp
82 82 :group 'mercurial)
83 83
84 84 (defcustom hg-commit-mode-hook nil
85 85 "Hook run when a buffer is created to prepare a commit."
86 86 :type 'sexp
87 87 :group 'mercurial)
88 88
89 89 (defcustom hg-pre-commit-hook nil
90 90 "Hook run before a commit is performed.
91 91 If you want to prevent the commit from proceeding, raise an error."
92 92 :type 'sexp
93 93 :group 'mercurial)
94 94
95 95 (defcustom hg-log-mode-hook nil
96 96 "Hook run after a buffer is filled with log information."
97 97 :type 'sexp
98 98 :group 'mercurial)
99 99
100 100 (defcustom hg-global-prefix "\C-ch"
101 101 "The global prefix for Mercurial keymap bindings."
102 102 :type 'sexp
103 103 :group 'mercurial)
104 104
105 105 (defcustom hg-commit-allow-empty-message nil
106 106 "Whether to allow changes to be committed with empty descriptions."
107 107 :type 'boolean
108 108 :group 'mercurial)
109 109
110 110 (defcustom hg-commit-allow-empty-file-list nil
111 111 "Whether to allow changes to be committed without any modified files."
112 112 :type 'boolean
113 113 :group 'mercurial)
114 114
115 115 (defcustom hg-rev-completion-limit 100
116 116 "The maximum number of revisions that hg-read-rev will offer to complete.
117 117 This affects memory usage and performance when prompting for revisions
118 118 in a repository with a lot of history."
119 119 :type 'integer
120 120 :group 'mercurial)
121 121
122 122 (defcustom hg-log-limit 50
123 123 "The maximum number of revisions that hg-log will display."
124 124 :type 'integer
125 125 :group 'mercurial)
126 126
127 127 (defcustom hg-update-modeline t
128 128 "Whether to update the modeline with the status of a file after every save.
129 129 Set this to nil on platforms with poor process management, such as Windows."
130 130 :type 'boolean
131 131 :group 'mercurial)
132 132
133 133 (defcustom hg-incoming-repository "default"
134 134 "The repository from which changes are pulled from by default.
135 135 This should be a symbolic repository name, since it is used for all
136 136 repository-related commands."
137 137 :type 'string
138 138 :group 'mercurial)
139 139
140 140 (defcustom hg-outgoing-repository "default-push"
141 141 "The repository to which changes are pushed to 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
148 148 ;;; Other variables.
149 149
150 150 (defconst hg-running-xemacs (string-match "XEmacs" emacs-version)
151 151 "Is mercurial.el running under XEmacs?")
152 152
153 153 (defvar hg-mode nil
154 154 "Is this file managed by Mercurial?")
155 155 (make-variable-buffer-local 'hg-mode)
156 156 (put 'hg-mode 'permanent-local t)
157 157
158 158 (defvar hg-status nil)
159 159 (make-variable-buffer-local 'hg-status)
160 160 (put 'hg-status 'permanent-local t)
161 161
162 162 (defvar hg-prev-buffer nil)
163 163 (make-variable-buffer-local 'hg-prev-buffer)
164 164 (put 'hg-prev-buffer 'permanent-local t)
165 165
166 166 (defvar hg-root nil)
167 167 (make-variable-buffer-local 'hg-root)
168 168 (put 'hg-root 'permanent-local t)
169 169
170 170 (defvar hg-output-buffer-name "*Hg*"
171 171 "The name to use for Mercurial output buffers.")
172 172
173 173 (defvar hg-file-history nil)
174 174 (defvar hg-repo-history nil)
175 175 (defvar hg-rev-history nil)
176 176
177 177
178 178 ;;; Random constants.
179 179
180 180 (defconst hg-commit-message-start
181 181 "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
182 182
183 183 (defconst hg-commit-message-end
184 184 "--- Files in bold will be committed. Click to toggle selection. ---\n")
185 185
186 186
187 187 ;;; hg-mode keymap.
188 188
189 189 (defvar hg-mode-map (make-sparse-keymap))
190 190 (define-key hg-mode-map "\C-xv" 'hg-prefix-map)
191 191
192 192 (defvar hg-prefix-map
193 193 (let ((map (copy-keymap vc-prefix-map)))
194 194 (if (functionp 'set-keymap-name)
195 195 (set-keymap-name map 'hg-prefix-map)); XEmacs
196 196 map)
197 197 "This keymap overrides some default vc-mode bindings.")
198 198 (fset 'hg-prefix-map hg-prefix-map)
199 199 (define-key hg-prefix-map "=" 'hg-diff)
200 200 (define-key hg-prefix-map "c" 'hg-undo)
201 201 (define-key hg-prefix-map "g" 'hg-annotate)
202 202 (define-key hg-prefix-map "l" 'hg-log)
203 203 (define-key hg-prefix-map "n" 'hg-commit-start)
204 204 ;; (define-key hg-prefix-map "r" 'hg-update)
205 205 (define-key hg-prefix-map "u" 'hg-revert-buffer)
206 206 (define-key hg-prefix-map "~" 'hg-version-other-window)
207 207
208 208 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
209 209
210 210
211 211 ;;; Global keymap.
212 212
213 213 (global-set-key "\C-xvi" 'hg-add)
214 214
215 215 (defvar hg-global-map (make-sparse-keymap))
216 216 (fset 'hg-global-map hg-global-map)
217 217 (global-set-key hg-global-prefix 'hg-global-map)
218 218 (define-key hg-global-map "," 'hg-incoming)
219 219 (define-key hg-global-map "." 'hg-outgoing)
220 220 (define-key hg-global-map "<" 'hg-pull)
221 221 (define-key hg-global-map "=" 'hg-diff-repo)
222 222 (define-key hg-global-map ">" 'hg-push)
223 223 (define-key hg-global-map "?" 'hg-help-overview)
224 224 (define-key hg-global-map "A" 'hg-addremove)
225 225 (define-key hg-global-map "U" 'hg-revert)
226 226 (define-key hg-global-map "a" 'hg-add)
227 227 (define-key hg-global-map "c" 'hg-commit-start)
228 228 (define-key hg-global-map "f" 'hg-forget)
229 229 (define-key hg-global-map "h" 'hg-help-overview)
230 230 (define-key hg-global-map "i" 'hg-init)
231 231 (define-key hg-global-map "l" 'hg-log-repo)
232 232 (define-key hg-global-map "r" 'hg-root)
233 233 (define-key hg-global-map "s" 'hg-status)
234 234 (define-key hg-global-map "u" 'hg-update)
235 235
236 236
237 237 ;;; View mode keymap.
238 238
239 239 (defvar hg-view-mode-map
240 240 (let ((map (copy-keymap (if (boundp 'view-minor-mode-map)
241 241 view-minor-mode-map
242 242 view-mode-map))))
243 243 (if (functionp 'set-keymap-name)
244 244 (set-keymap-name map 'hg-view-mode-map)); XEmacs
245 245 map))
246 246 (fset 'hg-view-mode-map hg-view-mode-map)
247 247 (define-key hg-view-mode-map
248 248 (if hg-running-xemacs [button2] [mouse-2])
249 249 'hg-buffer-mouse-clicked)
250 250
251 251
252 252 ;;; Commit mode keymaps.
253 253
254 254 (defvar hg-commit-mode-map (make-sparse-keymap))
255 255 (define-key hg-commit-mode-map "\C-c\C-c" 'hg-commit-finish)
256 256 (define-key hg-commit-mode-map "\C-c\C-k" 'hg-commit-kill)
257 257 (define-key hg-commit-mode-map "\C-xv=" 'hg-diff-repo)
258 258
259 259 (defvar hg-commit-mode-file-map (make-sparse-keymap))
260 260 (define-key hg-commit-mode-file-map
261 261 (if hg-running-xemacs [button2] [mouse-2])
262 262 'hg-commit-mouse-clicked)
263 263 (define-key hg-commit-mode-file-map " " 'hg-commit-toggle-file)
264 264 (define-key hg-commit-mode-file-map "\r" 'hg-commit-toggle-file)
265 265
266 266
267 267 ;;; Convenience functions.
268 268
269 269 (defsubst hg-binary ()
270 270 (if hg-binary
271 271 hg-binary
272 272 (error "No `hg' executable found!")))
273 273
274 274 (defsubst hg-replace-in-string (str regexp newtext &optional literal)
275 275 "Replace all matches in STR for REGEXP with NEWTEXT string.
276 276 Return the new string. Optional LITERAL non-nil means do a literal
277 277 replacement.
278 278
279 279 This function bridges yet another pointless impedance gap between
280 280 XEmacs and GNU Emacs."
281 281 (if (fboundp 'replace-in-string)
282 282 (replace-in-string str regexp newtext literal)
283 283 (replace-regexp-in-string regexp newtext str nil literal)))
284 284
285 285 (defsubst hg-strip (str)
286 286 "Strip leading and trailing blank lines from a string."
287 287 (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
288 288 "\\`[ \t\r\n]*[\r\n]" ""))
289 289
290 290 (defsubst hg-chomp (str)
291 291 "Strip trailing newlines from a string."
292 292 (hg-replace-in-string str "[\r\n]+\'" ""))
293 293
294 294 (defun hg-run-command (command &rest args)
295 295 "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
296 296 The list ARGS contains a list of arguments to pass to the command."
297 297 (let* (exit-code
298 298 (output
299 299 (with-output-to-string
300 300 (with-current-buffer
301 301 standard-output
302 302 (setq exit-code
303 303 (apply 'call-process command nil t nil args))))))
304 304 (cons exit-code output)))
305 305
306 306 (defun hg-run (command &rest args)
307 307 "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
308 308 (apply 'hg-run-command (hg-binary) command args))
309 309
310 310 (defun hg-run0 (command &rest args)
311 311 "Run the Mercurial command COMMAND, returning its output.
312 312 If the command does not exit with a zero status code, raise an error."
313 313 (let ((res (apply 'hg-run-command (hg-binary) command args)))
314 314 (if (not (eq (car res) 0))
315 315 (error "Mercurial command failed %s - exit code %s"
316 316 (cons command args)
317 317 (car res))
318 318 (cdr res))))
319 319
320 320 (defun hg-sync-buffers (path)
321 321 "Sync buffers visiting PATH with their on-disk copies.
322 322 If PATH is not being visited, but is under the repository root, sync
323 323 all buffers visiting files in the repository."
324 324 (let ((buf (find-buffer-visiting path)))
325 325 (if buf
326 326 (with-current-buffer buf
327 327 (vc-buffer-sync))
328 328 (hg-do-across-repo path
329 329 (vc-buffer-sync)))))
330 330
331 331 (defun hg-buffer-commands (pnt)
332 332 "Use the properties of a character to do something sensible."
333 333 (interactive "d")
334 334 (let ((rev (get-char-property pnt 'rev))
335 335 (file (get-char-property pnt 'file))
336 336 (date (get-char-property pnt 'date))
337 337 (user (get-char-property pnt 'user))
338 338 (host (get-char-property pnt 'host))
339 339 (prev-buf (current-buffer)))
340 340 (cond
341 341 (file
342 342 (find-file-other-window file))
343 343 (rev
344 344 (hg-diff hg-view-file-name rev rev prev-buf))
345 345 ((message "I don't know how to do that yet")))))
346 346
347 347 (defsubst hg-event-point (event)
348 348 "Return the character position of the mouse event EVENT."
349 349 (if hg-running-xemacs
350 350 (event-point event)
351 351 (posn-point (event-start event))))
352 352
353 353 (defsubst hg-event-window (event)
354 354 "Return the window over which mouse event EVENT occurred."
355 355 (if hg-running-xemacs
356 356 (event-window event)
357 357 (posn-window (event-start event))))
358 358
359 359 (defun hg-buffer-mouse-clicked (event)
360 360 "Translate the mouse clicks in a HG log buffer to character events.
361 361 These are then handed off to `hg-buffer-commands'.
362 362
363 363 Handle frickin' frackin' gratuitous event-related incompatibilities."
364 364 (interactive "e")
365 365 (select-window (hg-event-window event))
366 366 (hg-buffer-commands (hg-event-point event)))
367 367
368 368 (unless (fboundp 'view-minor-mode)
369 369 (defun view-minor-mode (prev-buffer exit-func)
370 370 (view-mode)))
371 371
372 372 (defsubst hg-abbrev-file-name (file)
373 373 "Portable wrapper around abbreviate-file-name."
374 374 (if hg-running-xemacs
375 375 (abbreviate-file-name file t)
376 376 (abbreviate-file-name file)))
377 377
378 378 (defun hg-read-file-name (&optional prompt default)
379 379 "Read a file or directory name, or a pattern, to use with a command."
380 380 (save-excursion
381 381 (while hg-prev-buffer
382 382 (set-buffer hg-prev-buffer))
383 383 (let ((path (or default (buffer-file-name) default-directory)))
384 384 (if (or (not path) current-prefix-arg)
385 385 (expand-file-name
386 386 (eval (list* 'read-file-name
387 387 (format "File, directory or pattern%s: "
388 388 (or prompt ""))
389 389 (and path (file-name-directory path))
390 390 nil nil
391 391 (and path (file-name-nondirectory path))
392 392 (if hg-running-xemacs
393 393 (cons (quote 'hg-file-history) nil)
394 394 nil))))
395 395 path))))
396 396
397 397 (defun hg-read-number (&optional prompt default)
398 398 "Read a integer value."
399 399 (save-excursion
400 400 (if (or (not default) current-prefix-arg)
401 401 (string-to-number
402 402 (eval (list* 'read-string
403 403 (or prompt "")
404 404 (if default (cons (format "%d" default) nil) nil))))
405 405 default)))
406 406
407 407 (defun hg-read-config ()
408 408 "Return an alist of (key . value) pairs of Mercurial config data.
409 409 Each key is of the form (section . name)."
410 410 (let (items)
411 411 (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
412 412 (string-match "^\\([^=]*\\)=\\(.*\\)" line)
413 413 (let* ((left (substring line (match-beginning 1) (match-end 1)))
414 414 (right (substring line (match-beginning 2) (match-end 2)))
415 415 (key (split-string left "\\."))
416 416 (value (hg-replace-in-string right "\\\\n" "\n" t)))
417 417 (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
418 418
419 419 (defun hg-config-section (section config)
420 420 "Return an alist of (name . value) pairs for SECTION of CONFIG."
421 421 (let (items)
422 422 (dolist (item config items)
423 423 (when (equal (caar item) section)
424 424 (setq items (cons (cons (cdar item) (cdr item)) items))))))
425 425
426 426 (defun hg-string-starts-with (sub str)
427 427 "Indicate whether string STR starts with the substring or character SUB."
428 428 (if (not (stringp sub))
429 429 (and (> (length str) 0) (equal (elt str 0) sub))
430 430 (let ((sub-len (length sub)))
431 431 (and (<= sub-len (length str))
432 432 (string= sub (substring str 0 sub-len))))))
433 433
434 434 (defun hg-complete-repo (string predicate all)
435 435 "Attempt to complete a repository name.
436 436 We complete on either symbolic names from Mercurial's config or real
437 437 directory names from the file system. We do not penalise URLs."
438 438 (or (if all
439 439 (all-completions string hg-repo-completion-table predicate)
440 440 (try-completion string hg-repo-completion-table predicate))
441 441 (let* ((str (expand-file-name string))
442 442 (dir (file-name-directory str))
443 443 (file (file-name-nondirectory str)))
444 444 (if all
445 445 (let (completions)
446 446 (dolist (name (delete "./" (file-name-all-completions file dir))
447 447 completions)
448 448 (let ((path (concat dir name)))
449 449 (when (file-directory-p path)
450 450 (setq completions (cons name completions))))))
451 451 (let ((comp (file-name-completion file dir)))
452 452 (if comp
453 453 (hg-abbrev-file-name (concat dir comp))))))))
454 454
455 455 (defun hg-read-repo-name (&optional prompt initial-contents default)
456 456 "Read the location of a repository."
457 457 (save-excursion
458 458 (while hg-prev-buffer
459 459 (set-buffer hg-prev-buffer))
460 460 (let (hg-repo-completion-table)
461 461 (if current-prefix-arg
462 462 (progn
463 463 (dolist (path (hg-config-section "paths" (hg-read-config)))
464 464 (setq hg-repo-completion-table
465 465 (cons (cons (car path) t) hg-repo-completion-table))
466 466 (unless (hg-string-starts-with directory-sep-char (cdr path))
467 467 (setq hg-repo-completion-table
468 468 (cons (cons (cdr path) t) hg-repo-completion-table))))
469 469 (completing-read (format "Repository%s: " (or prompt ""))
470 470 'hg-complete-repo
471 471 nil
472 472 nil
473 473 initial-contents
474 474 'hg-repo-history
475 475 default))
476 476 default))))
477 477
478 478 (defun hg-read-rev (&optional prompt default)
479 479 "Read a revision or tag, offering completions."
480 480 (save-excursion
481 481 (while hg-prev-buffer
482 482 (set-buffer hg-prev-buffer))
483 483 (let ((rev (or default "tip")))
484 484 (if current-prefix-arg
485 485 (let ((revs (split-string
486 486 (hg-chomp
487 487 (hg-run0 "-q" "log" "-r"
488 488 (format "-%d:tip" hg-rev-completion-limit)))
489 489 "[\n:]")))
490 490 (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
491 491 (setq revs (cons (car (split-string line "\\s-")) revs)))
492 492 (completing-read (format "Revision%s (%s): "
493 493 (or prompt "")
494 494 (or default "tip"))
495 495 (map 'list 'cons revs revs)
496 496 nil
497 497 nil
498 498 nil
499 499 'hg-rev-history
500 500 (or default "tip")))
501 501 rev))))
502 502
503 503 (defmacro hg-do-across-repo (path &rest body)
504 504 (let ((root-name (gensym "root-"))
505 505 (buf-name (gensym "buf-")))
506 506 `(let ((,root-name (hg-root ,path)))
507 507 (save-excursion
508 508 (dolist (,buf-name (buffer-list))
509 509 (set-buffer ,buf-name)
510 510 (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
511 511 ,@body))))))
512 512
513 513 (put 'hg-do-across-repo 'lisp-indent-function 1)
514 514
515 515
516 516 ;;; View mode bits.
517 517
518 518 (defun hg-exit-view-mode (buf)
519 519 "Exit from hg-view-mode.
520 520 We delete the current window if entering hg-view-mode split the
521 521 current frame."
522 522 (when (and (eq buf (current-buffer))
523 523 (> (length (window-list)) 1))
524 524 (delete-window))
525 525 (when (buffer-live-p buf)
526 526 (kill-buffer buf)))
527 527
528 528 (defun hg-view-mode (prev-buffer &optional file-name)
529 529 (goto-char (point-min))
530 530 (set-buffer-modified-p nil)
531 531 (toggle-read-only t)
532 532 (view-minor-mode prev-buffer 'hg-exit-view-mode)
533 533 (use-local-map hg-view-mode-map)
534 534 (setq truncate-lines t)
535 535 (when file-name
536 536 (set (make-local-variable 'hg-view-file-name)
537 537 (hg-abbrev-file-name file-name))))
538 538
539 539 (defun hg-file-status (file)
540 540 "Return status of FILE, or nil if FILE does not exist or is unmanaged."
541 541 (let* ((s (hg-run "status" file))
542 542 (exit (car s))
543 543 (output (cdr s)))
544 544 (if (= exit 0)
545 545 (let ((state (assoc (substring output 0 (min (length output) 2))
546 546 '(("M " . modified)
547 547 ("A " . added)
548 548 ("R " . removed)
549 549 ("? " . nil)))))
550 550 (if state
551 551 (cdr state)
552 552 'normal)))))
553 553
554 554 (defun hg-tip ()
555 555 (split-string (hg-chomp (hg-run0 "-q" "tip")) ":"))
556 556
557 557 (defmacro hg-view-output (args &rest body)
558 558 "Execute BODY in a clean buffer, then quickly display that buffer.
559 559 If the buffer contains one line, its contents are displayed in the
560 560 minibuffer. Otherwise, the buffer is displayed in view-mode.
561 561 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
562 562 the name of the buffer to create, and FILE is the name of the file
563 563 being viewed."
564 564 (let ((prev-buf (gensym "prev-buf-"))
565 565 (v-b-name (car args))
566 566 (v-m-rest (cdr args)))
567 567 `(let ((view-buf-name ,v-b-name)
568 568 (,prev-buf (current-buffer)))
569 569 (get-buffer-create view-buf-name)
570 570 (kill-buffer view-buf-name)
571 571 (get-buffer-create view-buf-name)
572 572 (set-buffer view-buf-name)
573 573 (save-excursion
574 574 ,@body)
575 575 (case (count-lines (point-min) (point-max))
576 576 ((0)
577 577 (kill-buffer view-buf-name)
578 578 (message "(No output)"))
579 579 ((1)
580 580 (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
581 581 (kill-buffer view-buf-name)
582 582 (message "%s" msg)))
583 583 (t
584 584 (pop-to-buffer view-buf-name)
585 585 (setq hg-prev-buffer ,prev-buf)
586 586 (hg-view-mode ,prev-buf ,@v-m-rest))))))
587 587
588 588 (put 'hg-view-output 'lisp-indent-function 1)
589 589
590 590 ;;; Context save and restore across revert.
591 591
592 592 (defun hg-position-context (pos)
593 593 "Return information to help find the given position again."
594 594 (let* ((end (min (point-max) (+ pos 98))))
595 595 (list pos
596 596 (buffer-substring (max (point-min) (- pos 2)) end)
597 597 (- end pos))))
598 598
599 599 (defun hg-buffer-context ()
600 600 "Return information to help restore a user's editing context.
601 601 This is useful across reverts and merges, where a context is likely
602 602 to have moved a little, but not really changed."
603 603 (let ((point-context (hg-position-context (point)))
604 604 (mark-context (let ((mark (mark-marker)))
605 605 (and mark (hg-position-context mark)))))
606 606 (list point-context mark-context)))
607 607
608 608 (defun hg-find-context (ctx)
609 609 "Attempt to find a context in the given buffer.
610 610 Always returns a valid, hopefully sane, position."
611 611 (let ((pos (nth 0 ctx))
612 612 (str (nth 1 ctx))
613 613 (fixup (nth 2 ctx)))
614 614 (save-excursion
615 615 (goto-char (max (point-min) (- pos 15000)))
616 616 (if (and (not (equal str ""))
617 617 (search-forward str nil t))
618 618 (- (point) fixup)
619 619 (max pos (point-min))))))
620 620
621 621 (defun hg-restore-context (ctx)
622 622 "Attempt to restore the user's editing context."
623 623 (let ((point-context (nth 0 ctx))
624 624 (mark-context (nth 1 ctx)))
625 625 (goto-char (hg-find-context point-context))
626 626 (when mark-context
627 627 (set-mark (hg-find-context mark-context)))))
628 628
629 629
630 630 ;;; Hooks.
631 631
632 632 (defun hg-mode-line (&optional force)
633 633 "Update the modeline with the current status of a file.
634 634 An update occurs if optional argument FORCE is non-nil,
635 635 hg-update-modeline is non-nil, or we have not yet checked the state of
636 636 the file."
637 637 (when (and (hg-root) (or force hg-update-modeline (not hg-mode)))
638 638 (let ((status (hg-file-status buffer-file-name)))
639 639 (setq hg-status status
640 640 hg-mode (and status (concat " Hg:"
641 641 (car (hg-tip))
642 642 (cdr (assq status
643 643 '((normal . "")
644 644 (removed . "r")
645 645 (added . "a")
646 646 (modified . "m")))))))
647 647 status)))
648 648
649 649 (defun hg-mode (&optional toggle)
650 650 "Minor mode for Mercurial distributed SCM integration.
651 651
652 652 The Mercurial mode user interface is based on that of VC mode, so if
653 653 you're already familiar with VC, the same keybindings and functions
654 654 will generally work.
655 655
656 Below is a list of many common SCM tasks. In the list, `G/L'
656 Below is a list of many common SCM tasks. In the list, `G/L\'
657 657 indicates whether a key binding is global (G) to a repository or local
658 658 (L) to a file. Many commands take a prefix argument.
659 659
660 660 SCM Task G/L Key Binding Command Name
661 661 -------- --- ----------- ------------
662 662 Help overview (what you are reading) G C-c h h hg-help-overview
663 663
664 664 Tell Mercurial to manage a file G C-c h a hg-add
665 665 Commit changes to current file only L C-x v n hg-commit-start
666 666 Undo changes to file since commit L C-x v u hg-revert-buffer
667 667
668 668 Diff file vs last checkin L C-x v = hg-diff
669 669
670 670 View file change history L C-x v l hg-log
671 671 View annotated file L C-x v a hg-annotate
672 672
673 673 Diff repo vs last checkin G C-c h = hg-diff-repo
674 674 View status of files in repo G C-c h s hg-status
675 675 Commit all changes G C-c h c hg-commit-start
676 676
677 677 Undo all changes since last commit G C-c h U hg-revert
678 678 View repo change history G C-c h l hg-log-repo
679 679
680 680 See changes that can be pulled G C-c h , hg-incoming
681 681 Pull changes G C-c h < hg-pull
682 682 Update working directory after pull G C-c h u hg-update
683 683 See changes that can be pushed G C-c h . hg-outgoing
684 684 Push changes G C-c h > hg-push"
685 (unless vc-make-backup-files
686 (set (make-local-variable 'backup-inhibited) t))
685 687 (run-hooks 'hg-mode-hook))
686 688
687 689 (defun hg-find-file-hook ()
688 690 (when (hg-mode-line)
689 691 (hg-mode)))
690 692
691 693 (add-hook 'find-file-hooks 'hg-find-file-hook)
692 694
693 695 (defun hg-after-save-hook ()
694 696 (let ((old-status hg-status))
695 697 (hg-mode-line)
696 698 (if (and (not old-status) hg-status)
697 699 (hg-mode))))
698 700
699 701 (add-hook 'after-save-hook 'hg-after-save-hook)
700 702
701 703
702 704 ;;; User interface functions.
703 705
704 706 (defun hg-help-overview ()
705 707 "This is an overview of the Mercurial SCM mode for Emacs.
706 708
707 709 You can find the source code, license (GPL v2), and credits for this
708 710 code by typing `M-x find-library mercurial RET'."
709 711 (interactive)
710 712 (hg-view-output ("Mercurial Help Overview")
711 713 (insert (documentation 'hg-help-overview))
712 714 (let ((pos (point)))
713 715 (insert (documentation 'hg-mode))
714 716 (goto-char pos)
715 717 (end-of-line 1)
716 718 (delete-region pos (point)))
717 719 (cd (hg-root))))
718 720
719 721 (defun hg-add (path)
720 722 "Add PATH to the Mercurial repository on the next commit.
721 723 With a prefix argument, prompt for the path to add."
722 724 (interactive (list (hg-read-file-name " to add")))
723 725 (let ((buf (current-buffer))
724 726 (update (equal buffer-file-name path)))
725 727 (hg-view-output (hg-output-buffer-name)
726 728 (apply 'call-process (hg-binary) nil t nil (list "add" path))
727 729 ;; "hg add" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
728 730 (replace-regexp " \\.\\.." " " nil 0 (buffer-size))
729 731 (goto-char 0)
730 732 (cd (hg-root path)))
731 733 (when update
734 (unless vc-make-backup-files
735 (set (make-local-variable 'backup-inhibited) t))
732 736 (with-current-buffer buf
733 737 (hg-mode-line)))))
734 738
735 739 (defun hg-addremove ()
736 740 (interactive)
737 741 (error "not implemented"))
738 742
739 743 (defun hg-annotate ()
740 744 (interactive)
741 745 (error "not implemented"))
742 746
743 747 (defun hg-commit-toggle-file (pos)
744 748 "Toggle whether or not the file at POS will be committed."
745 749 (interactive "d")
746 750 (save-excursion
747 751 (goto-char pos)
748 752 (let ((face (get-text-property pos 'face))
749 753 (inhibit-read-only t)
750 754 bol)
751 755 (beginning-of-line)
752 756 (setq bol (+ (point) 4))
753 757 (end-of-line)
754 758 (if (eq face 'bold)
755 759 (progn
756 760 (remove-text-properties bol (point) '(face nil))
757 761 (message "%s will not be committed"
758 762 (buffer-substring bol (point))))
759 763 (add-text-properties bol (point) '(face bold))
760 764 (message "%s will be committed"
761 765 (buffer-substring bol (point)))))))
762 766
763 767 (defun hg-commit-mouse-clicked (event)
764 768 "Toggle whether or not the file at POS will be committed."
765 769 (interactive "@e")
766 770 (hg-commit-toggle-file (hg-event-point event)))
767 771
768 772 (defun hg-commit-kill ()
769 773 "Kill the commit currently being prepared."
770 774 (interactive)
771 775 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
772 776 (let ((buf hg-prev-buffer))
773 777 (kill-buffer nil)
774 778 (switch-to-buffer buf))))
775 779
776 780 (defun hg-commit-finish ()
777 781 "Finish preparing a commit, and perform the actual commit.
778 782 The hook hg-pre-commit-hook is run before anything else is done. If
779 783 the commit message is empty and hg-commit-allow-empty-message is nil,
780 784 an error is raised. If the list of files to commit is empty and
781 785 hg-commit-allow-empty-file-list is nil, an error is raised."
782 786 (interactive)
783 787 (let ((root hg-root))
784 788 (save-excursion
785 789 (run-hooks 'hg-pre-commit-hook)
786 790 (goto-char (point-min))
787 791 (search-forward hg-commit-message-start)
788 792 (let (message files)
789 793 (let ((start (point)))
790 794 (goto-char (point-max))
791 795 (search-backward hg-commit-message-end)
792 796 (setq message (hg-strip (buffer-substring start (point)))))
793 797 (when (and (= (length message) 0)
794 798 (not hg-commit-allow-empty-message))
795 799 (error "Cannot proceed - commit message is empty"))
796 800 (forward-line 1)
797 801 (beginning-of-line)
798 802 (while (< (point) (point-max))
799 803 (let ((pos (+ (point) 4)))
800 804 (end-of-line)
801 805 (when (eq (get-text-property pos 'face) 'bold)
802 806 (end-of-line)
803 807 (setq files (cons (buffer-substring pos (point)) files))))
804 808 (forward-line 1))
805 809 (when (and (= (length files) 0)
806 810 (not hg-commit-allow-empty-file-list))
807 811 (error "Cannot proceed - no files to commit"))
808 812 (setq message (concat message "\n"))
809 813 (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
810 814 (let ((buf hg-prev-buffer))
811 815 (kill-buffer nil)
812 816 (switch-to-buffer buf))
813 817 (hg-do-across-repo root
814 818 (hg-mode-line)))))
815 819
816 820 (defun hg-commit-mode ()
817 821 "Mode for describing a commit of changes to a Mercurial repository.
818 822 This involves two actions: describing the changes with a commit
819 823 message, and choosing the files to commit.
820 824
821 825 To describe the commit, simply type some text in the designated area.
822 826
823 827 By default, all modified, added and removed files are selected for
824 828 committing. Files that will be committed are displayed in bold face\;
825 829 those that will not are displayed in normal face.
826 830
827 831 To toggle whether a file will be committed, move the cursor over a
828 832 particular file and hit space or return. Alternatively, middle click
829 833 on the file.
830 834
831 835 Key bindings
832 836 ------------
833 837 \\[hg-commit-finish] proceed with commit
834 838 \\[hg-commit-kill] kill commit
835 839
836 840 \\[hg-diff-repo] view diff of pending changes"
837 841 (interactive)
838 842 (use-local-map hg-commit-mode-map)
839 843 (set-syntax-table text-mode-syntax-table)
840 844 (setq local-abbrev-table text-mode-abbrev-table
841 845 major-mode 'hg-commit-mode
842 846 mode-name "Hg-Commit")
843 847 (set-buffer-modified-p nil)
844 848 (setq buffer-undo-list nil)
845 849 (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
846 850
847 851 (defun hg-commit-start ()
848 852 "Prepare a commit of changes to the repository containing the current file."
849 853 (interactive)
850 854 (while hg-prev-buffer
851 855 (set-buffer hg-prev-buffer))
852 856 (let ((root (hg-root))
853 857 (prev-buffer (current-buffer))
854 858 modified-files)
855 859 (unless root
856 860 (error "Cannot commit outside a repository!"))
857 861 (hg-sync-buffers root)
858 862 (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
859 863 (when (and (= (length modified-files) 0)
860 864 (not hg-commit-allow-empty-file-list))
861 865 (error "No pending changes to commit"))
862 866 (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
863 867 (pop-to-buffer (get-buffer-create buf-name))
864 868 (when (= (point-min) (point-max))
865 869 (set (make-local-variable 'hg-root) root)
866 870 (setq hg-prev-buffer prev-buffer)
867 871 (insert "\n")
868 872 (let ((bol (point)))
869 873 (insert hg-commit-message-end)
870 874 (add-text-properties bol (point) '(face bold-italic)))
871 875 (let ((file-area (point)))
872 876 (insert modified-files)
873 877 (goto-char file-area)
874 878 (while (< (point) (point-max))
875 879 (let ((bol (point)))
876 880 (forward-char 1)
877 881 (insert " ")
878 882 (end-of-line)
879 883 (add-text-properties (+ bol 4) (point)
880 884 '(face bold mouse-face highlight)))
881 885 (forward-line 1))
882 886 (goto-char file-area)
883 887 (add-text-properties (point) (point-max)
884 888 `(keymap ,hg-commit-mode-file-map))
885 889 (goto-char (point-min))
886 890 (insert hg-commit-message-start)
887 891 (add-text-properties (point-min) (point) '(face bold-italic))
888 892 (insert "\n\n")
889 893 (forward-line -1)
890 894 (save-excursion
891 895 (goto-char (point-max))
892 896 (search-backward hg-commit-message-end)
893 897 (add-text-properties (match-beginning 0) (point-max)
894 898 '(read-only t))
895 899 (goto-char (point-min))
896 900 (search-forward hg-commit-message-start)
897 901 (add-text-properties (match-beginning 0) (match-end 0)
898 902 '(read-only t)))
899 903 (hg-commit-mode)
900 904 (cd root))))))
901 905
902 906 (defun hg-diff (path &optional rev1 rev2)
903 907 "Show the differences between REV1 and REV2 of PATH.
904 908 When called interactively, the default behaviour is to treat REV1 as
905 909 the \"parent\" revision, REV2 as the current edited version of the file, and
906 910 PATH as the file edited in the current buffer.
907 911 With a prefix argument, prompt for all of these."
908 912 (interactive (list (hg-read-file-name " to diff")
909 913 (let ((rev1 (hg-read-rev " to start with" 'parent)))
910 914 (and (not (eq rev1 'parent)) rev1))
911 915 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
912 916 (and (not (eq rev2 'working-dir)) rev2))))
913 917 (hg-sync-buffers path)
914 918 (let ((a-path (hg-abbrev-file-name path))
915 919 ;; none revision is specified explicitly
916 920 (none (and (not rev1) (not rev2)))
917 921 ;; only one revision is specified explicitly
918 922 (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
919 923 (and (not rev1) rev2)))
920 924 diff)
921 925 (hg-view-output ((cond
922 926 (none
923 927 (format "Mercurial: Diff against parent of %s" a-path))
924 928 (one
925 929 (format "Mercurial: Diff of rev %s of %s" one a-path))
926 930 (t
927 931 (format "Mercurial: Diff from rev %s to %s of %s"
928 932 rev1 rev2 a-path))))
929 933 (cond
930 934 (none
931 935 (call-process (hg-binary) nil t nil "diff" path))
932 936 (one
933 937 (call-process (hg-binary) nil t nil "diff" "-r" one path))
934 938 (t
935 939 (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
936 940 (diff-mode)
937 941 (setq diff (not (= (point-min) (point-max))))
938 942 (font-lock-fontify-buffer)
939 943 (cd (hg-root path)))
940 944 diff))
941 945
942 946 (defun hg-diff-repo (path &optional rev1 rev2)
943 947 "Show the differences between REV1 and REV2 of repository containing PATH.
944 948 When called interactively, the default behaviour is to treat REV1 as
945 949 the \"parent\" revision, REV2 as the current edited version of the file, and
946 950 PATH as the `hg-root' of the current buffer.
947 951 With a prefix argument, prompt for all of these."
948 952 (interactive (list (hg-read-file-name " to diff")
949 953 (let ((rev1 (hg-read-rev " to start with" 'parent)))
950 954 (and (not (eq rev1 'parent)) rev1))
951 955 (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
952 956 (and (not (eq rev2 'working-dir)) rev2))))
953 957 (hg-diff (hg-root path) rev1 rev2))
954 958
955 959 (defun hg-forget (path)
956 960 "Lose track of PATH, which has been added, but not yet committed.
957 961 This will prevent the file from being incorporated into the Mercurial
958 962 repository on the next commit.
959 963 With a prefix argument, prompt for the path to forget."
960 964 (interactive (list (hg-read-file-name " to forget")))
961 965 (let ((buf (current-buffer))
962 966 (update (equal buffer-file-name path)))
963 967 (hg-view-output (hg-output-buffer-name)
964 968 (apply 'call-process (hg-binary) nil t nil (list "forget" path))
965 969 ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
966 970 (replace-regexp " \\.\\.." " " nil 0 (buffer-size))
967 971 (goto-char 0)
968 972 (cd (hg-root path)))
969 973 (when update
970 974 (with-current-buffer buf
975 (set (make-local-variable 'backup-inhibited) nil)
971 976 (hg-mode-line)))))
972 977
973 978 (defun hg-incoming (&optional repo)
974 979 "Display changesets present in REPO that are not present locally."
975 980 (interactive (list (hg-read-repo-name " where changes would come from")))
976 981 (hg-view-output ((format "Mercurial: Incoming from %s to %s"
977 982 (hg-abbrev-file-name (hg-root))
978 983 (hg-abbrev-file-name
979 984 (or repo hg-incoming-repository))))
980 985 (call-process (hg-binary) nil t nil "incoming"
981 986 (or repo hg-incoming-repository))
982 987 (hg-log-mode)
983 988 (cd (hg-root))))
984 989
985 990 (defun hg-init ()
986 991 (interactive)
987 992 (error "not implemented"))
988 993
989 994 (defun hg-log-mode ()
990 995 "Mode for viewing a Mercurial change log."
991 996 (goto-char (point-min))
992 997 (when (looking-at "^searching for changes.*$")
993 998 (delete-region (match-beginning 0) (match-end 0)))
994 999 (run-hooks 'hg-log-mode-hook))
995 1000
996 1001 (defun hg-log (path &optional rev1 rev2 log-limit)
997 1002 "Display the revision history of PATH.
998 1003 History is displayed between REV1 and REV2.
999 1004 Number of displayed changesets is limited to LOG-LIMIT.
1000 1005 REV1 defaults to the tip, while
1001 1006 REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
1002 1007 LOG-LIMIT defaults to `hg-log-limit'.
1003 1008 With a prefix argument, prompt for each parameter."
1004 1009 (interactive (list (hg-read-file-name " to log")
1005 1010 (hg-read-rev " to start with"
1006 1011 "tip")
1007 1012 (hg-read-rev " to end with"
1008 1013 (format "%d" (- hg-rev-completion-limit)))
1009 1014 (hg-read-number "Output limited to: "
1010 1015 hg-log-limit)))
1011 1016 (let ((a-path (hg-abbrev-file-name path))
1012 1017 (r1 (or rev1 (format "-%d" hg-rev-completion-limit)))
1013 1018 (r2 (or rev2 rev1 "tip"))
1014 1019 (limit (format "%d" (or log-limit hg-log-limit))))
1015 1020 (hg-view-output ((if (equal r1 r2)
1016 1021 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
1017 1022 (format
1018 1023 "Mercurial: at most %s log(s) from rev %s to %s of %s"
1019 1024 limit r1 r2 a-path)))
1020 1025 (eval (list* 'call-process (hg-binary) nil t nil
1021 1026 "log"
1022 1027 "-r" (format "%s:%s" r1 r2)
1023 1028 "-l" limit
1024 1029 (if (> (length path) (length (hg-root path)))
1025 1030 (cons path nil)
1026 1031 nil)))
1027 1032 (hg-log-mode)
1028 1033 (cd (hg-root path)))))
1029 1034
1030 1035 (defun hg-log-repo (path &optional rev1 rev2 log-limit)
1031 1036 "Display the revision history of the repository containing PATH.
1032 1037 History is displayed between REV1 and REV2.
1033 1038 Number of displayed changesets is limited to LOG-LIMIT,
1034 1039 REV1 defaults to the tip, while
1035 1040 REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
1036 1041 LOG-LIMIT defaults to `hg-log-limit'.
1037 1042 With a prefix argument, prompt for each parameter."
1038 1043 (interactive (list (hg-read-file-name " to log")
1039 1044 (hg-read-rev " to start with"
1040 1045 "tip")
1041 1046 (hg-read-rev " to end with"
1042 1047 (format "%d" (- hg-rev-completion-limit)))
1043 1048 (hg-read-number "Output limited to: "
1044 1049 hg-log-limit)))
1045 1050 (hg-log (hg-root path) rev1 rev2 log-limit))
1046 1051
1047 1052 (defun hg-outgoing (&optional repo)
1048 1053 "Display changesets present locally that are not present in REPO."
1049 1054 (interactive (list (hg-read-repo-name " where changes would go to" nil
1050 1055 hg-outgoing-repository)))
1051 1056 (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
1052 1057 (hg-abbrev-file-name (hg-root))
1053 1058 (hg-abbrev-file-name
1054 1059 (or repo hg-outgoing-repository))))
1055 1060 (call-process (hg-binary) nil t nil "outgoing"
1056 1061 (or repo hg-outgoing-repository))
1057 1062 (hg-log-mode)
1058 1063 (cd (hg-root))))
1059 1064
1060 1065 (defun hg-pull (&optional repo)
1061 1066 "Pull changes from repository REPO.
1062 1067 This does not update the working directory."
1063 1068 (interactive (list (hg-read-repo-name " to pull from")))
1064 1069 (hg-view-output ((format "Mercurial: Pull to %s from %s"
1065 1070 (hg-abbrev-file-name (hg-root))
1066 1071 (hg-abbrev-file-name
1067 1072 (or repo hg-incoming-repository))))
1068 1073 (call-process (hg-binary) nil t nil "pull"
1069 1074 (or repo hg-incoming-repository))
1070 1075 (cd (hg-root))))
1071 1076
1072 1077 (defun hg-push (&optional repo)
1073 1078 "Push changes to repository REPO."
1074 1079 (interactive (list (hg-read-repo-name " to push to")))
1075 1080 (hg-view-output ((format "Mercurial: Push from %s to %s"
1076 1081 (hg-abbrev-file-name (hg-root))
1077 1082 (hg-abbrev-file-name
1078 1083 (or repo hg-outgoing-repository))))
1079 1084 (call-process (hg-binary) nil t nil "push"
1080 1085 (or repo hg-outgoing-repository))
1081 1086 (cd (hg-root))))
1082 1087
1083 1088 (defun hg-revert-buffer-internal ()
1084 1089 (let ((ctx (hg-buffer-context)))
1085 1090 (message "Reverting %s..." buffer-file-name)
1086 1091 (hg-run0 "revert" buffer-file-name)
1087 1092 (revert-buffer t t t)
1088 1093 (hg-restore-context ctx)
1089 1094 (hg-mode-line)
1090 1095 (message "Reverting %s...done" buffer-file-name)))
1091 1096
1092 1097 (defun hg-revert-buffer ()
1093 1098 "Revert current buffer's file back to the latest committed version.
1094 1099 If the file has not changed, nothing happens. Otherwise, this
1095 1100 displays a diff and asks for confirmation before reverting."
1096 1101 (interactive)
1097 1102 (let ((vc-suppress-confirm nil)
1098 1103 (obuf (current-buffer))
1099 1104 diff)
1100 1105 (vc-buffer-sync)
1101 1106 (unwind-protect
1102 1107 (setq diff (hg-diff buffer-file-name))
1103 1108 (when diff
1104 1109 (unless (yes-or-no-p "Discard changes? ")
1105 1110 (error "Revert cancelled")))
1106 1111 (when diff
1107 1112 (let ((buf (current-buffer)))
1108 1113 (delete-window (selected-window))
1109 1114 (kill-buffer buf))))
1110 1115 (set-buffer obuf)
1111 1116 (when diff
1112 1117 (hg-revert-buffer-internal))))
1113 1118
1114 1119 (defun hg-root (&optional path)
1115 1120 "Return the root of the repository that contains the given path.
1116 1121 If the path is outside a repository, return nil.
1117 1122 When called interactively, the root is printed. A prefix argument
1118 1123 prompts for a path to check."
1119 1124 (interactive (list (hg-read-file-name)))
1120 1125 (if (or path (not hg-root))
1121 1126 (let ((root (do ((prev nil dir)
1122 1127 (dir (file-name-directory
1123 1128 (or
1124 1129 path
1125 1130 buffer-file-name
1126 1131 (expand-file-name default-directory)))
1127 1132 (file-name-directory (directory-file-name dir))))
1128 1133 ((equal prev dir))
1129 1134 (when (file-directory-p (concat dir ".hg"))
1130 1135 (return dir)))))
1131 1136 (when (interactive-p)
1132 1137 (if root
1133 1138 (message "The root of this repository is `%s'." root)
1134 1139 (message "The path `%s' is not in a Mercurial repository."
1135 1140 (hg-abbrev-file-name path))))
1136 1141 root)
1137 1142 hg-root))
1138 1143
1139 1144 (defun hg-status (path)
1140 1145 "Print revision control status of a file or directory.
1141 1146 With prefix argument, prompt for the path to give status for.
1142 1147 Names are displayed relative to the repository root."
1143 1148 (interactive (list (hg-read-file-name " for status" (hg-root))))
1144 1149 (let ((root (hg-root)))
1145 1150 (hg-view-output ((format "Mercurial: Status of %s in %s"
1146 1151 (let ((name (substring (expand-file-name path)
1147 1152 (length root))))
1148 1153 (if (> (length name) 0)
1149 1154 name
1150 1155 "*"))
1151 1156 (hg-abbrev-file-name root)))
1152 1157 (apply 'call-process (hg-binary) nil t nil
1153 1158 (list "--cwd" root "status" path))
1154 1159 (cd (hg-root path)))))
1155 1160
1156 1161 (defun hg-undo ()
1157 1162 (interactive)
1158 1163 (error "not implemented"))
1159 1164
1160 1165 (defun hg-update ()
1161 1166 (interactive)
1162 1167 (error "not implemented"))
1163 1168
1164 1169 (defun hg-version-other-window ()
1165 1170 (interactive)
1166 1171 (error "not implemented"))
1167 1172
1168 1173
1169 1174 (provide 'mercurial)
1170 1175
1171 1176
1172 1177 ;;; Local Variables:
1173 1178 ;;; prompt-to-byte-compile: nil
1174 1179 ;;; end:
@@ -1,1315 +1,1322 b''
1 1 # queue.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from mercurial.demandload import *
9 9 demandload(globals(), "os sys re struct traceback errno bz2")
10 10 from mercurial.i18n import gettext as _
11 11 from mercurial import ui, hg, revlog, commands, util
12 12
13 13 versionstr = "0.45"
14 14
15 15 repomap = {}
16 16
17 17 commands.norepo += " qversion"
18 18 class queue:
19 19 def __init__(self, ui, path, patchdir=None):
20 20 self.basepath = path
21 21 if patchdir:
22 22 self.path = patchdir
23 23 else:
24 24 self.path = os.path.join(path, "patches")
25 25 self.opener = util.opener(self.path)
26 26 self.ui = ui
27 27 self.applied = []
28 28 self.full_series = []
29 29 self.applied_dirty = 0
30 30 self.series_dirty = 0
31 31 self.series_path = "series"
32 32 self.status_path = "status"
33 33
34 34 if os.path.exists(os.path.join(self.path, self.series_path)):
35 35 self.full_series = self.opener(self.series_path).read().splitlines()
36 36 self.read_series(self.full_series)
37 37
38 38 if os.path.exists(os.path.join(self.path, self.status_path)):
39 39 self.applied = self.opener(self.status_path).read().splitlines()
40 40
41 41 def find_series(self, patch):
42 42 pre = re.compile("(\s*)([^#]+)")
43 43 index = 0
44 44 for l in self.full_series:
45 45 m = pre.match(l)
46 46 if m:
47 47 s = m.group(2)
48 48 s = s.rstrip()
49 49 if s == patch:
50 50 return index
51 51 index += 1
52 52 return None
53 53
54 54 def read_series(self, list):
55 55 def matcher(list):
56 56 pre = re.compile("(\s*)([^#]+)")
57 57 for l in list:
58 58 m = pre.match(l)
59 59 if m:
60 60 s = m.group(2)
61 61 s = s.rstrip()
62 62 if len(s) > 0:
63 63 yield s
64 64 self.series = []
65 65 self.series = [ x for x in matcher(list) ]
66 66
67 67 def save_dirty(self):
68 68 if self.applied_dirty:
69 69 if len(self.applied) > 0:
70 70 nl = "\n"
71 71 else:
72 72 nl = ""
73 73 f = self.opener(self.status_path, "w")
74 74 f.write("\n".join(self.applied) + nl)
75 75 if self.series_dirty:
76 76 if len(self.full_series) > 0:
77 77 nl = "\n"
78 78 else:
79 79 nl = ""
80 80 f = self.opener(self.series_path, "w")
81 81 f.write("\n".join(self.full_series) + nl)
82 82
83 83 def readheaders(self, patch):
84 84 def eatdiff(lines):
85 85 while lines:
86 86 l = lines[-1]
87 87 if (l.startswith("diff -") or
88 88 l.startswith("Index:") or
89 89 l.startswith("===========")):
90 90 del lines[-1]
91 91 else:
92 92 break
93 93 def eatempty(lines):
94 94 while lines:
95 95 l = lines[-1]
96 96 if re.match('\s*$', l):
97 97 del lines[-1]
98 98 else:
99 99 break
100 100
101 101 pf = os.path.join(self.path, patch)
102 102 message = []
103 103 comments = []
104 104 user = None
105 105 date = None
106 106 format = None
107 107 subject = None
108 108 diffstart = 0
109 109
110 110 for line in file(pf):
111 111 line = line.rstrip()
112 112 if diffstart:
113 113 if line.startswith('+++ '):
114 114 diffstart = 2
115 115 break
116 116 if line.startswith("--- "):
117 117 diffstart = 1
118 118 continue
119 119 elif format == "hgpatch":
120 120 # parse values when importing the result of an hg export
121 121 if line.startswith("# User "):
122 122 user = line[7:]
123 123 elif line.startswith("# Date "):
124 124 date = line[7:]
125 125 elif not line.startswith("# ") and line:
126 126 message.append(line)
127 127 format = None
128 128 elif line == '# HG changeset patch':
129 129 format = "hgpatch"
130 130 elif (format != "tagdone" and (line.startswith("Subject: ") or
131 131 line.startswith("subject: "))):
132 132 subject = line[9:]
133 133 format = "tag"
134 134 elif (format != "tagdone" and (line.startswith("From: ") or
135 135 line.startswith("from: "))):
136 136 user = line[6:]
137 137 format = "tag"
138 138 elif format == "tag" and line == "":
139 139 # when looking for tags (subject: from: etc) they
140 140 # end once you find a blank line in the source
141 141 format = "tagdone"
142 142 elif message or line:
143 143 message.append(line)
144 144 comments.append(line)
145 145
146 146 eatdiff(message)
147 147 eatdiff(comments)
148 148 eatempty(message)
149 149 eatempty(comments)
150 150
151 151 # make sure message isn't empty
152 152 if format and format.startswith("tag") and subject:
153 153 message.insert(0, "")
154 154 message.insert(0, subject)
155 155 return (message, comments, user, date, diffstart > 1)
156 156
157 157 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
158 158 # first try just applying the patch
159 159 (err, n) = self.apply(repo, [ patch ], update_status=False,
160 160 strict=True, merge=rev, wlock=wlock)
161 161
162 162 if err == 0:
163 163 return (err, n)
164 164
165 165 if n is None:
166 166 self.ui.warn("apply failed for patch %s\n" % patch)
167 167 sys.exit(1)
168 168
169 169 self.ui.warn("patch didn't work out, merging %s\n" % patch)
170 170
171 171 # apply failed, strip away that rev and merge.
172 172 repo.update(head, allow=False, force=True, wlock=wlock)
173 173 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
174 174
175 175 c = repo.changelog.read(rev)
176 176 ret = repo.update(rev, allow=True, wlock=wlock)
177 177 if ret:
178 178 self.ui.warn("update returned %d\n" % ret)
179 179 sys.exit(1)
180 180 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
181 181 if n == None:
182 182 self.ui.warn("repo commit failed\n")
183 183 sys.exit(1)
184 184 try:
185 185 message, comments, user, date, patchfound = mergeq.readheaders(patch)
186 186 except:
187 187 self.ui.warn("Unable to read %s\n" % patch)
188 188 sys.exit(1)
189 189
190 190 patchf = self.opener(patch, "w")
191 191 if comments:
192 192 comments = "\n".join(comments) + '\n\n'
193 193 patchf.write(comments)
194 194 commands.dodiff(patchf, self.ui, repo, head, n)
195 195 patchf.close()
196 196 return (0, n)
197 197
198 198 def qparents(self, repo, rev=None):
199 199 if rev is None:
200 200 (p1, p2) = repo.dirstate.parents()
201 201 if p2 == revlog.nullid:
202 202 return p1
203 203 if len(self.applied) == 0:
204 204 return None
205 205 (top, patch) = self.applied[-1].split(':')
206 206 top = revlog.bin(top)
207 207 return top
208 208 pp = repo.changelog.parents(rev)
209 209 if pp[1] != revlog.nullid:
210 210 arevs = [ x.split(':')[0] for x in self.applied ]
211 211 p0 = revlog.hex(pp[0])
212 212 p1 = revlog.hex(pp[1])
213 213 if p0 in arevs:
214 214 return pp[0]
215 215 if p1 in arevs:
216 216 return pp[1]
217 return None
218 217 return pp[0]
219 218
220 219 def mergepatch(self, repo, mergeq, series, wlock):
221 220 if len(self.applied) == 0:
222 221 # each of the patches merged in will have two parents. This
223 222 # can confuse the qrefresh, qdiff, and strip code because it
224 223 # needs to know which parent is actually in the patch queue.
225 224 # so, we insert a merge marker with only one parent. This way
226 225 # the first patch in the queue is never a merge patch
227 226 #
228 227 pname = ".hg.patches.merge.marker"
229 228 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
230 229 wlock=wlock)
231 230 self.applied.append(revlog.hex(n) + ":" + pname)
232 231 self.applied_dirty = 1
233 232
234 233 head = self.qparents(repo)
235 234
236 235 for patch in series:
237 236 patch = mergeq.lookup(patch)
238 237 if not patch:
239 238 self.ui.warn("patch %s does not exist\n" % patch)
240 239 return (1, None)
241 240
242 241 info = mergeq.isapplied(patch)
243 242 if not info:
244 243 self.ui.warn("patch %s is not applied\n" % patch)
245 244 return (1, None)
246 245 rev = revlog.bin(info[1])
247 246 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
248 247 if head:
249 248 self.applied.append(revlog.hex(head) + ":" + patch)
250 249 self.applied_dirty = 1
251 250 if err:
252 251 return (err, head)
253 252 return (0, head)
254 253
255 254 def apply(self, repo, series, list=False, update_status=True,
256 255 strict=False, patchdir=None, merge=None, wlock=None):
257 256 # TODO unify with commands.py
258 257 if not patchdir:
259 258 patchdir = self.path
260 259 pwd = os.getcwd()
261 260 os.chdir(repo.root)
262 261 err = 0
263 262 if not wlock:
264 263 wlock = repo.wlock()
265 264 lock = repo.lock()
266 265 tr = repo.transaction()
267 266 n = None
268 267 for patch in series:
269 268 self.ui.warn("applying %s\n" % patch)
270 269 pf = os.path.join(patchdir, patch)
271 270
272 271 try:
273 272 message, comments, user, date, patchfound = self.readheaders(patch)
274 273 except:
275 274 self.ui.warn("Unable to read %s\n" % pf)
276 275 err = 1
277 276 break
278 277
279 278 if not message:
280 279 message = "imported patch %s\n" % patch
281 280 else:
282 281 if list:
283 282 message.append("\nimported patch %s" % patch)
284 283 message = '\n'.join(message)
285 284
286 285 try:
287 286 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
288 287 f = os.popen("%s -p1 --no-backup-if-mismatch < '%s'" % (pp, pf))
289 288 except:
290 289 self.ui.warn("patch failed, unable to continue (try -v)\n")
291 290 err = 1
292 291 break
293 292 files = []
294 293 fuzz = False
295 294 for l in f:
296 295 l = l.rstrip('\r\n');
297 296 if self.ui.verbose:
298 297 self.ui.warn(l + "\n")
299 298 if l[:14] == 'patching file ':
300 299 pf = os.path.normpath(l[14:])
301 300 # when patch finds a space in the file name, it puts
302 301 # single quotes around the filename. strip them off
303 302 if pf[0] == "'" and pf[-1] == "'":
304 303 pf = pf[1:-1]
305 304 if pf not in files:
306 305 files.append(pf)
307 306 printed_file = False
308 307 file_str = l
309 308 elif l.find('with fuzz') >= 0:
310 309 if not printed_file:
311 310 self.ui.warn(file_str + '\n')
312 311 printed_file = True
313 312 self.ui.warn(l + '\n')
314 313 fuzz = True
315 314 elif l.find('saving rejects to file') >= 0:
316 315 self.ui.warn(l + '\n')
317 316 elif l.find('FAILED') >= 0:
318 317 if not printed_file:
319 318 self.ui.warn(file_str + '\n')
320 319 printed_file = True
321 320 self.ui.warn(l + '\n')
322 321 patcherr = f.close()
323 322
324 323 if merge and len(files) > 0:
325 324 # Mark as merged and update dirstate parent info
326 325 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
327 326 p1, p2 = repo.dirstate.parents()
328 327 repo.dirstate.setparents(p1, merge)
329 328 if len(files) > 0:
330 329 commands.addremove_lock(self.ui, repo, files,
331 330 opts={}, wlock=wlock)
332 331 n = repo.commit(files, message, user, date, force=1, lock=lock,
333 332 wlock=wlock)
334 333
335 334 if n == None:
336 335 self.ui.warn("repo commit failed\n")
337 336 sys.exit(1)
338 337
339 338 if update_status:
340 339 self.applied.append(revlog.hex(n) + ":" + patch)
341 340
342 341 if patcherr:
343 342 if not patchfound:
344 343 self.ui.warn("patch %s is empty\n" % patch)
345 344 err = 0
346 345 else:
347 346 self.ui.warn("patch failed, rejects left in working dir\n")
348 347 err = 1
349 348 break
350 349
351 350 if fuzz and strict:
352 351 self.ui.warn("fuzz found when applying patch, stopping\n")
353 352 err = 1
354 353 break
355 354 tr.close()
356 355 os.chdir(pwd)
357 356 return (err, n)
358 357
359 358 def delete(self, repo, patch):
360 359 patch = self.lookup(patch)
361 360 info = self.isapplied(patch)
362 361 if info:
363 362 self.ui.warn("cannot delete applied patch %s\n" % patch)
364 363 sys.exit(1)
365 364 if patch not in self.series:
366 365 self.ui.warn("patch %s not in series file\n" % patch)
367 366 sys.exit(1)
368 367 i = self.find_series(patch)
369 368 del self.full_series[i]
370 369 self.read_series(self.full_series)
371 370 self.series_dirty = 1
372 371
373 372 def check_toppatch(self, repo):
374 373 if len(self.applied) > 0:
375 374 (top, patch) = self.applied[-1].split(':')
376 375 top = revlog.bin(top)
377 376 pp = repo.dirstate.parents()
378 377 if top not in pp:
379 378 self.ui.warn("queue top not at dirstate parents. top %s dirstate %s %s\n" %( revlog.short(top), revlog.short(pp[0]), revlog.short(pp[1])))
380 379 sys.exit(1)
381 380 return top
382 381 return None
383 382 def check_localchanges(self, repo):
384 383 (c, a, r, d, u) = repo.changes(None, None)
385 384 if c or a or d or r:
386 385 self.ui.write("Local changes found, refresh first\n")
387 386 sys.exit(1)
388 387 def new(self, repo, patch, msg=None, force=None):
388 commitfiles = []
389 (c, a, r, d, u) = repo.changes(None, None)
390 if c or a or d or r:
389 391 if not force:
390 self.check_localchanges(repo)
392 raise util.Abort(_("Local changes found, refresh first"))
393 else:
394 commitfiles = c + a + r
391 395 self.check_toppatch(repo)
392 396 wlock = repo.wlock()
393 397 insert = self.series_end()
394 398 if msg:
395 n = repo.commit([], "[mq]: %s" % msg, force=True, wlock=wlock)
399 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
400 wlock=wlock)
396 401 else:
397 n = repo.commit([],
402 n = repo.commit(commitfiles,
398 403 "New patch: %s" % patch, force=True, wlock=wlock)
399 404 if n == None:
400 405 self.ui.warn("repo commit failed\n")
401 406 sys.exit(1)
402 407 self.full_series[insert:insert] = [patch]
403 408 self.applied.append(revlog.hex(n) + ":" + patch)
404 409 self.read_series(self.full_series)
405 410 self.series_dirty = 1
406 411 self.applied_dirty = 1
407 412 p = self.opener(patch, "w")
408 413 if msg:
409 414 msg = msg + "\n"
410 415 p.write(msg)
411 416 p.close()
412 417 wlock = None
413 418 r = self.qrepo()
414 419 if r: r.add([patch])
420 if commitfiles:
421 self.refresh(repo, short=True)
415 422
416 423 def strip(self, repo, rev, update=True, backup="all", wlock=None):
417 424 def limitheads(chlog, stop):
418 425 """return the list of all nodes that have no children"""
419 426 p = {}
420 427 h = []
421 428 stoprev = 0
422 429 if stop in chlog.nodemap:
423 430 stoprev = chlog.rev(stop)
424 431
425 432 for r in range(chlog.count() - 1, -1, -1):
426 433 n = chlog.node(r)
427 434 if n not in p:
428 435 h.append(n)
429 436 if n == stop:
430 437 break
431 438 if r < stoprev:
432 439 break
433 440 for pn in chlog.parents(n):
434 441 p[pn] = 1
435 442 return h
436 443
437 444 def bundle(cg):
438 445 backupdir = repo.join("strip-backup")
439 446 if not os.path.isdir(backupdir):
440 447 os.mkdir(backupdir)
441 448 name = os.path.join(backupdir, "%s" % revlog.short(rev))
442 449 name = savename(name)
443 450 self.ui.warn("saving bundle to %s\n" % name)
444 451 # TODO, exclusive open
445 452 f = open(name, "wb")
446 453 try:
447 454 f.write("HG10")
448 455 z = bz2.BZ2Compressor(9)
449 456 while 1:
450 457 chunk = cg.read(4096)
451 458 if not chunk:
452 459 break
453 460 f.write(z.compress(chunk))
454 461 f.write(z.flush())
455 462 except:
456 463 os.unlink(name)
457 464 raise
458 465 f.close()
459 466 return name
460 467
461 468 def stripall(rev, revnum):
462 469 cl = repo.changelog
463 470 c = cl.read(rev)
464 471 mm = repo.manifest.read(c[0])
465 472 seen = {}
466 473
467 474 for x in xrange(revnum, cl.count()):
468 475 c = cl.read(cl.node(x))
469 476 for f in c[3]:
470 477 if f in seen:
471 478 continue
472 479 seen[f] = 1
473 480 if f in mm:
474 481 filerev = mm[f]
475 482 else:
476 483 filerev = 0
477 484 seen[f] = filerev
478 485 # we go in two steps here so the strip loop happens in a
479 486 # sensible order. When stripping many files, this helps keep
480 487 # our disk access patterns under control.
481 488 list = seen.keys()
482 489 list.sort()
483 490 for f in list:
484 491 ff = repo.file(f)
485 492 filerev = seen[f]
486 493 if filerev != 0:
487 494 if filerev in ff.nodemap:
488 495 filerev = ff.rev(filerev)
489 496 else:
490 497 filerev = 0
491 498 ff.strip(filerev, revnum)
492 499
493 500 if not wlock:
494 501 wlock = repo.wlock()
495 502 lock = repo.lock()
496 503 chlog = repo.changelog
497 504 # TODO delete the undo files, and handle undo of merge sets
498 505 pp = chlog.parents(rev)
499 506 revnum = chlog.rev(rev)
500 507
501 508 if update:
502 509 urev = self.qparents(repo, rev)
503 510 repo.update(urev, allow=False, force=True, wlock=wlock)
504 511 repo.dirstate.write()
505 512
506 513 # save is a list of all the branches we are truncating away
507 514 # that we actually want to keep. changegroup will be used
508 515 # to preserve them and add them back after the truncate
509 516 saveheads = []
510 517 savebases = {}
511 518
512 519 tip = chlog.tip()
513 520 heads = limitheads(chlog, rev)
514 521 seen = {}
515 522
516 523 # search through all the heads, finding those where the revision
517 524 # we want to strip away is an ancestor. Also look for merges
518 525 # that might be turned into new heads by the strip.
519 526 while heads:
520 527 h = heads.pop()
521 528 n = h
522 529 while True:
523 530 seen[n] = 1
524 531 pp = chlog.parents(n)
525 532 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
526 533 if pp[1] not in seen:
527 534 heads.append(pp[1])
528 535 if pp[0] == revlog.nullid:
529 536 break
530 537 if chlog.rev(pp[0]) < revnum:
531 538 break
532 539 n = pp[0]
533 540 if n == rev:
534 541 break
535 542 r = chlog.reachable(h, rev)
536 543 if rev not in r:
537 544 saveheads.append(h)
538 545 for x in r:
539 546 if chlog.rev(x) > revnum:
540 547 savebases[x] = 1
541 548
542 549 # create a changegroup for all the branches we need to keep
543 550 if backup is "all":
544 551 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
545 552 bundle(backupch)
546 553 if saveheads:
547 554 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
548 555 chgrpfile = bundle(backupch)
549 556
550 557 stripall(rev, revnum)
551 558
552 559 change = chlog.read(rev)
553 560 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
554 561 chlog.strip(revnum, revnum)
555 562 if saveheads:
556 563 self.ui.status("adding branch\n")
557 564 commands.unbundle(self.ui, repo, chgrpfile, update=False)
558 565 if backup is not "strip":
559 566 os.unlink(chgrpfile)
560 567
561 568 def isapplied(self, patch):
562 569 """returns (index, rev, patch)"""
563 570 for i in xrange(len(self.applied)):
564 571 p = self.applied[i]
565 572 a = p.split(':')
566 573 if a[1] == patch:
567 574 return (i, a[0], a[1])
568 575 return None
569 576
570 577 def lookup(self, patch):
571 578 if patch == None:
572 579 return None
573 580 if patch in self.series:
574 581 return patch
575 582 if not os.path.isfile(os.path.join(self.path, patch)):
576 583 try:
577 584 sno = int(patch)
578 585 except(ValueError, OverflowError):
579 586 self.ui.warn("patch %s not in series\n" % patch)
580 587 sys.exit(1)
581 588 if sno >= len(self.series):
582 589 self.ui.warn("patch number %d is out of range\n" % sno)
583 590 sys.exit(1)
584 591 patch = self.series[sno]
585 592 else:
586 593 self.ui.warn("patch %s not in series\n" % patch)
587 594 sys.exit(1)
588 595 return patch
589 596
590 597 def push(self, repo, patch=None, force=False, list=False,
591 598 mergeq=None, wlock=None):
592 599 if not wlock:
593 600 wlock = repo.wlock()
594 601 patch = self.lookup(patch)
595 602 if patch and self.isapplied(patch):
596 603 self.ui.warn("patch %s is already applied\n" % patch)
597 604 sys.exit(1)
598 605 if self.series_end() == len(self.series):
599 606 self.ui.warn("File series fully applied\n")
600 607 sys.exit(1)
601 608 if not force:
602 609 self.check_localchanges(repo)
603 610
604 611 self.applied_dirty = 1;
605 612 start = self.series_end()
606 613 if start > 0:
607 614 self.check_toppatch(repo)
608 615 if not patch:
609 616 patch = self.series[start]
610 617 end = start + 1
611 618 else:
612 619 end = self.series.index(patch, start) + 1
613 620 s = self.series[start:end]
614 621 if mergeq:
615 622 ret = self.mergepatch(repo, mergeq, s, wlock)
616 623 else:
617 624 ret = self.apply(repo, s, list, wlock=wlock)
618 625 top = self.applied[-1].split(':')[1]
619 626 if ret[0]:
620 627 self.ui.write("Errors during apply, please fix and refresh %s\n" %
621 628 top)
622 629 else:
623 630 self.ui.write("Now at: %s\n" % top)
624 631 return ret[0]
625 632
626 633 def pop(self, repo, patch=None, force=False, update=True, wlock=None):
627 634 def getfile(f, rev):
628 635 t = repo.file(f).read(rev)
629 636 try:
630 637 repo.wfile(f, "w").write(t)
631 638 except IOError:
632 639 try:
633 640 os.makedirs(os.path.dirname(repo.wjoin(f)))
634 641 except OSError, err:
635 642 if err.errno != errno.EEXIST: raise
636 643 repo.wfile(f, "w").write(t)
637 644
638 645 if not wlock:
639 646 wlock = repo.wlock()
640 647 if patch:
641 648 # index, rev, patch
642 649 info = self.isapplied(patch)
643 650 if not info:
644 651 patch = self.lookup(patch)
645 652 info = self.isapplied(patch)
646 653 if not info:
647 654 self.ui.warn("patch %s is not applied\n" % patch)
648 655 sys.exit(1)
649 656 if len(self.applied) == 0:
650 657 self.ui.warn("No patches applied\n")
651 658 sys.exit(1)
652 659
653 660 if not update:
654 661 parents = repo.dirstate.parents()
655 662 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
656 663 for p in parents:
657 664 if p in rr:
658 665 self.ui.warn("qpop: forcing dirstate update\n")
659 666 update = True
660 667
661 668 if not force and update:
662 669 self.check_localchanges(repo)
663 670
664 671 self.applied_dirty = 1;
665 672 end = len(self.applied)
666 673 if not patch:
667 674 info = [len(self.applied) - 1] + self.applied[-1].split(':')
668 675 start = info[0]
669 676 rev = revlog.bin(info[1])
670 677
671 678 # we know there are no local changes, so we can make a simplified
672 679 # form of hg.update.
673 680 if update:
674 681 top = self.check_toppatch(repo)
675 682 qp = self.qparents(repo, rev)
676 683 changes = repo.changelog.read(qp)
677 684 mf1 = repo.manifest.readflags(changes[0])
678 685 mmap = repo.manifest.read(changes[0])
679 686 (c, a, r, d, u) = repo.changes(qp, top)
680 687 if d:
681 688 raise util.Abort("deletions found between repo revs")
682 689 for f in c:
683 690 getfile(f, mmap[f])
684 691 for f in r:
685 692 getfile(f, mmap[f])
686 693 util.set_exec(repo.wjoin(f), mf1[f])
687 694 repo.dirstate.update(c + r, 'n')
688 695 for f in a:
689 696 try: os.unlink(repo.wjoin(f))
690 697 except: raise
691 698 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
692 699 except: pass
693 700 if a:
694 701 repo.dirstate.forget(a)
695 702 repo.dirstate.setparents(qp, revlog.nullid)
696 703 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
697 704 del self.applied[start:end]
698 705 if len(self.applied):
699 706 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
700 707 else:
701 708 self.ui.write("Patch queue now empty\n")
702 709
703 710 def diff(self, repo, files):
704 711 top = self.check_toppatch(repo)
705 712 if not top:
706 713 self.ui.write("No patches applied\n")
707 714 return
708 715 qp = self.qparents(repo, top)
709 716 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
710 717
711 718 def refresh(self, repo, short=False):
712 719 if len(self.applied) == 0:
713 720 self.ui.write("No patches applied\n")
714 721 return
715 722 wlock = repo.wlock()
716 723 self.check_toppatch(repo)
717 724 qp = self.qparents(repo)
718 725 (top, patch) = self.applied[-1].split(':')
719 726 top = revlog.bin(top)
720 727 cparents = repo.changelog.parents(top)
721 728 patchparent = self.qparents(repo, top)
722 729 message, comments, user, date, patchfound = self.readheaders(patch)
723 730
724 731 patchf = self.opener(patch, "w")
725 732 if comments:
726 733 comments = "\n".join(comments) + '\n\n'
727 734 patchf.write(comments)
728 735
729 736 tip = repo.changelog.tip()
730 737 if top == tip:
731 738 # if the top of our patch queue is also the tip, there is an
732 739 # optimization here. We update the dirstate in place and strip
733 740 # off the tip commit. Then just commit the current directory
734 741 # tree. We can also send repo.commit the list of files
735 742 # changed to speed up the diff
736 743 #
737 744 # in short mode, we only diff the files included in the
738 745 # patch already
739 746 #
740 747 # this should really read:
741 748 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
742 749 # but we do it backwards to take advantage of manifest/chlog
743 750 # caching against the next repo.changes call
744 751 #
745 752 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
746 753 if short:
747 754 filelist = cc + aa + dd
748 755 else:
749 756 filelist = None
750 757 (c, a, r, d, u) = repo.changes(None, None, filelist)
751 758
752 759 # we might end up with files that were added between tip and
753 760 # the dirstate parent, but then changed in the local dirstate.
754 761 # in this case, we want them to only show up in the added section
755 762 for x in c:
756 763 if x not in aa:
757 764 cc.append(x)
758 765 # we might end up with files added by the local dirstate that
759 766 # were deleted by the patch. In this case, they should only
760 767 # show up in the changed section.
761 768 for x in a:
762 769 if x in dd:
763 770 del dd[dd.index(x)]
764 771 cc.append(x)
765 772 else:
766 773 aa.append(x)
767 774 # make sure any files deleted in the local dirstate
768 775 # are not in the add or change column of the patch
769 776 forget = []
770 777 for x in d + r:
771 778 if x in aa:
772 779 del aa[aa.index(x)]
773 780 forget.append(x)
774 781 continue
775 782 elif x in cc:
776 783 del cc[cc.index(x)]
777 784 dd.append(x)
778 785
779 786 c = list(util.unique(cc))
780 787 r = list(util.unique(dd))
781 788 a = list(util.unique(aa))
782 789 filelist = list(util.unique(c + r + a ))
783 790 commands.dodiff(patchf, self.ui, repo, patchparent, None,
784 791 filelist, changes=(c, a, r, [], u))
785 792 patchf.close()
786 793
787 794 changes = repo.changelog.read(tip)
788 795 repo.dirstate.setparents(*cparents)
789 796 repo.dirstate.update(a, 'a')
790 797 repo.dirstate.update(r, 'r')
791 798 repo.dirstate.update(c, 'n')
792 799 repo.dirstate.forget(forget)
793 800
794 801 if not message:
795 802 message = "patch queue: %s\n" % patch
796 803 else:
797 804 message = "\n".join(message)
798 805 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
799 806 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
800 807 self.applied[-1] = revlog.hex(n) + ':' + patch
801 808 self.applied_dirty = 1
802 809 else:
803 810 commands.dodiff(patchf, self.ui, repo, patchparent, None)
804 811 patchf.close()
805 812 self.pop(repo, force=True, wlock=wlock)
806 813 self.push(repo, force=True, wlock=wlock)
807 814
808 815 def init(self, repo, create=False):
809 816 if os.path.isdir(self.path):
810 817 raise util.Abort("patch queue directory already exists")
811 818 os.mkdir(self.path)
812 819 if create:
813 820 return self.qrepo(create=True)
814 821
815 822 def unapplied(self, repo, patch=None):
816 823 if patch and patch not in self.series:
817 824 self.ui.warn("%s not in the series file\n" % patch)
818 825 sys.exit(1)
819 826 if not patch:
820 827 start = self.series_end()
821 828 else:
822 829 start = self.series.index(patch) + 1
823 830 for p in self.series[start:]:
824 831 self.ui.write("%s\n" % p)
825 832
826 833 def qseries(self, repo, missing=None):
827 834 start = self.series_end()
828 835 if not missing:
829 836 for p in self.series[:start]:
830 837 if self.ui.verbose:
831 838 self.ui.write("%d A " % self.series.index(p))
832 839 self.ui.write("%s\n" % p)
833 840 for p in self.series[start:]:
834 841 if self.ui.verbose:
835 842 self.ui.write("%d U " % self.series.index(p))
836 843 self.ui.write("%s\n" % p)
837 844 else:
838 845 list = []
839 846 for root, dirs, files in os.walk(self.path):
840 847 d = root[len(self.path) + 1:]
841 848 for f in files:
842 849 fl = os.path.join(d, f)
843 850 if (fl not in self.series and
844 851 fl not in (self.status_path, self.series_path)
845 852 and not fl.startswith('.')):
846 853 list.append(fl)
847 854 list.sort()
848 855 if list:
849 856 for x in list:
850 857 if self.ui.verbose:
851 858 self.ui.write("D ")
852 859 self.ui.write("%s\n" % x)
853 860
854 861 def issaveline(self, l):
855 862 name = l.split(':')[1]
856 863 if name == '.hg.patches.save.line':
857 864 return True
858 865
859 866 def qrepo(self, create=False):
860 867 if create or os.path.isdir(os.path.join(self.path, ".hg")):
861 868 return hg.repository(self.ui, path=self.path, create=create)
862 869
863 870 def restore(self, repo, rev, delete=None, qupdate=None):
864 871 c = repo.changelog.read(rev)
865 872 desc = c[4].strip()
866 873 lines = desc.splitlines()
867 874 i = 0
868 875 datastart = None
869 876 series = []
870 877 applied = []
871 878 qpp = None
872 879 for i in xrange(0, len(lines)):
873 880 if lines[i] == 'Patch Data:':
874 881 datastart = i + 1
875 882 elif lines[i].startswith('Dirstate:'):
876 883 l = lines[i].rstrip()
877 884 l = l[10:].split(' ')
878 885 qpp = [ hg.bin(x) for x in l ]
879 886 elif datastart != None:
880 887 l = lines[i].rstrip()
881 888 index = l.index(':')
882 889 id = l[:index]
883 890 file = l[index + 1:]
884 891 if id:
885 892 applied.append(l)
886 893 series.append(file)
887 894 if datastart == None:
888 895 self.ui.warn("No saved patch data found\n")
889 896 return 1
890 897 self.ui.warn("restoring status: %s\n" % lines[0])
891 898 self.full_series = series
892 899 self.applied = applied
893 900 self.read_series(self.full_series)
894 901 self.series_dirty = 1
895 902 self.applied_dirty = 1
896 903 heads = repo.changelog.heads()
897 904 if delete:
898 905 if rev not in heads:
899 906 self.ui.warn("save entry has children, leaving it alone\n")
900 907 else:
901 908 self.ui.warn("removing save entry %s\n" % hg.short(rev))
902 909 pp = repo.dirstate.parents()
903 910 if rev in pp:
904 911 update = True
905 912 else:
906 913 update = False
907 914 self.strip(repo, rev, update=update, backup='strip')
908 915 if qpp:
909 916 self.ui.warn("saved queue repository parents: %s %s\n" %
910 917 (hg.short(qpp[0]), hg.short(qpp[1])))
911 918 if qupdate:
912 919 print "queue directory updating"
913 920 r = self.qrepo()
914 921 if not r:
915 922 self.ui.warn("Unable to load queue repository\n")
916 923 return 1
917 924 r.update(qpp[0], allow=False, force=True)
918 925
919 926 def save(self, repo, msg=None):
920 927 if len(self.applied) == 0:
921 928 self.ui.warn("save: no patches applied, exiting\n")
922 929 return 1
923 930 if self.issaveline(self.applied[-1]):
924 931 self.ui.warn("status is already saved\n")
925 932 return 1
926 933
927 934 ar = [ ':' + x for x in self.full_series ]
928 935 if not msg:
929 936 msg = "hg patches saved state"
930 937 else:
931 938 msg = "hg patches: " + msg.rstrip('\r\n')
932 939 r = self.qrepo()
933 940 if r:
934 941 pp = r.dirstate.parents()
935 942 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
936 943 msg += "\n\nPatch Data:\n"
937 944 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
938 945 + '\n' or "")
939 946 n = repo.commit(None, text, user=None, force=1)
940 947 if not n:
941 948 self.ui.warn("repo commit failed\n")
942 949 return 1
943 950 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
944 951 self.applied_dirty = 1
945 952
946 953 def series_end(self):
947 954 end = 0
948 955 if len(self.applied) > 0:
949 956 (top, p) = self.applied[-1].split(':')
950 957 try:
951 958 end = self.series.index(p)
952 959 except ValueError:
953 960 return 0
954 961 return end + 1
955 962 return end
956 963
957 964 def qapplied(self, repo, patch=None):
958 965 if patch and patch not in self.series:
959 966 self.ui.warn("%s not in the series file\n" % patch)
960 967 sys.exit(1)
961 968 if not patch:
962 969 end = len(self.applied)
963 970 else:
964 971 end = self.series.index(patch) + 1
965 972 for x in xrange(end):
966 973 p = self.appliedname(x)
967 974 self.ui.write("%s\n" % p)
968 975
969 976 def appliedname(self, index):
970 977 p = self.applied[index]
971 978 if not self.ui.verbose:
972 979 p = p.split(':')[1]
973 980 return p
974 981
975 982 def top(self, repo):
976 983 if len(self.applied):
977 984 p = self.appliedname(-1)
978 985 self.ui.write(p + '\n')
979 986 else:
980 987 self.ui.write("No patches applied\n")
981 988
982 989 def next(self, repo):
983 990 end = self.series_end()
984 991 if end == len(self.series):
985 992 self.ui.write("All patches applied\n")
986 993 else:
987 994 self.ui.write(self.series[end] + '\n')
988 995
989 996 def prev(self, repo):
990 997 if len(self.applied) > 1:
991 998 p = self.appliedname(-2)
992 999 self.ui.write(p + '\n')
993 1000 elif len(self.applied) == 1:
994 1001 self.ui.write("Only one patch applied\n")
995 1002 else:
996 1003 self.ui.write("No patches applied\n")
997 1004
998 1005 def qimport(self, repo, files, patch=None, existing=None, force=None):
999 1006 if len(files) > 1 and patch:
1000 1007 self.ui.warn("-n option not valid when importing multiple files\n")
1001 1008 sys.exit(1)
1002 1009 i = 0
1003 1010 added = []
1004 1011 for filename in files:
1005 1012 if existing:
1006 1013 if not patch:
1007 1014 patch = filename
1008 1015 if not os.path.isfile(os.path.join(self.path, patch)):
1009 1016 self.ui.warn("patch %s does not exist\n" % patch)
1010 1017 sys.exit(1)
1011 1018 else:
1012 1019 try:
1013 1020 text = file(filename).read()
1014 1021 except IOError:
1015 1022 self.ui.warn("Unable to read %s\n" % patch)
1016 1023 sys.exit(1)
1017 1024 if not patch:
1018 1025 patch = os.path.split(filename)[1]
1019 1026 if not force and os.path.isfile(os.path.join(self.path, patch)):
1020 1027 self.ui.warn("patch %s already exists\n" % patch)
1021 1028 sys.exit(1)
1022 1029 patchf = self.opener(patch, "w")
1023 1030 patchf.write(text)
1024 1031 if patch in self.series:
1025 1032 self.ui.warn("patch %s is already in the series file\n" % patch)
1026 1033 sys.exit(1)
1027 1034 index = self.series_end() + i
1028 1035 self.full_series[index:index] = [patch]
1029 1036 self.read_series(self.full_series)
1030 1037 self.ui.warn("adding %s to series file\n" % patch)
1031 1038 i += 1
1032 1039 added.append(patch)
1033 1040 patch = None
1034 1041 self.series_dirty = 1
1035 1042 qrepo = self.qrepo()
1036 1043 if qrepo:
1037 1044 qrepo.add(added)
1038 1045
1039 1046 def delete(ui, repo, patch, **opts):
1040 1047 """remove a patch from the series file"""
1041 1048 q = repomap[repo]
1042 1049 q.delete(repo, patch)
1043 1050 q.save_dirty()
1044 1051 return 0
1045 1052
1046 1053 def applied(ui, repo, patch=None, **opts):
1047 1054 """print the patches already applied"""
1048 1055 repomap[repo].qapplied(repo, patch)
1049 1056 return 0
1050 1057
1051 1058 def unapplied(ui, repo, patch=None, **opts):
1052 1059 """print the patches not yet applied"""
1053 1060 repomap[repo].unapplied(repo, patch)
1054 1061 return 0
1055 1062
1056 1063 def qimport(ui, repo, *filename, **opts):
1057 1064 """import a patch"""
1058 1065 q = repomap[repo]
1059 1066 q.qimport(repo, filename, patch=opts['name'],
1060 1067 existing=opts['existing'], force=opts['force'])
1061 1068 q.save_dirty()
1062 1069 return 0
1063 1070
1064 1071 def init(ui, repo, **opts):
1065 1072 """init a new queue repository"""
1066 1073 q = repomap[repo]
1067 1074 r = q.init(repo, create=opts['create_repo'])
1068 1075 q.save_dirty()
1069 1076 if r:
1070 1077 fp = r.wopener('.hgignore', 'w')
1071 1078 print >> fp, 'syntax: glob'
1072 1079 print >> fp, 'status'
1073 1080 fp.close()
1074 1081 r.wopener('series', 'w').close()
1075 1082 r.add(['.hgignore', 'series'])
1076 1083 return 0
1077 1084
1078 1085 def commit(ui, repo, *pats, **opts):
1079 1086 """commit changes in the queue repository"""
1080 1087 q = repomap[repo]
1081 1088 r = q.qrepo()
1082 1089 if not r: raise util.Abort('no queue repository')
1083 1090 commands.commit(r.ui, r, *pats, **opts)
1084 1091
1085 1092 def series(ui, repo, **opts):
1086 1093 """print the entire series file"""
1087 1094 repomap[repo].qseries(repo, missing=opts['missing'])
1088 1095 return 0
1089 1096
1090 1097 def top(ui, repo, **opts):
1091 1098 """print the name of the current patch"""
1092 1099 repomap[repo].top(repo)
1093 1100 return 0
1094 1101
1095 1102 def next(ui, repo, **opts):
1096 1103 """print the name of the next patch"""
1097 1104 repomap[repo].next(repo)
1098 1105 return 0
1099 1106
1100 1107 def prev(ui, repo, **opts):
1101 1108 """print the name of the previous patch"""
1102 1109 repomap[repo].prev(repo)
1103 1110 return 0
1104 1111
1105 1112 def new(ui, repo, patch, **opts):
1106 1113 """create a new patch"""
1107 1114 q = repomap[repo]
1108 1115 q.new(repo, patch, msg=opts['message'], force=opts['force'])
1109 1116 q.save_dirty()
1110 1117 return 0
1111 1118
1112 1119 def refresh(ui, repo, **opts):
1113 1120 """update the current patch"""
1114 1121 q = repomap[repo]
1115 1122 q.refresh(repo, short=opts['short'])
1116 1123 q.save_dirty()
1117 1124 return 0
1118 1125
1119 1126 def diff(ui, repo, *files, **opts):
1120 1127 """diff of the current patch"""
1121 1128 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1122 1129 repomap[repo].diff(repo, list(files))
1123 1130 return 0
1124 1131
1125 1132 def lastsavename(path):
1126 1133 (dir, base) = os.path.split(path)
1127 1134 names = os.listdir(dir)
1128 1135 namere = re.compile("%s.([0-9]+)" % base)
1129 1136 max = None
1130 1137 maxname = None
1131 1138 for f in names:
1132 1139 m = namere.match(f)
1133 1140 if m:
1134 1141 index = int(m.group(1))
1135 1142 if max == None or index > max:
1136 1143 max = index
1137 1144 maxname = f
1138 1145 if maxname:
1139 1146 return (os.path.join(dir, maxname), max)
1140 1147 return (None, None)
1141 1148
1142 1149 def savename(path):
1143 1150 (last, index) = lastsavename(path)
1144 1151 if last is None:
1145 1152 index = 0
1146 1153 newpath = path + ".%d" % (index + 1)
1147 1154 return newpath
1148 1155
1149 1156 def push(ui, repo, patch=None, **opts):
1150 1157 """push the next patch onto the stack"""
1151 1158 q = repomap[repo]
1152 1159 mergeq = None
1153 1160
1154 1161 if opts['all']:
1155 1162 patch = q.series[-1]
1156 1163 if opts['merge']:
1157 1164 if opts['name']:
1158 1165 newpath = opts['name']
1159 1166 else:
1160 1167 newpath, i = lastsavename(q.path)
1161 1168 if not newpath:
1162 1169 ui.warn("no saved queues found, please use -n\n")
1163 1170 return 1
1164 1171 mergeq = queue(ui, repo.join(""), newpath)
1165 1172 ui.warn("merging with queue at: %s\n" % mergeq.path)
1166 1173 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1167 1174 mergeq=mergeq)
1168 1175 q.save_dirty()
1169 1176 return ret
1170 1177
1171 1178 def pop(ui, repo, patch=None, **opts):
1172 1179 """pop the current patch off the stack"""
1173 1180 localupdate = True
1174 1181 if opts['name']:
1175 1182 q = queue(ui, repo.join(""), repo.join(opts['name']))
1176 1183 ui.warn('using patch queue: %s\n' % q.path)
1177 1184 localupdate = False
1178 1185 else:
1179 1186 q = repomap[repo]
1180 1187 if opts['all'] and len(q.applied) > 0:
1181 1188 patch = q.applied[0].split(':')[1]
1182 1189 q.pop(repo, patch, force=opts['force'], update=localupdate)
1183 1190 q.save_dirty()
1184 1191 return 0
1185 1192
1186 1193 def restore(ui, repo, rev, **opts):
1187 1194 """restore the queue state saved by a rev"""
1188 1195 rev = repo.lookup(rev)
1189 1196 q = repomap[repo]
1190 1197 q.restore(repo, rev, delete=opts['delete'],
1191 1198 qupdate=opts['update'])
1192 1199 q.save_dirty()
1193 1200 return 0
1194 1201
1195 1202 def save(ui, repo, **opts):
1196 1203 """save current queue state"""
1197 1204 q = repomap[repo]
1198 1205 ret = q.save(repo, msg=opts['message'])
1199 1206 if ret:
1200 1207 return ret
1201 1208 q.save_dirty()
1202 1209 if opts['copy']:
1203 1210 path = q.path
1204 1211 if opts['name']:
1205 1212 newpath = os.path.join(q.basepath, opts['name'])
1206 1213 if os.path.exists(newpath):
1207 1214 if not os.path.isdir(newpath):
1208 1215 ui.warn("destination %s exists and is not a directory\n" %
1209 1216 newpath)
1210 1217 sys.exit(1)
1211 1218 if not opts['force']:
1212 1219 ui.warn("destination %s exists, use -f to force\n" %
1213 1220 newpath)
1214 1221 sys.exit(1)
1215 1222 else:
1216 1223 newpath = savename(path)
1217 1224 ui.warn("copy %s to %s\n" % (path, newpath))
1218 1225 util.copyfiles(path, newpath)
1219 1226 if opts['empty']:
1220 1227 try:
1221 1228 os.unlink(os.path.join(q.path, q.status_path))
1222 1229 except:
1223 1230 pass
1224 1231 return 0
1225 1232
1226 1233 def strip(ui, repo, rev, **opts):
1227 1234 """strip a revision and all later revs on the same branch"""
1228 1235 rev = repo.lookup(rev)
1229 1236 backup = 'all'
1230 1237 if opts['backup']:
1231 1238 backup = 'strip'
1232 1239 elif opts['nobackup']:
1233 1240 backup = 'none'
1234 1241 repomap[repo].strip(repo, rev, backup=backup)
1235 1242 return 0
1236 1243
1237 1244 def version(ui, q=None):
1238 1245 """print the version number"""
1239 1246 ui.write("mq version %s\n" % versionstr)
1240 1247 return 0
1241 1248
1242 1249 def reposetup(ui, repo):
1243 1250 repomap[repo] = queue(ui, repo.join(""))
1244 1251
1245 1252 cmdtable = {
1246 1253 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1247 1254 "qcommit|qci":
1248 1255 (commit,
1249 1256 commands.table["^commit|ci"][1],
1250 1257 'hg qcommit [OPTION]... [FILE]...'),
1251 1258 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1252 1259 "qdelete": (delete, [], 'hg qdelete PATCH'),
1253 1260 "^qimport":
1254 1261 (qimport,
1255 1262 [('e', 'existing', None, 'import file in patch dir'),
1256 1263 ('n', 'name', '', 'patch file name'),
1257 1264 ('f', 'force', None, 'overwrite existing files')],
1258 1265 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1259 1266 "^qinit":
1260 1267 (init,
1261 1268 [('c', 'create-repo', None, 'create queue repository')],
1262 1269 'hg qinit [-c]'),
1263 1270 "qnew":
1264 1271 (new,
1265 1272 [('m', 'message', '', 'commit message'),
1266 1273 ('f', 'force', None, 'force')],
1267 1274 'hg qnew [-m TEXT] [-f] PATCH'),
1268 1275 "qnext": (next, [], 'hg qnext'),
1269 1276 "qprev": (prev, [], 'hg qprev'),
1270 1277 "^qpop":
1271 1278 (pop,
1272 1279 [('a', 'all', None, 'pop all patches'),
1273 1280 ('n', 'name', '', 'queue name to pop'),
1274 1281 ('f', 'force', None, 'forget any local changes')],
1275 1282 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1276 1283 "^qpush":
1277 1284 (push,
1278 1285 [('f', 'force', None, 'apply if the patch has rejects'),
1279 1286 ('l', 'list', None, 'list patch name in commit text'),
1280 1287 ('a', 'all', None, 'apply all patches'),
1281 1288 ('m', 'merge', None, 'merge from another queue'),
1282 1289 ('n', 'name', '', 'merge queue name')],
1283 1290 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1284 1291 "^qrefresh":
1285 1292 (refresh,
1286 1293 [('s', 'short', None, 'short refresh')],
1287 1294 'hg qrefresh [-s]'),
1288 1295 "qrestore":
1289 1296 (restore,
1290 1297 [('d', 'delete', None, 'delete save entry'),
1291 1298 ('u', 'update', None, 'update queue working dir')],
1292 1299 'hg qrestore [-d] [-u] REV'),
1293 1300 "qsave":
1294 1301 (save,
1295 1302 [('m', 'message', '', 'commit message'),
1296 1303 ('c', 'copy', None, 'copy patch directory'),
1297 1304 ('n', 'name', '', 'copy directory name'),
1298 1305 ('e', 'empty', None, 'clear queue status file'),
1299 1306 ('f', 'force', None, 'force copy')],
1300 1307 'hg qsave [-m TEXT] [-c] [-n NAME] [-e] [-f]'),
1301 1308 "qseries":
1302 1309 (series,
1303 1310 [('m', 'missing', None, 'print patches not in series')],
1304 1311 'hg qseries [-m]'),
1305 1312 "^strip":
1306 1313 (strip,
1307 1314 [('f', 'force', None, 'force multi-head removal'),
1308 1315 ('b', 'backup', None, 'bundle unrelated changesets'),
1309 1316 ('n', 'nobackup', None, 'no backups')],
1310 1317 'hg strip [-f] [-b] [-n] REV'),
1311 1318 "qtop": (top, [], 'hg qtop'),
1312 1319 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1313 1320 "qversion": (version, [], 'hg qversion')
1314 1321 }
1315 1322
@@ -1,12 +1,16 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # An example CGI script to use hgweb, edit as necessary
4 4
5 5 import cgitb, os, sys
6 6 cgitb.enable()
7 7
8 8 # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
9 from mercurial import hgweb
9 from mercurial.hgweb.hgweb_mod import hgweb
10 from mercurial.hgweb.request import wsgiapplication
11 import mercurial.hgweb.wsgicgi as wsgicgi
10 12
11 h = hgweb.hgweb("/path/to/repo", "repository name")
12 h.run()
13 def make_web_app():
14 return hgweb("/path/to/repo", "repository name")
15
16 wsgicgi.launch(wsgiapplication(make_web_app))
@@ -1,31 +1,35 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # An example CGI script to export multiple hgweb repos, edit as necessary
4 4
5 5 import cgitb, sys
6 6 cgitb.enable()
7 7
8 8 # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
9 from mercurial import hgweb
9 from mercurial.hgweb.hgwebdir_mod import hgwebdir
10 from mercurial.hgweb.request import wsgiapplication
11 import mercurial.hgweb.wsgicgi as wsgicgi
10 12
11 13 # The config file looks like this. You can have paths to individual
12 14 # repos, collections of repos in a directory tree, or both.
13 15 #
14 16 # [paths]
15 17 # virtual/path = /real/path
16 18 # virtual/path = /real/path
17 19 #
18 20 # [collections]
19 21 # /prefix/to/strip/off = /root/of/tree/full/of/repos
20 22 #
21 23 # collections example: say directory tree /foo contains repos /foo/bar,
22 24 # /foo/quux/baz. Give this config section:
23 25 # [collections]
24 26 # /foo = /foo
25 27 # Then repos will list as bar and quux/baz.
26 28
27 29 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
28 30 # or use a dictionary with entries like 'virtual/path': '/real/path'
29 31
30 h = hgweb.hgwebdir("hgweb.config")
31 h.run()
32 def make_web_app():
33 return hgwebdir("hgweb.config")
34
35 wsgicgi.launch(wsgiapplication(make_web_app))
@@ -1,59 +1,48 b''
1 1 # changelog.py - changelog class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from revlog import *
9 9 from i18n import gettext as _
10 10 from demandload import demandload
11 11 demandload(globals(), "os time util")
12 12
13 13 class changelog(revlog):
14 14 def __init__(self, opener, defversion=REVLOGV0):
15 15 revlog.__init__(self, opener, "00changelog.i", "00changelog.d",
16 16 defversion)
17 17
18 18 def extract(self, text):
19 19 if not text:
20 20 return (nullid, "", (0, 0), [], "")
21 21 last = text.index("\n\n")
22 22 desc = text[last + 2:]
23 23 l = text[:last].splitlines()
24 24 manifest = bin(l[0])
25 25 user = l[1]
26 26 date = l[2].split(' ')
27 27 time = float(date.pop(0))
28 28 try:
29 29 # various tools did silly things with the time zone field.
30 30 timezone = int(date[0])
31 31 except:
32 32 timezone = 0
33 33 files = l[3:]
34 34 return (manifest, user, (time, timezone), files, desc)
35 35
36 36 def read(self, node):
37 37 return self.extract(self.revision(node))
38 38
39 39 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
40 40 user=None, date=None):
41 41 if date:
42 # validate explicit (probably user-specified) date and
43 # time zone offset. values must fit in signed 32 bits for
44 # current 32-bit linux runtimes. timezones go from UTC-12
45 # to UTC+14
46 try:
47 when, offset = map(int, date.split(' '))
48 except ValueError:
49 raise ValueError(_('invalid date: %r') % date)
50 if abs(when) > 0x7fffffff:
51 raise ValueError(_('date exceeds 32 bits: %d') % when)
52 if offset < -50400 or offset > 43200:
53 raise ValueError(_('impossible time zone offset: %d') % offset)
42 parseddate = "%d %d" % util.parsedate(date)
54 43 else:
55 date = "%d %d" % util.makedate()
44 parseddate = "%d %d" % util.makedate()
56 45 list.sort()
57 l = [hex(manifest), user, date] + list + ["", desc]
46 l = [hex(manifest), user, parseddate] + list + ["", desc]
58 47 text = "\n".join(l)
59 48 return self.addrevision(text, transaction, self.count(), p1, p2)
@@ -1,3521 +1,3554 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 13 demandload(globals(), "fnmatch mdiff random signal tempfile time")
14 14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 demandload(globals(), "archival changegroup")
15 demandload(globals(), "archival cStringIO changegroup email.Parser")
16 16 demandload(globals(), "hgweb.server sshserver")
17 17
18 18 class UnknownCommand(Exception):
19 19 """Exception raised if command is not in the command table."""
20 20 class AmbiguousCommand(Exception):
21 21 """Exception raised if command shortcut matches more than one command."""
22 22
23 23 def bail_if_changed(repo):
24 24 modified, added, removed, deleted, unknown = repo.changes()
25 25 if modified or added or removed or deleted:
26 26 raise util.Abort(_("outstanding uncommitted changes"))
27 27
28 28 def filterfiles(filters, files):
29 29 l = [x for x in files if x in filters]
30 30
31 31 for t in filters:
32 32 if t and t[-1] != "/":
33 33 t += "/"
34 34 l += [x for x in files if x.startswith(t)]
35 35 return l
36 36
37 37 def relpath(repo, args):
38 38 cwd = repo.getcwd()
39 39 if cwd:
40 40 return [util.normpath(os.path.join(cwd, x)) for x in args]
41 41 return args
42 42
43 43 def matchpats(repo, pats=[], opts={}, head=''):
44 44 cwd = repo.getcwd()
45 45 if not pats and cwd:
46 46 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
47 47 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
48 48 cwd = ''
49 49 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
50 50 opts.get('exclude'), head)
51 51
52 52 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
53 53 files, matchfn, anypats = matchpats(repo, pats, opts, head)
54 54 exact = dict(zip(files, files))
55 55 def walk():
56 56 for src, fn in repo.walk(node=node, files=files, match=matchfn,
57 57 badmatch=badmatch):
58 58 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
59 59 return files, matchfn, walk()
60 60
61 61 def walk(repo, pats, opts, node=None, head='', badmatch=None):
62 62 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
63 63 for r in results:
64 64 yield r
65 65
66 66 def walkchangerevs(ui, repo, pats, opts):
67 67 '''Iterate over files and the revs they changed in.
68 68
69 69 Callers most commonly need to iterate backwards over the history
70 70 it is interested in. Doing so has awful (quadratic-looking)
71 71 performance, so we use iterators in a "windowed" way.
72 72
73 73 We walk a window of revisions in the desired order. Within the
74 74 window, we first walk forwards to gather data, then in the desired
75 75 order (usually backwards) to display it.
76 76
77 77 This function returns an (iterator, getchange, matchfn) tuple. The
78 78 getchange function returns the changelog entry for a numeric
79 79 revision. The iterator yields 3-tuples. They will be of one of
80 80 the following forms:
81 81
82 82 "window", incrementing, lastrev: stepping through a window,
83 83 positive if walking forwards through revs, last rev in the
84 84 sequence iterated over - use to reset state for the current window
85 85
86 86 "add", rev, fns: out-of-order traversal of the given file names
87 87 fns, which changed during revision rev - use to gather data for
88 88 possible display
89 89
90 90 "iter", rev, None: in-order traversal of the revs earlier iterated
91 91 over with "add" - use to display data'''
92 92
93 93 def increasing_windows(start, end, windowsize=8, sizelimit=512):
94 94 if start < end:
95 95 while start < end:
96 96 yield start, min(windowsize, end-start)
97 97 start += windowsize
98 98 if windowsize < sizelimit:
99 99 windowsize *= 2
100 100 else:
101 101 while start > end:
102 102 yield start, min(windowsize, start-end-1)
103 103 start -= windowsize
104 104 if windowsize < sizelimit:
105 105 windowsize *= 2
106 106
107 107
108 108 files, matchfn, anypats = matchpats(repo, pats, opts)
109 109
110 110 if repo.changelog.count() == 0:
111 111 return [], False, matchfn
112 112
113 113 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
114 114 wanted = {}
115 115 slowpath = anypats
116 116 fncache = {}
117 117
118 118 chcache = {}
119 119 def getchange(rev):
120 120 ch = chcache.get(rev)
121 121 if ch is None:
122 122 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
123 123 return ch
124 124
125 125 if not slowpath and not files:
126 126 # No files, no patterns. Display all revs.
127 127 wanted = dict(zip(revs, revs))
128 128 if not slowpath:
129 129 # Only files, no patterns. Check the history of each file.
130 130 def filerevgen(filelog):
131 131 for i, window in increasing_windows(filelog.count()-1, -1):
132 132 revs = []
133 133 for j in xrange(i - window, i + 1):
134 134 revs.append(filelog.linkrev(filelog.node(j)))
135 135 revs.reverse()
136 136 for rev in revs:
137 137 yield rev
138 138
139 139 minrev, maxrev = min(revs), max(revs)
140 140 for file_ in files:
141 141 filelog = repo.file(file_)
142 142 # A zero count may be a directory or deleted file, so
143 143 # try to find matching entries on the slow path.
144 144 if filelog.count() == 0:
145 145 slowpath = True
146 146 break
147 147 for rev in filerevgen(filelog):
148 148 if rev <= maxrev:
149 149 if rev < minrev:
150 150 break
151 151 fncache.setdefault(rev, [])
152 152 fncache[rev].append(file_)
153 153 wanted[rev] = 1
154 154 if slowpath:
155 155 # The slow path checks files modified in every changeset.
156 156 def changerevgen():
157 157 for i, window in increasing_windows(repo.changelog.count()-1, -1):
158 158 for j in xrange(i - window, i + 1):
159 159 yield j, getchange(j)[3]
160 160
161 161 for rev, changefiles in changerevgen():
162 162 matches = filter(matchfn, changefiles)
163 163 if matches:
164 164 fncache[rev] = matches
165 165 wanted[rev] = 1
166 166
167 167 def iterate():
168 168 for i, window in increasing_windows(0, len(revs)):
169 169 yield 'window', revs[0] < revs[-1], revs[-1]
170 170 nrevs = [rev for rev in revs[i:i+window]
171 171 if rev in wanted]
172 172 srevs = list(nrevs)
173 173 srevs.sort()
174 174 for rev in srevs:
175 175 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
176 176 yield 'add', rev, fns
177 177 for rev in nrevs:
178 178 yield 'iter', rev, None
179 179 return iterate(), getchange, matchfn
180 180
181 181 revrangesep = ':'
182 182
183 183 def revfix(repo, val, defval):
184 184 '''turn user-level id of changeset into rev number.
185 185 user-level id can be tag, changeset, rev number, or negative rev
186 186 number relative to number of revs (-1 is tip, etc).'''
187 187 if not val:
188 188 return defval
189 189 try:
190 190 num = int(val)
191 191 if str(num) != val:
192 192 raise ValueError
193 193 if num < 0:
194 194 num += repo.changelog.count()
195 195 if num < 0:
196 196 num = 0
197 197 elif num >= repo.changelog.count():
198 198 raise ValueError
199 199 except ValueError:
200 200 try:
201 201 num = repo.changelog.rev(repo.lookup(val))
202 202 except KeyError:
203 203 raise util.Abort(_('invalid revision identifier %s'), val)
204 204 return num
205 205
206 206 def revpair(ui, repo, revs):
207 207 '''return pair of nodes, given list of revisions. second item can
208 208 be None, meaning use working dir.'''
209 209 if not revs:
210 210 return repo.dirstate.parents()[0], None
211 211 end = None
212 212 if len(revs) == 1:
213 213 start = revs[0]
214 214 if revrangesep in start:
215 215 start, end = start.split(revrangesep, 1)
216 216 start = revfix(repo, start, 0)
217 217 end = revfix(repo, end, repo.changelog.count() - 1)
218 218 else:
219 219 start = revfix(repo, start, None)
220 220 elif len(revs) == 2:
221 221 if revrangesep in revs[0] or revrangesep in revs[1]:
222 222 raise util.Abort(_('too many revisions specified'))
223 223 start = revfix(repo, revs[0], None)
224 224 end = revfix(repo, revs[1], None)
225 225 else:
226 226 raise util.Abort(_('too many revisions specified'))
227 227 if end is not None: end = repo.lookup(str(end))
228 228 return repo.lookup(str(start)), end
229 229
230 230 def revrange(ui, repo, revs):
231 231 """Yield revision as strings from a list of revision specifications."""
232 232 seen = {}
233 233 for spec in revs:
234 234 if spec.find(revrangesep) >= 0:
235 235 start, end = spec.split(revrangesep, 1)
236 236 start = revfix(repo, start, 0)
237 237 end = revfix(repo, end, repo.changelog.count() - 1)
238 238 step = start > end and -1 or 1
239 239 for rev in xrange(start, end+step, step):
240 240 if rev in seen:
241 241 continue
242 242 seen[rev] = 1
243 243 yield str(rev)
244 244 else:
245 245 rev = revfix(repo, spec, None)
246 246 if rev in seen:
247 247 continue
248 248 seen[rev] = 1
249 249 yield str(rev)
250 250
251 251 def make_filename(repo, r, pat, node=None,
252 252 total=None, seqno=None, revwidth=None, pathname=None):
253 253 node_expander = {
254 254 'H': lambda: hex(node),
255 255 'R': lambda: str(r.rev(node)),
256 256 'h': lambda: short(node),
257 257 }
258 258 expander = {
259 259 '%': lambda: '%',
260 260 'b': lambda: os.path.basename(repo.root),
261 261 }
262 262
263 263 try:
264 264 if node:
265 265 expander.update(node_expander)
266 266 if node and revwidth is not None:
267 267 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
268 268 if total is not None:
269 269 expander['N'] = lambda: str(total)
270 270 if seqno is not None:
271 271 expander['n'] = lambda: str(seqno)
272 272 if total is not None and seqno is not None:
273 273 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
274 274 if pathname is not None:
275 275 expander['s'] = lambda: os.path.basename(pathname)
276 276 expander['d'] = lambda: os.path.dirname(pathname) or '.'
277 277 expander['p'] = lambda: pathname
278 278
279 279 newname = []
280 280 patlen = len(pat)
281 281 i = 0
282 282 while i < patlen:
283 283 c = pat[i]
284 284 if c == '%':
285 285 i += 1
286 286 c = pat[i]
287 287 c = expander[c]()
288 288 newname.append(c)
289 289 i += 1
290 290 return ''.join(newname)
291 291 except KeyError, inst:
292 292 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
293 293 inst.args[0])
294 294
295 295 def make_file(repo, r, pat, node=None,
296 296 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
297 297 if not pat or pat == '-':
298 298 return 'w' in mode and sys.stdout or sys.stdin
299 299 if hasattr(pat, 'write') and 'w' in mode:
300 300 return pat
301 301 if hasattr(pat, 'read') and 'r' in mode:
302 302 return pat
303 303 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
304 304 pathname),
305 305 mode)
306 306
307 307 def write_bundle(cg, filename=None, compress=True):
308 308 """Write a bundle file and return its filename.
309 309
310 310 Existing files will not be overwritten.
311 311 If no filename is specified, a temporary file is created.
312 312 bz2 compression can be turned off.
313 313 The bundle file will be deleted in case of errors.
314 314 """
315 315 class nocompress(object):
316 316 def compress(self, x):
317 317 return x
318 318 def flush(self):
319 319 return ""
320 320
321 321 fh = None
322 322 cleanup = None
323 323 try:
324 324 if filename:
325 325 if os.path.exists(filename):
326 326 raise util.Abort(_("file '%s' already exists"), filename)
327 327 fh = open(filename, "wb")
328 328 else:
329 329 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
330 330 fh = os.fdopen(fd, "wb")
331 331 cleanup = filename
332 332
333 333 if compress:
334 334 fh.write("HG10")
335 335 z = bz2.BZ2Compressor(9)
336 336 else:
337 337 fh.write("HG10UN")
338 338 z = nocompress()
339 339 # parse the changegroup data, otherwise we will block
340 340 # in case of sshrepo because we don't know the end of the stream
341 341
342 342 # an empty chunkiter is the end of the changegroup
343 343 empty = False
344 344 while not empty:
345 345 empty = True
346 346 for chunk in changegroup.chunkiter(cg):
347 347 empty = False
348 348 fh.write(z.compress(changegroup.genchunk(chunk)))
349 349 fh.write(z.compress(changegroup.closechunk()))
350 350 fh.write(z.flush())
351 351 cleanup = None
352 352 return filename
353 353 finally:
354 354 if fh is not None:
355 355 fh.close()
356 356 if cleanup is not None:
357 357 os.unlink(cleanup)
358 358
359 359 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
360 360 changes=None, text=False, opts={}):
361 361 if not node1:
362 362 node1 = repo.dirstate.parents()[0]
363 363 # reading the data for node1 early allows it to play nicely
364 364 # with repo.changes and the revlog cache.
365 365 change = repo.changelog.read(node1)
366 366 mmap = repo.manifest.read(change[0])
367 367 date1 = util.datestr(change[2])
368 368
369 369 if not changes:
370 370 changes = repo.changes(node1, node2, files, match=match)
371 371 modified, added, removed, deleted, unknown = changes
372 372 if files:
373 373 modified, added, removed = map(lambda x: filterfiles(files, x),
374 374 (modified, added, removed))
375 375
376 376 if not modified and not added and not removed:
377 377 return
378 378
379 379 if node2:
380 380 change = repo.changelog.read(node2)
381 381 mmap2 = repo.manifest.read(change[0])
382 382 _date2 = util.datestr(change[2])
383 383 def date2(f):
384 384 return _date2
385 385 def read(f):
386 386 return repo.file(f).read(mmap2[f])
387 387 else:
388 388 tz = util.makedate()[1]
389 389 _date2 = util.datestr()
390 390 def date2(f):
391 391 try:
392 392 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
393 393 except OSError, err:
394 394 if err.errno != errno.ENOENT: raise
395 395 return _date2
396 396 def read(f):
397 397 return repo.wread(f)
398 398
399 399 if ui.quiet:
400 400 r = None
401 401 else:
402 402 hexfunc = ui.verbose and hex or short
403 403 r = [hexfunc(node) for node in [node1, node2] if node]
404 404
405 405 diffopts = ui.diffopts()
406 406 showfunc = opts.get('show_function') or diffopts['showfunc']
407 407 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
408 408 for f in modified:
409 409 to = None
410 410 if f in mmap:
411 411 to = repo.file(f).read(mmap[f])
412 412 tn = read(f)
413 413 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
414 414 showfunc=showfunc, ignorews=ignorews))
415 415 for f in added:
416 416 to = None
417 417 tn = read(f)
418 418 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
419 419 showfunc=showfunc, ignorews=ignorews))
420 420 for f in removed:
421 421 to = repo.file(f).read(mmap[f])
422 422 tn = None
423 423 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
424 424 showfunc=showfunc, ignorews=ignorews))
425 425
426 426 def trimuser(ui, name, rev, revcache):
427 427 """trim the name of the user who committed a change"""
428 428 user = revcache.get(rev)
429 429 if user is None:
430 430 user = revcache[rev] = ui.shortuser(name)
431 431 return user
432 432
433 433 class changeset_printer(object):
434 434 '''show changeset information when templating not requested.'''
435 435
436 436 def __init__(self, ui, repo):
437 437 self.ui = ui
438 438 self.repo = repo
439 439
440 440 def show(self, rev=0, changenode=None, brinfo=None):
441 441 '''show a single changeset or file revision'''
442 442 log = self.repo.changelog
443 443 if changenode is None:
444 444 changenode = log.node(rev)
445 445 elif not rev:
446 446 rev = log.rev(changenode)
447 447
448 448 if self.ui.quiet:
449 449 self.ui.write("%d:%s\n" % (rev, short(changenode)))
450 450 return
451 451
452 452 changes = log.read(changenode)
453 453 date = util.datestr(changes[2])
454 454
455 455 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
456 456 for p in log.parents(changenode)
457 457 if self.ui.debugflag or p != nullid]
458 458 if (not self.ui.debugflag and len(parents) == 1 and
459 459 parents[0][0] == rev-1):
460 460 parents = []
461 461
462 462 if self.ui.verbose:
463 463 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
464 464 else:
465 465 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
466 466
467 467 for tag in self.repo.nodetags(changenode):
468 468 self.ui.status(_("tag: %s\n") % tag)
469 469 for parent in parents:
470 470 self.ui.write(_("parent: %d:%s\n") % parent)
471 471
472 472 if brinfo and changenode in brinfo:
473 473 br = brinfo[changenode]
474 474 self.ui.write(_("branch: %s\n") % " ".join(br))
475 475
476 476 self.ui.debug(_("manifest: %d:%s\n") %
477 477 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
478 478 self.ui.status(_("user: %s\n") % changes[1])
479 479 self.ui.status(_("date: %s\n") % date)
480 480
481 481 if self.ui.debugflag:
482 482 files = self.repo.changes(log.parents(changenode)[0], changenode)
483 483 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
484 484 files):
485 485 if value:
486 486 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
487 487 else:
488 488 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
489 489
490 490 description = changes[4].strip()
491 491 if description:
492 492 if self.ui.verbose:
493 493 self.ui.status(_("description:\n"))
494 494 self.ui.status(description)
495 495 self.ui.status("\n\n")
496 496 else:
497 497 self.ui.status(_("summary: %s\n") %
498 498 description.splitlines()[0])
499 499 self.ui.status("\n")
500 500
501 501 def show_changeset(ui, repo, opts):
502 502 '''show one changeset. uses template or regular display. caller
503 503 can pass in 'style' and 'template' options in opts.'''
504 504
505 505 tmpl = opts.get('template')
506 506 if tmpl:
507 507 tmpl = templater.parsestring(tmpl, quoted=False)
508 508 else:
509 509 tmpl = ui.config('ui', 'logtemplate')
510 510 if tmpl: tmpl = templater.parsestring(tmpl)
511 511 mapfile = opts.get('style') or ui.config('ui', 'style')
512 512 if tmpl or mapfile:
513 513 if mapfile:
514 514 if not os.path.isfile(mapfile):
515 515 mapname = templater.templatepath('map-cmdline.' + mapfile)
516 516 if not mapname: mapname = templater.templatepath(mapfile)
517 517 if mapname: mapfile = mapname
518 518 try:
519 519 t = templater.changeset_templater(ui, repo, mapfile)
520 520 except SyntaxError, inst:
521 521 raise util.Abort(inst.args[0])
522 522 if tmpl: t.use_template(tmpl)
523 523 return t
524 524 return changeset_printer(ui, repo)
525 525
526 526 def show_version(ui):
527 527 """output version and copyright information"""
528 528 ui.write(_("Mercurial Distributed SCM (version %s)\n")
529 529 % version.get_version())
530 530 ui.status(_(
531 531 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
532 532 "This is free software; see the source for copying conditions. "
533 533 "There is NO\nwarranty; "
534 534 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
535 535 ))
536 536
537 537 def help_(ui, cmd=None, with_version=False):
538 538 """show help for a given command or all commands"""
539 539 option_lists = []
540 540 if cmd and cmd != 'shortlist':
541 541 if with_version:
542 542 show_version(ui)
543 543 ui.write('\n')
544 544 aliases, i = find(cmd)
545 545 # synopsis
546 546 ui.write("%s\n\n" % i[2])
547 547
548 548 # description
549 549 doc = i[0].__doc__
550 550 if not doc:
551 551 doc = _("(No help text available)")
552 552 if ui.quiet:
553 553 doc = doc.splitlines(0)[0]
554 554 ui.write("%s\n" % doc.rstrip())
555 555
556 556 if not ui.quiet:
557 557 # aliases
558 558 if len(aliases) > 1:
559 559 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
560 560
561 561 # options
562 562 if i[1]:
563 563 option_lists.append(("options", i[1]))
564 564
565 565 else:
566 566 # program name
567 567 if ui.verbose or with_version:
568 568 show_version(ui)
569 569 else:
570 570 ui.status(_("Mercurial Distributed SCM\n"))
571 571 ui.status('\n')
572 572
573 573 # list of commands
574 574 if cmd == "shortlist":
575 575 ui.status(_('basic commands (use "hg help" '
576 576 'for the full list or option "-v" for details):\n\n'))
577 577 elif ui.verbose:
578 578 ui.status(_('list of commands:\n\n'))
579 579 else:
580 580 ui.status(_('list of commands (use "hg help -v" '
581 581 'to show aliases and global options):\n\n'))
582 582
583 583 h = {}
584 584 cmds = {}
585 585 for c, e in table.items():
586 586 f = c.split("|")[0]
587 587 if cmd == "shortlist" and not f.startswith("^"):
588 588 continue
589 589 f = f.lstrip("^")
590 590 if not ui.debugflag and f.startswith("debug"):
591 591 continue
592 592 doc = e[0].__doc__
593 593 if not doc:
594 594 doc = _("(No help text available)")
595 595 h[f] = doc.splitlines(0)[0].rstrip()
596 596 cmds[f] = c.lstrip("^")
597 597
598 598 fns = h.keys()
599 599 fns.sort()
600 600 m = max(map(len, fns))
601 601 for f in fns:
602 602 if ui.verbose:
603 603 commands = cmds[f].replace("|",", ")
604 604 ui.write(" %s:\n %s\n"%(commands, h[f]))
605 605 else:
606 606 ui.write(' %-*s %s\n' % (m, f, h[f]))
607 607
608 608 # global options
609 609 if ui.verbose:
610 610 option_lists.append(("global options", globalopts))
611 611
612 612 # list all option lists
613 613 opt_output = []
614 614 for title, options in option_lists:
615 615 opt_output.append(("\n%s:\n" % title, None))
616 616 for shortopt, longopt, default, desc in options:
617 617 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
618 618 longopt and " --%s" % longopt),
619 619 "%s%s" % (desc,
620 620 default
621 621 and _(" (default: %s)") % default
622 622 or "")))
623 623
624 624 if opt_output:
625 625 opts_len = max([len(line[0]) for line in opt_output if line[1]])
626 626 for first, second in opt_output:
627 627 if second:
628 628 ui.write(" %-*s %s\n" % (opts_len, first, second))
629 629 else:
630 630 ui.write("%s\n" % first)
631 631
632 632 # Commands start here, listed alphabetically
633 633
634 634 def add(ui, repo, *pats, **opts):
635 635 """add the specified files on the next commit
636 636
637 637 Schedule files to be version controlled and added to the repository.
638 638
639 639 The files will be added to the repository at the next commit.
640 640
641 641 If no names are given, add all files in the repository.
642 642 """
643 643
644 644 names = []
645 645 for src, abs, rel, exact in walk(repo, pats, opts):
646 646 if exact:
647 647 if ui.verbose:
648 648 ui.status(_('adding %s\n') % rel)
649 649 names.append(abs)
650 650 elif repo.dirstate.state(abs) == '?':
651 651 ui.status(_('adding %s\n') % rel)
652 652 names.append(abs)
653 653 if not opts.get('dry_run'):
654 654 repo.add(names)
655 655
656 656 def addremove(ui, repo, *pats, **opts):
657 657 """add all new files, delete all missing files (DEPRECATED)
658 658
659 659 (DEPRECATED)
660 660 Add all new files and remove all missing files from the repository.
661 661
662 662 New files are ignored if they match any of the patterns in .hgignore. As
663 663 with add, these changes take effect at the next commit.
664 664
665 665 This command is now deprecated and will be removed in a future
666 666 release. Please use add and remove --after instead.
667 667 """
668 668 ui.warn(_('(the addremove command is deprecated; use add and remove '
669 669 '--after instead)\n'))
670 670 return addremove_lock(ui, repo, pats, opts)
671 671
672 672 def addremove_lock(ui, repo, pats, opts, wlock=None):
673 673 add, remove = [], []
674 674 for src, abs, rel, exact in walk(repo, pats, opts):
675 675 if src == 'f' and repo.dirstate.state(abs) == '?':
676 676 add.append(abs)
677 677 if ui.verbose or not exact:
678 678 ui.status(_('adding %s\n') % ((pats and rel) or abs))
679 679 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
680 680 remove.append(abs)
681 681 if ui.verbose or not exact:
682 682 ui.status(_('removing %s\n') % ((pats and rel) or abs))
683 683 if not opts.get('dry_run'):
684 684 repo.add(add, wlock=wlock)
685 685 repo.remove(remove, wlock=wlock)
686 686
687 687 def annotate(ui, repo, *pats, **opts):
688 688 """show changeset information per file line
689 689
690 690 List changes in files, showing the revision id responsible for each line
691 691
692 692 This command is useful to discover who did a change or when a change took
693 693 place.
694 694
695 695 Without the -a option, annotate will avoid processing files it
696 696 detects as binary. With -a, annotate will generate an annotation
697 697 anyway, probably with undesirable results.
698 698 """
699 699 def getnode(rev):
700 700 return short(repo.changelog.node(rev))
701 701
702 702 ucache = {}
703 703 def getname(rev):
704 704 cl = repo.changelog.read(repo.changelog.node(rev))
705 705 return trimuser(ui, cl[1], rev, ucache)
706 706
707 707 dcache = {}
708 708 def getdate(rev):
709 709 datestr = dcache.get(rev)
710 710 if datestr is None:
711 711 cl = repo.changelog.read(repo.changelog.node(rev))
712 712 datestr = dcache[rev] = util.datestr(cl[2])
713 713 return datestr
714 714
715 715 if not pats:
716 716 raise util.Abort(_('at least one file name or pattern required'))
717 717
718 718 opmap = [['user', getname], ['number', str], ['changeset', getnode],
719 719 ['date', getdate]]
720 720 if not opts['user'] and not opts['changeset'] and not opts['date']:
721 721 opts['number'] = 1
722 722
723 723 if opts['rev']:
724 724 node = repo.changelog.lookup(opts['rev'])
725 725 else:
726 726 node = repo.dirstate.parents()[0]
727 727 change = repo.changelog.read(node)
728 728 mmap = repo.manifest.read(change[0])
729 729
730 730 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
731 731 f = repo.file(abs)
732 732 if not opts['text'] and util.binary(f.read(mmap[abs])):
733 733 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
734 734 continue
735 735
736 736 lines = f.annotate(mmap[abs])
737 737 pieces = []
738 738
739 739 for o, f in opmap:
740 740 if opts[o]:
741 741 l = [f(n) for n, dummy in lines]
742 742 if l:
743 743 m = max(map(len, l))
744 744 pieces.append(["%*s" % (m, x) for x in l])
745 745
746 746 if pieces:
747 747 for p, l in zip(zip(*pieces), lines):
748 748 ui.write("%s: %s" % (" ".join(p), l[1]))
749 749
750 750 def archive(ui, repo, dest, **opts):
751 751 '''create unversioned archive of a repository revision
752 752
753 753 By default, the revision used is the parent of the working
754 754 directory; use "-r" to specify a different revision.
755 755
756 756 To specify the type of archive to create, use "-t". Valid
757 757 types are:
758 758
759 759 "files" (default): a directory full of files
760 760 "tar": tar archive, uncompressed
761 761 "tbz2": tar archive, compressed using bzip2
762 762 "tgz": tar archive, compressed using gzip
763 763 "uzip": zip archive, uncompressed
764 764 "zip": zip archive, compressed using deflate
765 765
766 766 The exact name of the destination archive or directory is given
767 767 using a format string; see "hg help export" for details.
768 768
769 769 Each member added to an archive file has a directory prefix
770 770 prepended. Use "-p" to specify a format string for the prefix.
771 771 The default is the basename of the archive, with suffixes removed.
772 772 '''
773 773
774 774 if opts['rev']:
775 775 node = repo.lookup(opts['rev'])
776 776 else:
777 777 node, p2 = repo.dirstate.parents()
778 778 if p2 != nullid:
779 779 raise util.Abort(_('uncommitted merge - please provide a '
780 780 'specific revision'))
781 781
782 782 dest = make_filename(repo, repo.changelog, dest, node)
783 783 if os.path.realpath(dest) == repo.root:
784 784 raise util.Abort(_('repository root cannot be destination'))
785 785 dummy, matchfn, dummy = matchpats(repo, [], opts)
786 786 kind = opts.get('type') or 'files'
787 787 prefix = opts['prefix']
788 788 if dest == '-':
789 789 if kind == 'files':
790 790 raise util.Abort(_('cannot archive plain files to stdout'))
791 791 dest = sys.stdout
792 792 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
793 793 prefix = make_filename(repo, repo.changelog, prefix, node)
794 794 archival.archive(repo, dest, node, kind, not opts['no_decode'],
795 795 matchfn, prefix)
796 796
797 797 def backout(ui, repo, rev, **opts):
798 798 '''reverse effect of earlier changeset
799 799
800 800 Commit the backed out changes as a new changeset. The new
801 801 changeset is a child of the backed out changeset.
802 802
803 803 If you back out a changeset other than the tip, a new head is
804 804 created. This head is the parent of the working directory. If
805 805 you back out an old changeset, your working directory will appear
806 806 old after the backout. You should merge the backout changeset
807 807 with another head.
808 808
809 809 The --merge option remembers the parent of the working directory
810 810 before starting the backout, then merges the new head with that
811 811 changeset afterwards. This saves you from doing the merge by
812 812 hand. The result of this merge is not committed, as for a normal
813 813 merge.'''
814 814
815 815 bail_if_changed(repo)
816 816 op1, op2 = repo.dirstate.parents()
817 817 if op2 != nullid:
818 818 raise util.Abort(_('outstanding uncommitted merge'))
819 819 node = repo.lookup(rev)
820 820 parent, p2 = repo.changelog.parents(node)
821 821 if parent == nullid:
822 822 raise util.Abort(_('cannot back out a change with no parents'))
823 823 if p2 != nullid:
824 824 raise util.Abort(_('cannot back out a merge'))
825 825 repo.update(node, force=True, show_stats=False)
826 826 revert_opts = opts.copy()
827 827 revert_opts['rev'] = hex(parent)
828 828 revert(ui, repo, **revert_opts)
829 829 commit_opts = opts.copy()
830 830 commit_opts['addremove'] = False
831 831 if not commit_opts['message'] and not commit_opts['logfile']:
832 832 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
833 833 commit_opts['force_editor'] = True
834 834 commit(ui, repo, **commit_opts)
835 835 def nice(node):
836 836 return '%d:%s' % (repo.changelog.rev(node), short(node))
837 837 ui.status(_('changeset %s backs out changeset %s\n') %
838 838 (nice(repo.changelog.tip()), nice(node)))
839 839 if op1 != node:
840 840 if opts['merge']:
841 841 ui.status(_('merging with changeset %s\n') % nice(op1))
842 842 doupdate(ui, repo, hex(op1), **opts)
843 843 else:
844 844 ui.status(_('the backout changeset is a new head - '
845 845 'do not forget to merge\n'))
846 846 ui.status(_('(use "backout -m" if you want to auto-merge)\n'))
847 847
848 848 def bundle(ui, repo, fname, dest=None, **opts):
849 849 """create a changegroup file
850 850
851 851 Generate a compressed changegroup file collecting all changesets
852 852 not found in the other repository.
853 853
854 854 This file can then be transferred using conventional means and
855 855 applied to another repository with the unbundle command. This is
856 856 useful when native push and pull are not available or when
857 857 exporting an entire repository is undesirable. The standard file
858 858 extension is ".hg".
859 859
860 860 Unlike import/export, this exactly preserves all changeset
861 861 contents including permissions, rename data, and revision history.
862 862 """
863 863 dest = ui.expandpath(dest or 'default-push', dest or 'default')
864 864 other = hg.repository(ui, dest)
865 865 o = repo.findoutgoing(other, force=opts['force'])
866 866 cg = repo.changegroup(o, 'bundle')
867 867 write_bundle(cg, fname)
868 868
869 869 def cat(ui, repo, file1, *pats, **opts):
870 870 """output the latest or given revisions of files
871 871
872 872 Print the specified files as they were at the given revision.
873 873 If no revision is given then the tip is used.
874 874
875 875 Output may be to a file, in which case the name of the file is
876 876 given using a format string. The formatting rules are the same as
877 877 for the export command, with the following additions:
878 878
879 879 %s basename of file being printed
880 880 %d dirname of file being printed, or '.' if in repo root
881 881 %p root-relative path name of file being printed
882 882 """
883 883 mf = {}
884 884 rev = opts['rev']
885 885 if rev:
886 886 node = repo.lookup(rev)
887 887 else:
888 888 node = repo.changelog.tip()
889 889 change = repo.changelog.read(node)
890 890 mf = repo.manifest.read(change[0])
891 891 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
892 892 r = repo.file(abs)
893 893 n = mf[abs]
894 894 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
895 895 fp.write(r.read(n))
896 896
897 897 def clone(ui, source, dest=None, **opts):
898 898 """make a copy of an existing repository
899 899
900 900 Create a copy of an existing repository in a new directory.
901 901
902 902 If no destination directory name is specified, it defaults to the
903 903 basename of the source.
904 904
905 905 The location of the source is added to the new repository's
906 906 .hg/hgrc file, as the default to be used for future pulls.
907 907
908 908 For efficiency, hardlinks are used for cloning whenever the source
909 909 and destination are on the same filesystem. Some filesystems,
910 910 such as AFS, implement hardlinking incorrectly, but do not report
911 911 errors. In these cases, use the --pull option to avoid
912 912 hardlinking.
913 913
914 914 See pull for valid source format details.
915 915 """
916 916 if dest is None:
917 917 dest = os.path.basename(os.path.normpath(source))
918 918
919 919 if os.path.exists(dest):
920 920 raise util.Abort(_("destination '%s' already exists"), dest)
921 921
922 922 dest = os.path.realpath(dest)
923 923
924 924 class Dircleanup(object):
925 925 def __init__(self, dir_):
926 926 self.rmtree = shutil.rmtree
927 927 self.dir_ = dir_
928 928 os.mkdir(dir_)
929 929 def close(self):
930 930 self.dir_ = None
931 931 def __del__(self):
932 932 if self.dir_:
933 933 self.rmtree(self.dir_, True)
934 934
935 935 if opts['ssh']:
936 936 ui.setconfig("ui", "ssh", opts['ssh'])
937 937 if opts['remotecmd']:
938 938 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
939 939
940 940 source = ui.expandpath(source)
941 941
942 942 d = Dircleanup(dest)
943 943 abspath = source
944 944 other = hg.repository(ui, source)
945 945
946 946 copy = False
947 947 if other.dev() != -1:
948 948 abspath = os.path.abspath(source)
949 949 if not opts['pull'] and not opts['rev']:
950 950 copy = True
951 951
952 952 if copy:
953 953 try:
954 954 # we use a lock here because if we race with commit, we
955 955 # can end up with extra data in the cloned revlogs that's
956 956 # not pointed to by changesets, thus causing verify to
957 957 # fail
958 958 l1 = other.lock()
959 959 except lock.LockException:
960 960 copy = False
961 961
962 962 if copy:
963 963 # we lock here to avoid premature writing to the target
964 964 os.mkdir(os.path.join(dest, ".hg"))
965 965 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
966 966
967 967 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
968 968 for f in files.split():
969 969 src = os.path.join(source, ".hg", f)
970 970 dst = os.path.join(dest, ".hg", f)
971 971 try:
972 972 util.copyfiles(src, dst)
973 973 except OSError, inst:
974 974 if inst.errno != errno.ENOENT:
975 975 raise
976 976
977 977 repo = hg.repository(ui, dest)
978 978
979 979 else:
980 980 revs = None
981 981 if opts['rev']:
982 982 if not other.local():
983 983 error = _("clone -r not supported yet for remote repositories.")
984 984 raise util.Abort(error)
985 985 else:
986 986 revs = [other.lookup(rev) for rev in opts['rev']]
987 987 repo = hg.repository(ui, dest, create=1)
988 988 repo.pull(other, heads = revs)
989 989
990 990 f = repo.opener("hgrc", "w", text=True)
991 991 f.write("[paths]\n")
992 992 f.write("default = %s\n" % abspath)
993 993 f.close()
994 994
995 995 if not opts['noupdate']:
996 996 doupdate(repo.ui, repo)
997 997
998 998 d.close()
999 999
1000 1000 def commit(ui, repo, *pats, **opts):
1001 1001 """commit the specified files or all outstanding changes
1002 1002
1003 1003 Commit changes to the given files into the repository.
1004 1004
1005 1005 If a list of files is omitted, all changes reported by "hg status"
1006 1006 will be committed.
1007 1007
1008 1008 If no commit message is specified, the editor configured in your hgrc
1009 1009 or in the EDITOR environment variable is started to enter a message.
1010 1010 """
1011 1011 message = opts['message']
1012 1012 logfile = opts['logfile']
1013 1013
1014 1014 if message and logfile:
1015 1015 raise util.Abort(_('options --message and --logfile are mutually '
1016 1016 'exclusive'))
1017 1017 if not message and logfile:
1018 1018 try:
1019 1019 if logfile == '-':
1020 1020 message = sys.stdin.read()
1021 1021 else:
1022 1022 message = open(logfile).read()
1023 1023 except IOError, inst:
1024 1024 raise util.Abort(_("can't read commit message '%s': %s") %
1025 1025 (logfile, inst.strerror))
1026 1026
1027 1027 if opts['addremove']:
1028 1028 addremove_lock(ui, repo, pats, opts)
1029 1029 fns, match, anypats = matchpats(repo, pats, opts)
1030 1030 if pats:
1031 1031 modified, added, removed, deleted, unknown = (
1032 1032 repo.changes(files=fns, match=match))
1033 1033 files = modified + added + removed
1034 1034 else:
1035 1035 files = []
1036 1036 try:
1037 1037 repo.commit(files, message, opts['user'], opts['date'], match,
1038 1038 force_editor=opts.get('force_editor'))
1039 1039 except ValueError, inst:
1040 1040 raise util.Abort(str(inst))
1041 1041
1042 1042 def docopy(ui, repo, pats, opts, wlock):
1043 1043 # called with the repo lock held
1044 1044 cwd = repo.getcwd()
1045 1045 errors = 0
1046 1046 copied = []
1047 1047 targets = {}
1048 1048
1049 1049 def okaytocopy(abs, rel, exact):
1050 1050 reasons = {'?': _('is not managed'),
1051 1051 'a': _('has been marked for add'),
1052 1052 'r': _('has been marked for remove')}
1053 1053 state = repo.dirstate.state(abs)
1054 1054 reason = reasons.get(state)
1055 1055 if reason:
1056 1056 if state == 'a':
1057 1057 origsrc = repo.dirstate.copied(abs)
1058 1058 if origsrc is not None:
1059 1059 return origsrc
1060 1060 if exact:
1061 1061 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1062 1062 else:
1063 1063 return abs
1064 1064
1065 1065 def copy(origsrc, abssrc, relsrc, target, exact):
1066 1066 abstarget = util.canonpath(repo.root, cwd, target)
1067 1067 reltarget = util.pathto(cwd, abstarget)
1068 1068 prevsrc = targets.get(abstarget)
1069 1069 if prevsrc is not None:
1070 1070 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1071 1071 (reltarget, abssrc, prevsrc))
1072 1072 return
1073 1073 if (not opts['after'] and os.path.exists(reltarget) or
1074 1074 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1075 1075 if not opts['force']:
1076 1076 ui.warn(_('%s: not overwriting - file exists\n') %
1077 1077 reltarget)
1078 1078 return
1079 1079 if not opts['after'] and not opts.get('dry_run'):
1080 1080 os.unlink(reltarget)
1081 1081 if opts['after']:
1082 1082 if not os.path.exists(reltarget):
1083 1083 return
1084 1084 else:
1085 1085 targetdir = os.path.dirname(reltarget) or '.'
1086 1086 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
1087 1087 os.makedirs(targetdir)
1088 1088 try:
1089 1089 restore = repo.dirstate.state(abstarget) == 'r'
1090 1090 if restore and not opts.get('dry_run'):
1091 1091 repo.undelete([abstarget], wlock)
1092 1092 try:
1093 1093 if not opts.get('dry_run'):
1094 1094 shutil.copyfile(relsrc, reltarget)
1095 1095 shutil.copymode(relsrc, reltarget)
1096 1096 restore = False
1097 1097 finally:
1098 1098 if restore:
1099 1099 repo.remove([abstarget], wlock)
1100 1100 except shutil.Error, inst:
1101 1101 raise util.Abort(str(inst))
1102 1102 except IOError, inst:
1103 1103 if inst.errno == errno.ENOENT:
1104 1104 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1105 1105 else:
1106 1106 ui.warn(_('%s: cannot copy - %s\n') %
1107 1107 (relsrc, inst.strerror))
1108 1108 errors += 1
1109 1109 return
1110 1110 if ui.verbose or not exact:
1111 1111 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1112 1112 targets[abstarget] = abssrc
1113 1113 if abstarget != origsrc and not opts.get('dry_run'):
1114 1114 repo.copy(origsrc, abstarget, wlock)
1115 1115 copied.append((abssrc, relsrc, exact))
1116 1116
1117 1117 def targetpathfn(pat, dest, srcs):
1118 1118 if os.path.isdir(pat):
1119 1119 abspfx = util.canonpath(repo.root, cwd, pat)
1120 1120 if destdirexists:
1121 1121 striplen = len(os.path.split(abspfx)[0])
1122 1122 else:
1123 1123 striplen = len(abspfx)
1124 1124 if striplen:
1125 1125 striplen += len(os.sep)
1126 1126 res = lambda p: os.path.join(dest, p[striplen:])
1127 1127 elif destdirexists:
1128 1128 res = lambda p: os.path.join(dest, os.path.basename(p))
1129 1129 else:
1130 1130 res = lambda p: dest
1131 1131 return res
1132 1132
1133 1133 def targetpathafterfn(pat, dest, srcs):
1134 1134 if util.patkind(pat, None)[0]:
1135 1135 # a mercurial pattern
1136 1136 res = lambda p: os.path.join(dest, os.path.basename(p))
1137 1137 else:
1138 1138 abspfx = util.canonpath(repo.root, cwd, pat)
1139 1139 if len(abspfx) < len(srcs[0][0]):
1140 1140 # A directory. Either the target path contains the last
1141 1141 # component of the source path or it does not.
1142 1142 def evalpath(striplen):
1143 1143 score = 0
1144 1144 for s in srcs:
1145 1145 t = os.path.join(dest, s[0][striplen:])
1146 1146 if os.path.exists(t):
1147 1147 score += 1
1148 1148 return score
1149 1149
1150 1150 striplen = len(abspfx)
1151 1151 if striplen:
1152 1152 striplen += len(os.sep)
1153 1153 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1154 1154 score = evalpath(striplen)
1155 1155 striplen1 = len(os.path.split(abspfx)[0])
1156 1156 if striplen1:
1157 1157 striplen1 += len(os.sep)
1158 1158 if evalpath(striplen1) > score:
1159 1159 striplen = striplen1
1160 1160 res = lambda p: os.path.join(dest, p[striplen:])
1161 1161 else:
1162 1162 # a file
1163 1163 if destdirexists:
1164 1164 res = lambda p: os.path.join(dest, os.path.basename(p))
1165 1165 else:
1166 1166 res = lambda p: dest
1167 1167 return res
1168 1168
1169 1169
1170 1170 pats = list(pats)
1171 1171 if not pats:
1172 1172 raise util.Abort(_('no source or destination specified'))
1173 1173 if len(pats) == 1:
1174 1174 raise util.Abort(_('no destination specified'))
1175 1175 dest = pats.pop()
1176 1176 destdirexists = os.path.isdir(dest)
1177 1177 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1178 1178 raise util.Abort(_('with multiple sources, destination must be an '
1179 1179 'existing directory'))
1180 1180 if opts['after']:
1181 1181 tfn = targetpathafterfn
1182 1182 else:
1183 1183 tfn = targetpathfn
1184 1184 copylist = []
1185 1185 for pat in pats:
1186 1186 srcs = []
1187 1187 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1188 1188 origsrc = okaytocopy(abssrc, relsrc, exact)
1189 1189 if origsrc:
1190 1190 srcs.append((origsrc, abssrc, relsrc, exact))
1191 1191 if not srcs:
1192 1192 continue
1193 1193 copylist.append((tfn(pat, dest, srcs), srcs))
1194 1194 if not copylist:
1195 1195 raise util.Abort(_('no files to copy'))
1196 1196
1197 1197 for targetpath, srcs in copylist:
1198 1198 for origsrc, abssrc, relsrc, exact in srcs:
1199 1199 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1200 1200
1201 1201 if errors:
1202 1202 ui.warn(_('(consider using --after)\n'))
1203 1203 return errors, copied
1204 1204
1205 1205 def copy(ui, repo, *pats, **opts):
1206 1206 """mark files as copied for the next commit
1207 1207
1208 1208 Mark dest as having copies of source files. If dest is a
1209 1209 directory, copies are put in that directory. If dest is a file,
1210 1210 there can only be one source.
1211 1211
1212 1212 By default, this command copies the contents of files as they
1213 1213 stand in the working directory. If invoked with --after, the
1214 1214 operation is recorded, but no copying is performed.
1215 1215
1216 1216 This command takes effect in the next commit.
1217 1217
1218 1218 NOTE: This command should be treated as experimental. While it
1219 1219 should properly record copied files, this information is not yet
1220 1220 fully used by merge, nor fully reported by log.
1221 1221 """
1222 1222 wlock = repo.wlock(0)
1223 1223 errs, copied = docopy(ui, repo, pats, opts, wlock)
1224 1224 return errs
1225 1225
1226 1226 def debugancestor(ui, index, rev1, rev2):
1227 1227 """find the ancestor revision of two revisions in a given index"""
1228 1228 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1229 1229 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1230 1230 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1231 1231
1232 1232 def debugcomplete(ui, cmd='', **opts):
1233 1233 """returns the completion list associated with the given command"""
1234 1234
1235 1235 if opts['options']:
1236 1236 options = []
1237 1237 otables = [globalopts]
1238 1238 if cmd:
1239 1239 aliases, entry = find(cmd)
1240 1240 otables.append(entry[1])
1241 1241 for t in otables:
1242 1242 for o in t:
1243 1243 if o[0]:
1244 1244 options.append('-%s' % o[0])
1245 1245 options.append('--%s' % o[1])
1246 1246 ui.write("%s\n" % "\n".join(options))
1247 1247 return
1248 1248
1249 1249 clist = findpossible(cmd).keys()
1250 1250 clist.sort()
1251 1251 ui.write("%s\n" % "\n".join(clist))
1252 1252
1253 1253 def debugrebuildstate(ui, repo, rev=None):
1254 1254 """rebuild the dirstate as it would look like for the given revision"""
1255 1255 if not rev:
1256 1256 rev = repo.changelog.tip()
1257 1257 else:
1258 1258 rev = repo.lookup(rev)
1259 1259 change = repo.changelog.read(rev)
1260 1260 n = change[0]
1261 1261 files = repo.manifest.readflags(n)
1262 1262 wlock = repo.wlock()
1263 1263 repo.dirstate.rebuild(rev, files.iteritems())
1264 1264
1265 1265 def debugcheckstate(ui, repo):
1266 1266 """validate the correctness of the current dirstate"""
1267 1267 parent1, parent2 = repo.dirstate.parents()
1268 1268 repo.dirstate.read()
1269 1269 dc = repo.dirstate.map
1270 1270 keys = dc.keys()
1271 1271 keys.sort()
1272 1272 m1n = repo.changelog.read(parent1)[0]
1273 1273 m2n = repo.changelog.read(parent2)[0]
1274 1274 m1 = repo.manifest.read(m1n)
1275 1275 m2 = repo.manifest.read(m2n)
1276 1276 errors = 0
1277 1277 for f in dc:
1278 1278 state = repo.dirstate.state(f)
1279 1279 if state in "nr" and f not in m1:
1280 1280 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1281 1281 errors += 1
1282 1282 if state in "a" and f in m1:
1283 1283 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1284 1284 errors += 1
1285 1285 if state in "m" and f not in m1 and f not in m2:
1286 1286 ui.warn(_("%s in state %s, but not in either manifest\n") %
1287 1287 (f, state))
1288 1288 errors += 1
1289 1289 for f in m1:
1290 1290 state = repo.dirstate.state(f)
1291 1291 if state not in "nrm":
1292 1292 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1293 1293 errors += 1
1294 1294 if errors:
1295 1295 error = _(".hg/dirstate inconsistent with current parent's manifest")
1296 1296 raise util.Abort(error)
1297 1297
1298 1298 def debugconfig(ui, repo, *values):
1299 1299 """show combined config settings from all hgrc files
1300 1300
1301 1301 With no args, print names and values of all config items.
1302 1302
1303 1303 With one arg of the form section.name, print just the value of
1304 1304 that config item.
1305 1305
1306 1306 With multiple args, print names and values of all config items
1307 1307 with matching section names."""
1308 1308
1309 1309 if values:
1310 1310 if len([v for v in values if '.' in v]) > 1:
1311 1311 raise util.Abort(_('only one config item permitted'))
1312 1312 for section, name, value in ui.walkconfig():
1313 1313 sectname = section + '.' + name
1314 1314 if values:
1315 1315 for v in values:
1316 1316 if v == section:
1317 1317 ui.write('%s=%s\n' % (sectname, value))
1318 1318 elif v == sectname:
1319 1319 ui.write(value, '\n')
1320 1320 else:
1321 1321 ui.write('%s=%s\n' % (sectname, value))
1322 1322
1323 1323 def debugsetparents(ui, repo, rev1, rev2=None):
1324 1324 """manually set the parents of the current working directory
1325 1325
1326 1326 This is useful for writing repository conversion tools, but should
1327 1327 be used with care.
1328 1328 """
1329 1329
1330 1330 if not rev2:
1331 1331 rev2 = hex(nullid)
1332 1332
1333 1333 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1334 1334
1335 1335 def debugstate(ui, repo):
1336 1336 """show the contents of the current dirstate"""
1337 1337 repo.dirstate.read()
1338 1338 dc = repo.dirstate.map
1339 1339 keys = dc.keys()
1340 1340 keys.sort()
1341 1341 for file_ in keys:
1342 1342 ui.write("%c %3o %10d %s %s\n"
1343 1343 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1344 1344 time.strftime("%x %X",
1345 1345 time.localtime(dc[file_][3])), file_))
1346 1346 for f in repo.dirstate.copies:
1347 1347 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1348 1348
1349 1349 def debugdata(ui, file_, rev):
1350 1350 """dump the contents of an data file revision"""
1351 1351 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1352 1352 file_[:-2] + ".i", file_, 0)
1353 1353 try:
1354 1354 ui.write(r.revision(r.lookup(rev)))
1355 1355 except KeyError:
1356 1356 raise util.Abort(_('invalid revision identifier %s'), rev)
1357 1357
1358 1358 def debugindex(ui, file_):
1359 1359 """dump the contents of an index file"""
1360 1360 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1361 1361 ui.write(" rev offset length base linkrev" +
1362 1362 " nodeid p1 p2\n")
1363 1363 for i in range(r.count()):
1364 1364 node = r.node(i)
1365 1365 pp = r.parents(node)
1366 1366 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1367 1367 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1368 1368 short(node), short(pp[0]), short(pp[1])))
1369 1369
1370 1370 def debugindexdot(ui, file_):
1371 1371 """dump an index DAG as a .dot file"""
1372 1372 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1373 1373 ui.write("digraph G {\n")
1374 1374 for i in range(r.count()):
1375 1375 node = r.node(i)
1376 1376 pp = r.parents(node)
1377 1377 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1378 1378 if pp[1] != nullid:
1379 1379 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1380 1380 ui.write("}\n")
1381 1381
1382 1382 def debugrename(ui, repo, file, rev=None):
1383 1383 """dump rename information"""
1384 1384 r = repo.file(relpath(repo, [file])[0])
1385 1385 if rev:
1386 1386 try:
1387 1387 # assume all revision numbers are for changesets
1388 1388 n = repo.lookup(rev)
1389 1389 change = repo.changelog.read(n)
1390 1390 m = repo.manifest.read(change[0])
1391 1391 n = m[relpath(repo, [file])[0]]
1392 1392 except (hg.RepoError, KeyError):
1393 1393 n = r.lookup(rev)
1394 1394 else:
1395 1395 n = r.tip()
1396 1396 m = r.renamed(n)
1397 1397 if m:
1398 1398 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1399 1399 else:
1400 1400 ui.write(_("not renamed\n"))
1401 1401
1402 1402 def debugwalk(ui, repo, *pats, **opts):
1403 1403 """show how files match on given patterns"""
1404 1404 items = list(walk(repo, pats, opts))
1405 1405 if not items:
1406 1406 return
1407 1407 fmt = '%%s %%-%ds %%-%ds %%s' % (
1408 1408 max([len(abs) for (src, abs, rel, exact) in items]),
1409 1409 max([len(rel) for (src, abs, rel, exact) in items]))
1410 1410 for src, abs, rel, exact in items:
1411 1411 line = fmt % (src, abs, rel, exact and 'exact' or '')
1412 1412 ui.write("%s\n" % line.rstrip())
1413 1413
1414 1414 def diff(ui, repo, *pats, **opts):
1415 1415 """diff repository (or selected files)
1416 1416
1417 1417 Show differences between revisions for the specified files.
1418 1418
1419 1419 Differences between files are shown using the unified diff format.
1420 1420
1421 1421 When two revision arguments are given, then changes are shown
1422 1422 between those revisions. If only one revision is specified then
1423 1423 that revision is compared to the working directory, and, when no
1424 1424 revisions are specified, the working directory files are compared
1425 1425 to its parent.
1426 1426
1427 1427 Without the -a option, diff will avoid generating diffs of files
1428 1428 it detects as binary. With -a, diff will generate a diff anyway,
1429 1429 probably with undesirable results.
1430 1430 """
1431 1431 node1, node2 = revpair(ui, repo, opts['rev'])
1432 1432
1433 1433 fns, matchfn, anypats = matchpats(repo, pats, opts)
1434 1434
1435 1435 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1436 1436 text=opts['text'], opts=opts)
1437 1437
1438 1438 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1439 1439 node = repo.lookup(changeset)
1440 1440 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1441 1441 if opts['switch_parent']:
1442 1442 parents.reverse()
1443 1443 prev = (parents and parents[0]) or nullid
1444 1444 change = repo.changelog.read(node)
1445 1445
1446 1446 fp = make_file(repo, repo.changelog, opts['output'],
1447 1447 node=node, total=total, seqno=seqno,
1448 1448 revwidth=revwidth)
1449 1449 if fp != sys.stdout:
1450 1450 ui.note("%s\n" % fp.name)
1451 1451
1452 1452 fp.write("# HG changeset patch\n")
1453 1453 fp.write("# User %s\n" % change[1])
1454 1454 fp.write("# Date %d %d\n" % change[2])
1455 1455 fp.write("# Node ID %s\n" % hex(node))
1456 1456 fp.write("# Parent %s\n" % hex(prev))
1457 1457 if len(parents) > 1:
1458 1458 fp.write("# Parent %s\n" % hex(parents[1]))
1459 1459 fp.write(change[4].rstrip())
1460 1460 fp.write("\n\n")
1461 1461
1462 1462 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1463 1463 if fp != sys.stdout:
1464 1464 fp.close()
1465 1465
1466 1466 def export(ui, repo, *changesets, **opts):
1467 1467 """dump the header and diffs for one or more changesets
1468 1468
1469 1469 Print the changeset header and diffs for one or more revisions.
1470 1470
1471 1471 The information shown in the changeset header is: author,
1472 1472 changeset hash, parent and commit comment.
1473 1473
1474 1474 Output may be to a file, in which case the name of the file is
1475 1475 given using a format string. The formatting rules are as follows:
1476 1476
1477 1477 %% literal "%" character
1478 1478 %H changeset hash (40 bytes of hexadecimal)
1479 1479 %N number of patches being generated
1480 1480 %R changeset revision number
1481 1481 %b basename of the exporting repository
1482 1482 %h short-form changeset hash (12 bytes of hexadecimal)
1483 1483 %n zero-padded sequence number, starting at 1
1484 1484 %r zero-padded changeset revision number
1485 1485
1486 1486 Without the -a option, export will avoid generating diffs of files
1487 1487 it detects as binary. With -a, export will generate a diff anyway,
1488 1488 probably with undesirable results.
1489 1489
1490 1490 With the --switch-parent option, the diff will be against the second
1491 1491 parent. It can be useful to review a merge.
1492 1492 """
1493 1493 if not changesets:
1494 1494 raise util.Abort(_("export requires at least one changeset"))
1495 1495 seqno = 0
1496 1496 revs = list(revrange(ui, repo, changesets))
1497 1497 total = len(revs)
1498 1498 revwidth = max(map(len, revs))
1499 1499 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1500 1500 ui.note(msg)
1501 1501 for cset in revs:
1502 1502 seqno += 1
1503 1503 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1504 1504
1505 1505 def forget(ui, repo, *pats, **opts):
1506 1506 """don't add the specified files on the next commit (DEPRECATED)
1507 1507
1508 1508 (DEPRECATED)
1509 1509 Undo an 'hg add' scheduled for the next commit.
1510 1510
1511 1511 This command is now deprecated and will be removed in a future
1512 1512 release. Please use revert instead.
1513 1513 """
1514 1514 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1515 1515 forget = []
1516 1516 for src, abs, rel, exact in walk(repo, pats, opts):
1517 1517 if repo.dirstate.state(abs) == 'a':
1518 1518 forget.append(abs)
1519 1519 if ui.verbose or not exact:
1520 1520 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1521 1521 repo.forget(forget)
1522 1522
1523 1523 def grep(ui, repo, pattern, *pats, **opts):
1524 1524 """search for a pattern in specified files and revisions
1525 1525
1526 1526 Search revisions of files for a regular expression.
1527 1527
1528 1528 This command behaves differently than Unix grep. It only accepts
1529 1529 Python/Perl regexps. It searches repository history, not the
1530 1530 working directory. It always prints the revision number in which
1531 1531 a match appears.
1532 1532
1533 1533 By default, grep only prints output for the first revision of a
1534 1534 file in which it finds a match. To get it to print every revision
1535 1535 that contains a change in match status ("-" for a match that
1536 1536 becomes a non-match, or "+" for a non-match that becomes a match),
1537 1537 use the --all flag.
1538 1538 """
1539 1539 reflags = 0
1540 1540 if opts['ignore_case']:
1541 1541 reflags |= re.I
1542 1542 regexp = re.compile(pattern, reflags)
1543 1543 sep, eol = ':', '\n'
1544 1544 if opts['print0']:
1545 1545 sep = eol = '\0'
1546 1546
1547 1547 fcache = {}
1548 1548 def getfile(fn):
1549 1549 if fn not in fcache:
1550 1550 fcache[fn] = repo.file(fn)
1551 1551 return fcache[fn]
1552 1552
1553 1553 def matchlines(body):
1554 1554 begin = 0
1555 1555 linenum = 0
1556 1556 while True:
1557 1557 match = regexp.search(body, begin)
1558 1558 if not match:
1559 1559 break
1560 1560 mstart, mend = match.span()
1561 1561 linenum += body.count('\n', begin, mstart) + 1
1562 1562 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1563 1563 lend = body.find('\n', mend)
1564 1564 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1565 1565 begin = lend + 1
1566 1566
1567 1567 class linestate(object):
1568 1568 def __init__(self, line, linenum, colstart, colend):
1569 1569 self.line = line
1570 1570 self.linenum = linenum
1571 1571 self.colstart = colstart
1572 1572 self.colend = colend
1573 1573 def __eq__(self, other):
1574 1574 return self.line == other.line
1575 1575 def __hash__(self):
1576 1576 return hash(self.line)
1577 1577
1578 1578 matches = {}
1579 1579 def grepbody(fn, rev, body):
1580 1580 matches[rev].setdefault(fn, {})
1581 1581 m = matches[rev][fn]
1582 1582 for lnum, cstart, cend, line in matchlines(body):
1583 1583 s = linestate(line, lnum, cstart, cend)
1584 1584 m[s] = s
1585 1585
1586 1586 # FIXME: prev isn't used, why ?
1587 1587 prev = {}
1588 1588 ucache = {}
1589 1589 def display(fn, rev, states, prevstates):
1590 1590 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1591 1591 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1592 1592 counts = {'-': 0, '+': 0}
1593 1593 filerevmatches = {}
1594 1594 for l in diff:
1595 1595 if incrementing or not opts['all']:
1596 1596 change = ((l in prevstates) and '-') or '+'
1597 1597 r = rev
1598 1598 else:
1599 1599 change = ((l in states) and '-') or '+'
1600 1600 r = prev[fn]
1601 1601 cols = [fn, str(rev)]
1602 1602 if opts['line_number']:
1603 1603 cols.append(str(l.linenum))
1604 1604 if opts['all']:
1605 1605 cols.append(change)
1606 1606 if opts['user']:
1607 1607 cols.append(trimuser(ui, getchange(rev)[1], rev,
1608 1608 ucache))
1609 1609 if opts['files_with_matches']:
1610 1610 c = (fn, rev)
1611 1611 if c in filerevmatches:
1612 1612 continue
1613 1613 filerevmatches[c] = 1
1614 1614 else:
1615 1615 cols.append(l.line)
1616 1616 ui.write(sep.join(cols), eol)
1617 1617 counts[change] += 1
1618 1618 return counts['+'], counts['-']
1619 1619
1620 1620 fstate = {}
1621 1621 skip = {}
1622 1622 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1623 1623 count = 0
1624 1624 incrementing = False
1625 1625 for st, rev, fns in changeiter:
1626 1626 if st == 'window':
1627 1627 incrementing = rev
1628 1628 matches.clear()
1629 1629 elif st == 'add':
1630 1630 change = repo.changelog.read(repo.lookup(str(rev)))
1631 1631 mf = repo.manifest.read(change[0])
1632 1632 matches[rev] = {}
1633 1633 for fn in fns:
1634 1634 if fn in skip:
1635 1635 continue
1636 1636 fstate.setdefault(fn, {})
1637 1637 try:
1638 1638 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1639 1639 except KeyError:
1640 1640 pass
1641 1641 elif st == 'iter':
1642 1642 states = matches[rev].items()
1643 1643 states.sort()
1644 1644 for fn, m in states:
1645 1645 if fn in skip:
1646 1646 continue
1647 1647 if incrementing or not opts['all'] or fstate[fn]:
1648 1648 pos, neg = display(fn, rev, m, fstate[fn])
1649 1649 count += pos + neg
1650 1650 if pos and not opts['all']:
1651 1651 skip[fn] = True
1652 1652 fstate[fn] = m
1653 1653 prev[fn] = rev
1654 1654
1655 1655 if not incrementing:
1656 1656 fstate = fstate.items()
1657 1657 fstate.sort()
1658 1658 for fn, state in fstate:
1659 1659 if fn in skip:
1660 1660 continue
1661 1661 display(fn, rev, {}, state)
1662 1662 return (count == 0 and 1) or 0
1663 1663
1664 1664 def heads(ui, repo, **opts):
1665 1665 """show current repository heads
1666 1666
1667 1667 Show all repository head changesets.
1668 1668
1669 1669 Repository "heads" are changesets that don't have children
1670 1670 changesets. They are where development generally takes place and
1671 1671 are the usual targets for update and merge operations.
1672 1672 """
1673 1673 if opts['rev']:
1674 1674 heads = repo.heads(repo.lookup(opts['rev']))
1675 1675 else:
1676 1676 heads = repo.heads()
1677 1677 br = None
1678 1678 if opts['branches']:
1679 1679 br = repo.branchlookup(heads)
1680 1680 displayer = show_changeset(ui, repo, opts)
1681 1681 for n in heads:
1682 1682 displayer.show(changenode=n, brinfo=br)
1683 1683
1684 1684 def identify(ui, repo):
1685 1685 """print information about the working copy
1686 1686
1687 1687 Print a short summary of the current state of the repo.
1688 1688
1689 1689 This summary identifies the repository state using one or two parent
1690 1690 hash identifiers, followed by a "+" if there are uncommitted changes
1691 1691 in the working directory, followed by a list of tags for this revision.
1692 1692 """
1693 1693 parents = [p for p in repo.dirstate.parents() if p != nullid]
1694 1694 if not parents:
1695 1695 ui.write(_("unknown\n"))
1696 1696 return
1697 1697
1698 1698 hexfunc = ui.verbose and hex or short
1699 1699 modified, added, removed, deleted, unknown = repo.changes()
1700 1700 output = ["%s%s" %
1701 1701 ('+'.join([hexfunc(parent) for parent in parents]),
1702 1702 (modified or added or removed or deleted) and "+" or "")]
1703 1703
1704 1704 if not ui.quiet:
1705 1705 # multiple tags for a single parent separated by '/'
1706 1706 parenttags = ['/'.join(tags)
1707 1707 for tags in map(repo.nodetags, parents) if tags]
1708 1708 # tags for multiple parents separated by ' + '
1709 1709 if parenttags:
1710 1710 output.append(' + '.join(parenttags))
1711 1711
1712 1712 ui.write("%s\n" % ' '.join(output))
1713 1713
1714 1714 def import_(ui, repo, patch1, *patches, **opts):
1715 1715 """import an ordered set of patches
1716 1716
1717 1717 Import a list of patches and commit them individually.
1718 1718
1719 1719 If there are outstanding changes in the working directory, import
1720 1720 will abort unless given the -f flag.
1721 1721
1722 If a patch looks like a mail message (its first line starts with
1723 "From " or looks like an RFC822 header), it will not be applied
1724 unless the -f option is used. The importer neither parses nor
1725 discards mail headers, so use -f only to override the "mailness"
1726 safety check, not to import a real mail message.
1722 You can import a patch straight from a mail message. Even patches
1723 as attachments work (body part must be type text/plain or
1724 text/x-patch to be used). From and Subject headers of email
1725 message are used as default committer and commit message. All
1726 text/plain body parts before first diff are added to commit
1727 message.
1728
1729 If imported patch was generated by hg export, user and description
1730 from patch override values from message headers and body. Values
1731 given on command line with -m and -u override these.
1727 1732
1728 1733 To read a patch from standard input, use patch name "-".
1729 1734 """
1730 1735 patches = (patch1,) + patches
1731 1736
1732 1737 if not opts['force']:
1733 1738 bail_if_changed(repo)
1734 1739
1735 1740 d = opts["base"]
1736 1741 strip = opts["strip"]
1737 1742
1738 1743 mailre = re.compile(r'(?:From |[\w-]+:)')
1739 1744
1740 1745 # attempt to detect the start of a patch
1741 1746 # (this heuristic is borrowed from quilt)
1742 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1747 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1743 1748 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1744 '(---|\*\*\*)[ \t])')
1749 '(---|\*\*\*)[ \t])', re.MULTILINE)
1745 1750
1746 1751 for patch in patches:
1747 1752 pf = os.path.join(d, patch)
1748 1753
1749 message = []
1754 message = None
1750 1755 user = None
1751 1756 date = None
1752 1757 hgpatch = False
1758
1759 p = email.Parser.Parser()
1753 1760 if pf == '-':
1754 f = sys.stdin
1755 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
1756 pf = tmpname
1757 tmpfp = os.fdopen(fd, 'w')
1761 msg = p.parse(sys.stdin)
1758 1762 ui.status(_("applying patch from stdin\n"))
1759 1763 else:
1760 f = open(pf)
1761 tmpfp, tmpname = None, None
1764 msg = p.parse(file(pf))
1762 1765 ui.status(_("applying %s\n") % patch)
1766
1767 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
1768 tmpfp = os.fdopen(fd, 'w')
1763 1769 try:
1764 while True:
1765 line = f.readline()
1766 if not line: break
1767 if tmpfp: tmpfp.write(line)
1768 line = line.rstrip()
1769 if (not message and not hgpatch and
1770 mailre.match(line) and not opts['force']):
1771 if len(line) > 35:
1772 line = line[:32] + '...'
1773 raise util.Abort(_('first line looks like a '
1774 'mail header: ') + line)
1775 if diffre.match(line):
1776 if tmpfp:
1777 for chunk in util.filechunkiter(f):
1778 tmpfp.write(chunk)
1779 break
1770 message = msg['Subject']
1771 if message:
1772 message = message.replace('\n\t', ' ')
1773 ui.debug('Subject: %s\n' % message)
1774 user = msg['From']
1775 if user:
1776 ui.debug('From: %s\n' % user)
1777 diffs_seen = 0
1778 ok_types = ('text/plain', 'text/x-patch')
1779 for part in msg.walk():
1780 content_type = part.get_content_type()
1781 ui.debug('Content-Type: %s\n' % content_type)
1782 if content_type not in ok_types:
1783 continue
1784 payload = part.get_payload(decode=True)
1785 m = diffre.search(payload)
1786 if m:
1787 ui.debug(_('found patch at byte %d\n') % m.start(0))
1788 diffs_seen += 1
1789 hgpatch = False
1790 fp = cStringIO.StringIO()
1791 if message:
1792 fp.write(message)
1793 fp.write('\n')
1794 for line in payload[:m.start(0)].splitlines():
1795 if line.startswith('# HG changeset patch'):
1796 ui.debug(_('patch generated by hg export\n'))
1797 hgpatch = True
1798 # drop earlier commit message content
1799 fp.seek(0)
1800 fp.truncate()
1780 1801 elif hgpatch:
1781 # parse values when importing the result of an hg export
1782 if line.startswith("# User "):
1802 if line.startswith('# User '):
1783 1803 user = line[7:]
1784 ui.debug(_('User: %s\n') % user)
1804 ui.debug('From: %s\n' % user)
1785 1805 elif line.startswith("# Date "):
1786 1806 date = line[7:]
1787 elif not line.startswith("# ") and line:
1788 message.append(line)
1789 hgpatch = False
1790 elif line == '# HG changeset patch':
1791 hgpatch = True
1792 message = [] # We may have collected garbage
1793 elif message or line:
1794 message.append(line)
1807 if not line.startswith('# '):
1808 fp.write(line)
1809 fp.write('\n')
1810 message = fp.getvalue()
1811 if tmpfp:
1812 tmpfp.write(payload)
1813 if not payload.endswith('\n'):
1814 tmpfp.write('\n')
1815 elif not diffs_seen and message and content_type == 'text/plain':
1816 message += '\n' + payload
1795 1817
1796 1818 if opts['message']:
1797 1819 # pickup the cmdline msg
1798 1820 message = opts['message']
1799 1821 elif message:
1800 1822 # pickup the patch msg
1801 message = '\n'.join(message).rstrip()
1823 message = message.strip()
1802 1824 else:
1803 1825 # launch the editor
1804 1826 message = None
1805 1827 ui.debug(_('message:\n%s\n') % message)
1806 1828
1807 if tmpfp: tmpfp.close()
1808 files = util.patch(strip, pf, ui)
1809
1829 tmpfp.close()
1830 if not diffs_seen:
1831 raise util.Abort(_('no diffs found'))
1832
1833 files = util.patch(strip, tmpname, ui)
1810 1834 if len(files) > 0:
1811 1835 addremove_lock(ui, repo, files, {})
1812 1836 repo.commit(files, message, user, date)
1813 1837 finally:
1814 if tmpname: os.unlink(tmpname)
1838 os.unlink(tmpname)
1815 1839
1816 1840 def incoming(ui, repo, source="default", **opts):
1817 1841 """show new changesets found in source
1818 1842
1819 1843 Show new changesets found in the specified path/URL or the default
1820 1844 pull location. These are the changesets that would be pulled if a pull
1821 1845 was requested.
1822 1846
1823 1847 For remote repository, using --bundle avoids downloading the changesets
1824 1848 twice if the incoming is followed by a pull.
1825 1849
1826 1850 See pull for valid source format details.
1827 1851 """
1828 1852 source = ui.expandpath(source)
1829 1853 if opts['ssh']:
1830 1854 ui.setconfig("ui", "ssh", opts['ssh'])
1831 1855 if opts['remotecmd']:
1832 1856 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1833 1857
1834 1858 other = hg.repository(ui, source)
1835 1859 incoming = repo.findincoming(other, force=opts["force"])
1836 1860 if not incoming:
1837 1861 ui.status(_("no changes found\n"))
1838 1862 return
1839 1863
1840 1864 cleanup = None
1841 1865 try:
1842 1866 fname = opts["bundle"]
1843 1867 if fname or not other.local():
1844 1868 # create a bundle (uncompressed if other repo is not local)
1845 1869 cg = other.changegroup(incoming, "incoming")
1846 1870 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1847 1871 # keep written bundle?
1848 1872 if opts["bundle"]:
1849 1873 cleanup = None
1850 1874 if not other.local():
1851 1875 # use the created uncompressed bundlerepo
1852 1876 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1853 1877
1854 o = other.changelog.nodesbetween(incoming)[0]
1878 revs = None
1879 if opts['rev']:
1880 revs = [other.lookup(rev) for rev in opts['rev']]
1881 o = other.changelog.nodesbetween(incoming, revs)[0]
1855 1882 if opts['newest_first']:
1856 1883 o.reverse()
1857 1884 displayer = show_changeset(ui, other, opts)
1858 1885 for n in o:
1859 1886 parents = [p for p in other.changelog.parents(n) if p != nullid]
1860 1887 if opts['no_merges'] and len(parents) == 2:
1861 1888 continue
1862 1889 displayer.show(changenode=n)
1863 1890 if opts['patch']:
1864 1891 prev = (parents and parents[0]) or nullid
1865 1892 dodiff(ui, ui, other, prev, n)
1866 1893 ui.write("\n")
1867 1894 finally:
1868 1895 if hasattr(other, 'close'):
1869 1896 other.close()
1870 1897 if cleanup:
1871 1898 os.unlink(cleanup)
1872 1899
1873 1900 def init(ui, dest="."):
1874 1901 """create a new repository in the given directory
1875 1902
1876 1903 Initialize a new repository in the given directory. If the given
1877 1904 directory does not exist, it is created.
1878 1905
1879 1906 If no directory is given, the current directory is used.
1880 1907 """
1881 1908 if not os.path.exists(dest):
1882 1909 os.mkdir(dest)
1883 1910 hg.repository(ui, dest, create=1)
1884 1911
1885 1912 def locate(ui, repo, *pats, **opts):
1886 1913 """locate files matching specific patterns
1887 1914
1888 1915 Print all files under Mercurial control whose names match the
1889 1916 given patterns.
1890 1917
1891 1918 This command searches the current directory and its
1892 1919 subdirectories. To search an entire repository, move to the root
1893 1920 of the repository.
1894 1921
1895 1922 If no patterns are given to match, this command prints all file
1896 1923 names.
1897 1924
1898 1925 If you want to feed the output of this command into the "xargs"
1899 1926 command, use the "-0" option to both this command and "xargs".
1900 1927 This will avoid the problem of "xargs" treating single filenames
1901 1928 that contain white space as multiple filenames.
1902 1929 """
1903 1930 end = opts['print0'] and '\0' or '\n'
1904 1931 rev = opts['rev']
1905 1932 if rev:
1906 1933 node = repo.lookup(rev)
1907 1934 else:
1908 1935 node = None
1909 1936
1910 1937 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1911 1938 head='(?:.*/|)'):
1912 1939 if not node and repo.dirstate.state(abs) == '?':
1913 1940 continue
1914 1941 if opts['fullpath']:
1915 1942 ui.write(os.path.join(repo.root, abs), end)
1916 1943 else:
1917 1944 ui.write(((pats and rel) or abs), end)
1918 1945
1919 1946 def log(ui, repo, *pats, **opts):
1920 1947 """show revision history of entire repository or files
1921 1948
1922 1949 Print the revision history of the specified files or the entire project.
1923 1950
1924 1951 By default this command outputs: changeset id and hash, tags,
1925 1952 non-trivial parents, user, date and time, and a summary for each
1926 1953 commit. When the -v/--verbose switch is used, the list of changed
1927 1954 files and full commit message is shown.
1928 1955 """
1929 1956 class dui(object):
1930 1957 # Implement and delegate some ui protocol. Save hunks of
1931 1958 # output for later display in the desired order.
1932 1959 def __init__(self, ui):
1933 1960 self.ui = ui
1934 1961 self.hunk = {}
1935 1962 self.header = {}
1936 1963 def bump(self, rev):
1937 1964 self.rev = rev
1938 1965 self.hunk[rev] = []
1939 1966 self.header[rev] = []
1940 1967 def note(self, *args):
1941 1968 if self.verbose:
1942 1969 self.write(*args)
1943 1970 def status(self, *args):
1944 1971 if not self.quiet:
1945 1972 self.write(*args)
1946 1973 def write(self, *args):
1947 1974 self.hunk[self.rev].append(args)
1948 1975 def write_header(self, *args):
1949 1976 self.header[self.rev].append(args)
1950 1977 def debug(self, *args):
1951 1978 if self.debugflag:
1952 1979 self.write(*args)
1953 1980 def __getattr__(self, key):
1954 1981 return getattr(self.ui, key)
1955 1982
1956 1983 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1957 1984
1958 1985 if opts['limit']:
1959 1986 try:
1960 1987 limit = int(opts['limit'])
1961 1988 except ValueError:
1962 1989 raise util.Abort(_('limit must be a positive integer'))
1963 1990 if limit <= 0: raise util.Abort(_('limit must be positive'))
1964 1991 else:
1965 1992 limit = sys.maxint
1966 1993 count = 0
1967 1994
1968 1995 displayer = show_changeset(ui, repo, opts)
1969 1996 for st, rev, fns in changeiter:
1970 1997 if st == 'window':
1971 1998 du = dui(ui)
1972 1999 displayer.ui = du
1973 2000 elif st == 'add':
1974 2001 du.bump(rev)
1975 2002 changenode = repo.changelog.node(rev)
1976 2003 parents = [p for p in repo.changelog.parents(changenode)
1977 2004 if p != nullid]
1978 2005 if opts['no_merges'] and len(parents) == 2:
1979 2006 continue
1980 2007 if opts['only_merges'] and len(parents) != 2:
1981 2008 continue
1982 2009
1983 2010 if opts['keyword']:
1984 2011 changes = getchange(rev)
1985 2012 miss = 0
1986 2013 for k in [kw.lower() for kw in opts['keyword']]:
1987 2014 if not (k in changes[1].lower() or
1988 2015 k in changes[4].lower() or
1989 2016 k in " ".join(changes[3][:20]).lower()):
1990 2017 miss = 1
1991 2018 break
1992 2019 if miss:
1993 2020 continue
1994 2021
1995 2022 br = None
1996 2023 if opts['branches']:
1997 2024 br = repo.branchlookup([repo.changelog.node(rev)])
1998 2025
1999 2026 displayer.show(rev, brinfo=br)
2000 2027 if opts['patch']:
2001 2028 prev = (parents and parents[0]) or nullid
2002 2029 dodiff(du, du, repo, prev, changenode, match=matchfn)
2003 2030 du.write("\n\n")
2004 2031 elif st == 'iter':
2005 2032 if count == limit: break
2006 2033 if du.header[rev]:
2007 2034 for args in du.header[rev]:
2008 2035 ui.write_header(*args)
2009 2036 if du.hunk[rev]:
2010 2037 count += 1
2011 2038 for args in du.hunk[rev]:
2012 2039 ui.write(*args)
2013 2040
2014 2041 def manifest(ui, repo, rev=None):
2015 2042 """output the latest or given revision of the project manifest
2016 2043
2017 2044 Print a list of version controlled files for the given revision.
2018 2045
2019 2046 The manifest is the list of files being version controlled. If no revision
2020 2047 is given then the tip is used.
2021 2048 """
2022 2049 if rev:
2023 2050 try:
2024 2051 # assume all revision numbers are for changesets
2025 2052 n = repo.lookup(rev)
2026 2053 change = repo.changelog.read(n)
2027 2054 n = change[0]
2028 2055 except hg.RepoError:
2029 2056 n = repo.manifest.lookup(rev)
2030 2057 else:
2031 2058 n = repo.manifest.tip()
2032 2059 m = repo.manifest.read(n)
2033 2060 mf = repo.manifest.readflags(n)
2034 2061 files = m.keys()
2035 2062 files.sort()
2036 2063
2037 2064 for f in files:
2038 2065 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
2039 2066
2040 2067 def merge(ui, repo, node=None, **opts):
2041 2068 """Merge working directory with another revision
2042 2069
2043 2070 Merge the contents of the current working directory and the
2044 2071 requested revision. Files that changed between either parent are
2045 2072 marked as changed for the next commit and a commit must be
2046 2073 performed before any further updates are allowed.
2047 2074 """
2048 2075 return doupdate(ui, repo, node=node, merge=True, **opts)
2049 2076
2050 2077 def outgoing(ui, repo, dest=None, **opts):
2051 2078 """show changesets not found in destination
2052 2079
2053 2080 Show changesets not found in the specified destination repository or
2054 2081 the default push location. These are the changesets that would be pushed
2055 2082 if a push was requested.
2056 2083
2057 2084 See pull for valid destination format details.
2058 2085 """
2059 2086 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2060 2087 if opts['ssh']:
2061 2088 ui.setconfig("ui", "ssh", opts['ssh'])
2062 2089 if opts['remotecmd']:
2063 2090 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2091 revs = None
2092 if opts['rev']:
2093 revs = [repo.lookup(rev) for rev in opts['rev']]
2064 2094
2065 2095 other = hg.repository(ui, dest)
2066 2096 o = repo.findoutgoing(other, force=opts['force'])
2067 2097 if not o:
2068 2098 ui.status(_("no changes found\n"))
2069 2099 return
2070 o = repo.changelog.nodesbetween(o)[0]
2100 o = repo.changelog.nodesbetween(o, revs)[0]
2071 2101 if opts['newest_first']:
2072 2102 o.reverse()
2073 2103 displayer = show_changeset(ui, repo, opts)
2074 2104 for n in o:
2075 2105 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2076 2106 if opts['no_merges'] and len(parents) == 2:
2077 2107 continue
2078 2108 displayer.show(changenode=n)
2079 2109 if opts['patch']:
2080 2110 prev = (parents and parents[0]) or nullid
2081 2111 dodiff(ui, ui, repo, prev, n)
2082 2112 ui.write("\n")
2083 2113
2084 2114 def parents(ui, repo, rev=None, branches=None, **opts):
2085 2115 """show the parents of the working dir or revision
2086 2116
2087 2117 Print the working directory's parent revisions.
2088 2118 """
2089 2119 if rev:
2090 2120 p = repo.changelog.parents(repo.lookup(rev))
2091 2121 else:
2092 2122 p = repo.dirstate.parents()
2093 2123
2094 2124 br = None
2095 2125 if branches is not None:
2096 2126 br = repo.branchlookup(p)
2097 2127 displayer = show_changeset(ui, repo, opts)
2098 2128 for n in p:
2099 2129 if n != nullid:
2100 2130 displayer.show(changenode=n, brinfo=br)
2101 2131
2102 2132 def paths(ui, repo, search=None):
2103 2133 """show definition of symbolic path names
2104 2134
2105 2135 Show definition of symbolic path name NAME. If no name is given, show
2106 2136 definition of available names.
2107 2137
2108 2138 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2109 2139 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2110 2140 """
2111 2141 if search:
2112 2142 for name, path in ui.configitems("paths"):
2113 2143 if name == search:
2114 2144 ui.write("%s\n" % path)
2115 2145 return
2116 2146 ui.warn(_("not found!\n"))
2117 2147 return 1
2118 2148 else:
2119 2149 for name, path in ui.configitems("paths"):
2120 2150 ui.write("%s = %s\n" % (name, path))
2121 2151
2122 2152 def postincoming(ui, repo, modheads, optupdate):
2123 2153 if modheads == 0:
2124 2154 return
2125 2155 if optupdate:
2126 2156 if modheads == 1:
2127 2157 return doupdate(ui, repo)
2128 2158 else:
2129 2159 ui.status(_("not updating, since new heads added\n"))
2130 2160 if modheads > 1:
2131 2161 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2132 2162 else:
2133 2163 ui.status(_("(run 'hg update' to get a working copy)\n"))
2134 2164
2135 2165 def pull(ui, repo, source="default", **opts):
2136 2166 """pull changes from the specified source
2137 2167
2138 2168 Pull changes from a remote repository to a local one.
2139 2169
2140 2170 This finds all changes from the repository at the specified path
2141 2171 or URL and adds them to the local repository. By default, this
2142 2172 does not update the copy of the project in the working directory.
2143 2173
2144 2174 Valid URLs are of the form:
2145 2175
2146 2176 local/filesystem/path
2147 2177 http://[user@]host[:port][/path]
2148 2178 https://[user@]host[:port][/path]
2149 2179 ssh://[user@]host[:port][/path]
2150 2180
2151 2181 Some notes about using SSH with Mercurial:
2152 2182 - SSH requires an accessible shell account on the destination machine
2153 2183 and a copy of hg in the remote path or specified with as remotecmd.
2154 2184 - /path is relative to the remote user's home directory by default.
2155 2185 Use two slashes at the start of a path to specify an absolute path.
2156 2186 - Mercurial doesn't use its own compression via SSH; the right thing
2157 2187 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2158 2188 Host *.mylocalnetwork.example.com
2159 2189 Compression off
2160 2190 Host *
2161 2191 Compression on
2162 2192 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2163 2193 with the --ssh command line option.
2164 2194 """
2165 2195 source = ui.expandpath(source)
2166 2196
2167 2197 if opts['ssh']:
2168 2198 ui.setconfig("ui", "ssh", opts['ssh'])
2169 2199 if opts['remotecmd']:
2170 2200 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2171 2201
2172 2202 other = hg.repository(ui, source)
2173 2203 ui.status(_('pulling from %s\n') % (source))
2174 2204 revs = None
2175 2205 if opts['rev'] and not other.local():
2176 2206 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2177 2207 elif opts['rev']:
2178 2208 revs = [other.lookup(rev) for rev in opts['rev']]
2179 2209 modheads = repo.pull(other, heads=revs, force=opts['force'])
2180 2210 return postincoming(ui, repo, modheads, opts['update'])
2181 2211
2182 2212 def push(ui, repo, dest=None, **opts):
2183 2213 """push changes to the specified destination
2184 2214
2185 2215 Push changes from the local repository to the given destination.
2186 2216
2187 2217 This is the symmetrical operation for pull. It helps to move
2188 2218 changes from the current repository to a different one. If the
2189 2219 destination is local this is identical to a pull in that directory
2190 2220 from the current one.
2191 2221
2192 2222 By default, push will refuse to run if it detects the result would
2193 2223 increase the number of remote heads. This generally indicates the
2194 2224 the client has forgotten to sync and merge before pushing.
2195 2225
2196 2226 Valid URLs are of the form:
2197 2227
2198 2228 local/filesystem/path
2199 2229 ssh://[user@]host[:port][/path]
2200 2230
2201 2231 Look at the help text for the pull command for important details
2202 2232 about ssh:// URLs.
2203 2233 """
2204 2234 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2205 2235
2206 2236 if opts['ssh']:
2207 2237 ui.setconfig("ui", "ssh", opts['ssh'])
2208 2238 if opts['remotecmd']:
2209 2239 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2210 2240
2211 2241 other = hg.repository(ui, dest)
2212 2242 ui.status('pushing to %s\n' % (dest))
2213 2243 revs = None
2214 2244 if opts['rev']:
2215 2245 revs = [repo.lookup(rev) for rev in opts['rev']]
2216 2246 r = repo.push(other, opts['force'], revs=revs)
2217 2247 return r == 0
2218 2248
2219 2249 def rawcommit(ui, repo, *flist, **rc):
2220 2250 """raw commit interface (DEPRECATED)
2221 2251
2222 2252 (DEPRECATED)
2223 2253 Lowlevel commit, for use in helper scripts.
2224 2254
2225 2255 This command is not intended to be used by normal users, as it is
2226 2256 primarily useful for importing from other SCMs.
2227 2257
2228 2258 This command is now deprecated and will be removed in a future
2229 2259 release, please use debugsetparents and commit instead.
2230 2260 """
2231 2261
2232 2262 ui.warn(_("(the rawcommit command is deprecated)\n"))
2233 2263
2234 2264 message = rc['message']
2235 2265 if not message and rc['logfile']:
2236 2266 try:
2237 2267 message = open(rc['logfile']).read()
2238 2268 except IOError:
2239 2269 pass
2240 2270 if not message and not rc['logfile']:
2241 2271 raise util.Abort(_("missing commit message"))
2242 2272
2243 2273 files = relpath(repo, list(flist))
2244 2274 if rc['files']:
2245 2275 files += open(rc['files']).read().splitlines()
2246 2276
2247 2277 rc['parent'] = map(repo.lookup, rc['parent'])
2248 2278
2249 2279 try:
2250 2280 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2251 2281 except ValueError, inst:
2252 2282 raise util.Abort(str(inst))
2253 2283
2254 2284 def recover(ui, repo):
2255 2285 """roll back an interrupted transaction
2256 2286
2257 2287 Recover from an interrupted commit or pull.
2258 2288
2259 2289 This command tries to fix the repository status after an interrupted
2260 2290 operation. It should only be necessary when Mercurial suggests it.
2261 2291 """
2262 2292 if repo.recover():
2263 2293 return repo.verify()
2264 2294 return 1
2265 2295
2266 2296 def remove(ui, repo, *pats, **opts):
2267 2297 """remove the specified files on the next commit
2268 2298
2269 2299 Schedule the indicated files for removal from the repository.
2270 2300
2271 2301 This command schedules the files to be removed at the next commit.
2272 2302 This only removes files from the current branch, not from the
2273 2303 entire project history. If the files still exist in the working
2274 2304 directory, they will be deleted from it. If invoked with --after,
2275 2305 files that have been manually deleted are marked as removed.
2276 2306
2277 2307 Modified files and added files are not removed by default. To
2278 2308 remove them, use the -f/--force option.
2279 2309 """
2280 2310 names = []
2281 2311 if not opts['after'] and not pats:
2282 2312 raise util.Abort(_('no files specified'))
2283 2313 files, matchfn, anypats = matchpats(repo, pats, opts)
2284 2314 exact = dict.fromkeys(files)
2285 2315 mardu = map(dict.fromkeys, repo.changes(files=files, match=matchfn))
2286 2316 modified, added, removed, deleted, unknown = mardu
2287 2317 remove, forget = [], []
2288 2318 for src, abs, rel, exact in walk(repo, pats, opts):
2289 2319 reason = None
2290 2320 if abs not in deleted and opts['after']:
2291 2321 reason = _('is still present')
2292 2322 elif abs in modified and not opts['force']:
2293 2323 reason = _('is modified (use -f to force removal)')
2294 2324 elif abs in added:
2295 2325 if opts['force']:
2296 2326 forget.append(abs)
2297 2327 continue
2298 2328 reason = _('has been marked for add (use -f to force removal)')
2299 2329 elif abs in unknown:
2300 2330 reason = _('is not managed')
2301 2331 elif abs in removed:
2302 2332 continue
2303 2333 if reason:
2304 2334 if exact:
2305 2335 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2306 2336 else:
2307 2337 if ui.verbose or not exact:
2308 2338 ui.status(_('removing %s\n') % rel)
2309 2339 remove.append(abs)
2310 2340 repo.forget(forget)
2311 2341 repo.remove(remove, unlink=not opts['after'])
2312 2342
2313 2343 def rename(ui, repo, *pats, **opts):
2314 2344 """rename files; equivalent of copy + remove
2315 2345
2316 2346 Mark dest as copies of sources; mark sources for deletion. If
2317 2347 dest is a directory, copies are put in that directory. If dest is
2318 2348 a file, there can only be one source.
2319 2349
2320 2350 By default, this command copies the contents of files as they
2321 2351 stand in the working directory. If invoked with --after, the
2322 2352 operation is recorded, but no copying is performed.
2323 2353
2324 2354 This command takes effect in the next commit.
2325 2355
2326 2356 NOTE: This command should be treated as experimental. While it
2327 2357 should properly record rename files, this information is not yet
2328 2358 fully used by merge, nor fully reported by log.
2329 2359 """
2330 2360 wlock = repo.wlock(0)
2331 2361 errs, copied = docopy(ui, repo, pats, opts, wlock)
2332 2362 names = []
2333 2363 for abs, rel, exact in copied:
2334 2364 if ui.verbose or not exact:
2335 2365 ui.status(_('removing %s\n') % rel)
2336 2366 names.append(abs)
2337 2367 if not opts.get('dry_run'):
2338 2368 repo.remove(names, True, wlock)
2339 2369 return errs
2340 2370
2341 2371 def revert(ui, repo, *pats, **opts):
2342 2372 """revert files or dirs to their states as of some revision
2343 2373
2344 2374 With no revision specified, revert the named files or directories
2345 2375 to the contents they had in the parent of the working directory.
2346 2376 This restores the contents of the affected files to an unmodified
2347 2377 state. If the working directory has two parents, you must
2348 2378 explicitly specify the revision to revert to.
2349 2379
2350 2380 Modified files are saved with a .orig suffix before reverting.
2351 2381 To disable these backups, use --no-backup.
2352 2382
2353 2383 Using the -r option, revert the given files or directories to
2354 2384 their contents as of a specific revision. This can be helpful to"roll
2355 2385 back" some or all of a change that should not have been committed.
2356 2386
2357 2387 Revert modifies the working directory. It does not commit any
2358 2388 changes, or change the parent of the working directory. If you
2359 2389 revert to a revision other than the parent of the working
2360 2390 directory, the reverted files will thus appear modified
2361 2391 afterwards.
2362 2392
2363 2393 If a file has been deleted, it is recreated. If the executable
2364 2394 mode of a file was changed, it is reset.
2365 2395
2366 2396 If names are given, all files matching the names are reverted.
2367 2397
2368 2398 If no arguments are given, all files in the repository are reverted.
2369 2399 """
2370 2400 parent, p2 = repo.dirstate.parents()
2371 2401 if opts['rev']:
2372 2402 node = repo.lookup(opts['rev'])
2373 2403 elif p2 != nullid:
2374 2404 raise util.Abort(_('working dir has two parents; '
2375 2405 'you must specify the revision to revert to'))
2376 2406 else:
2377 2407 node = parent
2378 2408 mf = repo.manifest.read(repo.changelog.read(node)[0])
2379 2409 if node == parent:
2380 2410 pmf = mf
2381 2411 else:
2382 2412 pmf = None
2383 2413
2384 2414 wlock = repo.wlock()
2385 2415
2386 2416 # need all matching names in dirstate and manifest of target rev,
2387 2417 # so have to walk both. do not print errors if files exist in one
2388 2418 # but not other.
2389 2419
2390 2420 names = {}
2391 2421 target_only = {}
2392 2422
2393 2423 # walk dirstate.
2394 2424
2395 2425 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2396 2426 names[abs] = (rel, exact)
2397 2427 if src == 'b':
2398 2428 target_only[abs] = True
2399 2429
2400 2430 # walk target manifest.
2401 2431
2402 2432 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2403 2433 badmatch=names.has_key):
2404 2434 if abs in names: continue
2405 2435 names[abs] = (rel, exact)
2406 2436 target_only[abs] = True
2407 2437
2408 2438 changes = repo.changes(match=names.has_key, wlock=wlock)
2409 2439 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2410 2440
2411 2441 revert = ([], _('reverting %s\n'))
2412 2442 add = ([], _('adding %s\n'))
2413 2443 remove = ([], _('removing %s\n'))
2414 2444 forget = ([], _('forgetting %s\n'))
2415 2445 undelete = ([], _('undeleting %s\n'))
2416 2446 update = {}
2417 2447
2418 2448 disptable = (
2419 2449 # dispatch table:
2420 2450 # file state
2421 2451 # action if in target manifest
2422 2452 # action if not in target manifest
2423 2453 # make backup if in target manifest
2424 2454 # make backup if not in target manifest
2425 2455 (modified, revert, remove, True, True),
2426 2456 (added, revert, forget, True, False),
2427 2457 (removed, undelete, None, False, False),
2428 2458 (deleted, revert, remove, False, False),
2429 2459 (unknown, add, None, True, False),
2430 2460 (target_only, add, None, False, False),
2431 2461 )
2432 2462
2433 2463 entries = names.items()
2434 2464 entries.sort()
2435 2465
2436 2466 for abs, (rel, exact) in entries:
2437 2467 mfentry = mf.get(abs)
2438 2468 def handle(xlist, dobackup):
2439 2469 xlist[0].append(abs)
2440 2470 update[abs] = 1
2441 2471 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2442 2472 bakname = "%s.orig" % rel
2443 2473 ui.note(_('saving current version of %s as %s\n') %
2444 2474 (rel, bakname))
2445 2475 if not opts.get('dry_run'):
2446 2476 shutil.copyfile(rel, bakname)
2447 2477 shutil.copymode(rel, bakname)
2448 2478 if ui.verbose or not exact:
2449 2479 ui.status(xlist[1] % rel)
2450 2480 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2451 2481 if abs not in table: continue
2452 2482 # file has changed in dirstate
2453 2483 if mfentry:
2454 2484 handle(hitlist, backuphit)
2455 2485 elif misslist is not None:
2456 2486 handle(misslist, backupmiss)
2457 2487 else:
2458 2488 if exact: ui.warn(_('file not managed: %s\n' % rel))
2459 2489 break
2460 2490 else:
2461 2491 # file has not changed in dirstate
2462 2492 if node == parent:
2463 2493 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2464 2494 continue
2465 2495 if pmf is None:
2466 2496 # only need parent manifest in this unlikely case,
2467 2497 # so do not read by default
2468 2498 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2469 2499 if abs in pmf:
2470 2500 if mfentry:
2471 2501 # if version of file is same in parent and target
2472 2502 # manifests, do nothing
2473 2503 if pmf[abs] != mfentry:
2474 2504 handle(revert, False)
2475 2505 else:
2476 2506 handle(remove, False)
2477 2507
2478 2508 if not opts.get('dry_run'):
2479 2509 repo.dirstate.forget(forget[0])
2480 2510 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2481 2511 show_stats=False)
2482 2512 repo.dirstate.update(add[0], 'a')
2483 2513 repo.dirstate.update(undelete[0], 'n')
2484 2514 repo.dirstate.update(remove[0], 'r')
2485 2515 return r
2486 2516
2487 2517 def rollback(ui, repo):
2488 2518 """roll back the last transaction in this repository
2489 2519
2490 2520 Roll back the last transaction in this repository, restoring the
2491 2521 project to its state prior to the transaction.
2492 2522
2493 2523 Transactions are used to encapsulate the effects of all commands
2494 2524 that create new changesets or propagate existing changesets into a
2495 2525 repository. For example, the following commands are transactional,
2496 2526 and their effects can be rolled back:
2497 2527
2498 2528 commit
2499 2529 import
2500 2530 pull
2501 2531 push (with this repository as destination)
2502 2532 unbundle
2503 2533
2504 2534 This command should be used with care. There is only one level of
2505 2535 rollback, and there is no way to undo a rollback.
2506 2536
2507 2537 This command is not intended for use on public repositories. Once
2508 2538 changes are visible for pull by other users, rolling a transaction
2509 2539 back locally is ineffective (someone else may already have pulled
2510 2540 the changes). Furthermore, a race is possible with readers of the
2511 2541 repository; for example an in-progress pull from the repository
2512 2542 may fail if a rollback is performed.
2513 2543 """
2514 2544 repo.rollback()
2515 2545
2516 2546 def root(ui, repo):
2517 2547 """print the root (top) of the current working dir
2518 2548
2519 2549 Print the root directory of the current repository.
2520 2550 """
2521 2551 ui.write(repo.root + "\n")
2522 2552
2523 2553 def serve(ui, repo, **opts):
2524 2554 """export the repository via HTTP
2525 2555
2526 2556 Start a local HTTP repository browser and pull server.
2527 2557
2528 2558 By default, the server logs accesses to stdout and errors to
2529 2559 stderr. Use the "-A" and "-E" options to log to files.
2530 2560 """
2531 2561
2532 2562 if opts["stdio"]:
2533 2563 if repo is None:
2534 2564 raise hg.RepoError(_('no repo found'))
2535 2565 s = sshserver.sshserver(ui, repo)
2536 2566 s.serve_forever()
2537 2567
2538 2568 optlist = ("name templates style address port ipv6"
2539 2569 " accesslog errorlog webdir_conf")
2540 2570 for o in optlist.split():
2541 2571 if opts[o]:
2542 2572 ui.setconfig("web", o, opts[o])
2543 2573
2544 2574 if repo is None and not ui.config("web", "webdir_conf"):
2545 2575 raise hg.RepoError(_('no repo found'))
2546 2576
2547 2577 if opts['daemon'] and not opts['daemon_pipefds']:
2548 2578 rfd, wfd = os.pipe()
2549 2579 args = sys.argv[:]
2550 2580 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2551 2581 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2552 2582 args[0], args)
2553 2583 os.close(wfd)
2554 2584 os.read(rfd, 1)
2555 2585 os._exit(0)
2556 2586
2557 2587 try:
2558 2588 httpd = hgweb.server.create_server(ui, repo)
2559 2589 except socket.error, inst:
2560 2590 raise util.Abort(_('cannot start server: ') + inst.args[1])
2561 2591
2562 2592 if ui.verbose:
2563 2593 addr, port = httpd.socket.getsockname()
2564 2594 if addr == '0.0.0.0':
2565 2595 addr = socket.gethostname()
2566 2596 else:
2567 2597 try:
2568 2598 addr = socket.gethostbyaddr(addr)[0]
2569 2599 except socket.error:
2570 2600 pass
2571 2601 if port != 80:
2572 2602 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2573 2603 else:
2574 2604 ui.status(_('listening at http://%s/\n') % addr)
2575 2605
2576 2606 if opts['pid_file']:
2577 2607 fp = open(opts['pid_file'], 'w')
2578 2608 fp.write(str(os.getpid()))
2579 2609 fp.close()
2580 2610
2581 2611 if opts['daemon_pipefds']:
2582 2612 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2583 2613 os.close(rfd)
2584 2614 os.write(wfd, 'y')
2585 2615 os.close(wfd)
2586 2616 sys.stdout.flush()
2587 2617 sys.stderr.flush()
2588 2618 fd = os.open(util.nulldev, os.O_RDWR)
2589 2619 if fd != 0: os.dup2(fd, 0)
2590 2620 if fd != 1: os.dup2(fd, 1)
2591 2621 if fd != 2: os.dup2(fd, 2)
2592 2622 if fd not in (0, 1, 2): os.close(fd)
2593 2623
2594 2624 httpd.serve_forever()
2595 2625
2596 2626 def status(ui, repo, *pats, **opts):
2597 2627 """show changed files in the working directory
2598 2628
2599 2629 Show changed files in the repository. If names are
2600 2630 given, only files that match are shown.
2601 2631
2602 2632 The codes used to show the status of files are:
2603 2633 M = modified
2604 2634 A = added
2605 2635 R = removed
2606 2636 ! = deleted, but still tracked
2607 2637 ? = not tracked
2608 2638 I = ignored (not shown by default)
2609 2639 """
2610 2640
2611 2641 show_ignored = opts['ignored'] and True or False
2612 2642 files, matchfn, anypats = matchpats(repo, pats, opts)
2613 2643 cwd = (pats and repo.getcwd()) or ''
2614 2644 modified, added, removed, deleted, unknown, ignored = [
2615 2645 [util.pathto(cwd, x) for x in n]
2616 2646 for n in repo.changes(files=files, match=matchfn,
2617 2647 show_ignored=show_ignored)]
2618 2648
2619 2649 changetypes = [('modified', 'M', modified),
2620 2650 ('added', 'A', added),
2621 2651 ('removed', 'R', removed),
2622 2652 ('deleted', '!', deleted),
2623 2653 ('unknown', '?', unknown),
2624 2654 ('ignored', 'I', ignored)]
2625 2655
2626 2656 end = opts['print0'] and '\0' or '\n'
2627 2657
2628 2658 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2629 2659 or changetypes):
2630 2660 if opts['no_status']:
2631 2661 format = "%%s%s" % end
2632 2662 else:
2633 2663 format = "%s %%s%s" % (char, end)
2634 2664
2635 2665 for f in changes:
2636 2666 ui.write(format % f)
2637 2667
2638 2668 def tag(ui, repo, name, rev_=None, **opts):
2639 2669 """add a tag for the current tip or a given revision
2640 2670
2641 2671 Name a particular revision using <name>.
2642 2672
2643 2673 Tags are used to name particular revisions of the repository and are
2644 2674 very useful to compare different revision, to go back to significant
2645 2675 earlier versions or to mark branch points as releases, etc.
2646 2676
2647 2677 If no revision is given, the tip is used.
2648 2678
2649 2679 To facilitate version control, distribution, and merging of tags,
2650 2680 they are stored as a file named ".hgtags" which is managed
2651 2681 similarly to other project files and can be hand-edited if
2652 2682 necessary. The file '.hg/localtags' is used for local tags (not
2653 2683 shared among repositories).
2654 2684 """
2655 2685 if name == "tip":
2656 2686 raise util.Abort(_("the name 'tip' is reserved"))
2657 2687 if rev_ is not None:
2658 2688 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2659 2689 "please use 'hg tag [-r REV] NAME' instead\n"))
2660 2690 if opts['rev']:
2661 2691 raise util.Abort(_("use only one form to specify the revision"))
2662 2692 if opts['rev']:
2663 2693 rev_ = opts['rev']
2664 2694 if rev_:
2665 2695 r = hex(repo.lookup(rev_))
2666 2696 else:
2667 2697 r = hex(repo.changelog.tip())
2668 2698
2669 2699 disallowed = (revrangesep, '\r', '\n')
2670 2700 for c in disallowed:
2671 2701 if name.find(c) >= 0:
2672 2702 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2673 2703
2674 2704 repo.hook('pretag', throw=True, node=r, tag=name,
2675 2705 local=int(not not opts['local']))
2676 2706
2677 2707 if opts['local']:
2678 2708 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2679 2709 repo.hook('tag', node=r, tag=name, local=1)
2680 2710 return
2681 2711
2682 2712 for x in repo.changes():
2683 2713 if ".hgtags" in x:
2684 2714 raise util.Abort(_("working copy of .hgtags is changed "
2685 2715 "(please commit .hgtags manually)"))
2686 2716
2687 2717 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2688 2718 if repo.dirstate.state(".hgtags") == '?':
2689 2719 repo.add([".hgtags"])
2690 2720
2691 2721 message = (opts['message'] or
2692 2722 _("Added tag %s for changeset %s") % (name, r))
2693 2723 try:
2694 2724 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2695 2725 repo.hook('tag', node=r, tag=name, local=0)
2696 2726 except ValueError, inst:
2697 2727 raise util.Abort(str(inst))
2698 2728
2699 2729 def tags(ui, repo):
2700 2730 """list repository tags
2701 2731
2702 2732 List the repository tags.
2703 2733
2704 2734 This lists both regular and local tags.
2705 2735 """
2706 2736
2707 2737 l = repo.tagslist()
2708 2738 l.reverse()
2709 2739 for t, n in l:
2710 2740 try:
2711 2741 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2712 2742 except KeyError:
2713 2743 r = " ?:?"
2714 2744 if ui.quiet:
2715 2745 ui.write("%s\n" % t)
2716 2746 else:
2717 2747 ui.write("%-30s %s\n" % (t, r))
2718 2748
2719 2749 def tip(ui, repo, **opts):
2720 2750 """show the tip revision
2721 2751
2722 2752 Show the tip revision.
2723 2753 """
2724 2754 n = repo.changelog.tip()
2725 2755 br = None
2726 2756 if opts['branches']:
2727 2757 br = repo.branchlookup([n])
2728 2758 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2729 2759 if opts['patch']:
2730 2760 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2731 2761
2732 2762 def unbundle(ui, repo, fname, **opts):
2733 2763 """apply a changegroup file
2734 2764
2735 2765 Apply a compressed changegroup file generated by the bundle
2736 2766 command.
2737 2767 """
2738 2768 f = urllib.urlopen(fname)
2739 2769
2740 2770 header = f.read(6)
2741 2771 if not header.startswith("HG"):
2742 2772 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2743 2773 elif not header.startswith("HG10"):
2744 2774 raise util.Abort(_("%s: unknown bundle version") % fname)
2745 2775 elif header == "HG10BZ":
2746 2776 def generator(f):
2747 2777 zd = bz2.BZ2Decompressor()
2748 2778 zd.decompress("BZ")
2749 2779 for chunk in f:
2750 2780 yield zd.decompress(chunk)
2751 2781 elif header == "HG10UN":
2752 2782 def generator(f):
2753 2783 for chunk in f:
2754 2784 yield chunk
2755 2785 else:
2756 2786 raise util.Abort(_("%s: unknown bundle compression type")
2757 2787 % fname)
2758 2788 gen = generator(util.filechunkiter(f, 4096))
2759 2789 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2760 2790 return postincoming(ui, repo, modheads, opts['update'])
2761 2791
2762 2792 def undo(ui, repo):
2763 2793 """undo the last commit or pull (DEPRECATED)
2764 2794
2765 2795 (DEPRECATED)
2766 2796 This command is now deprecated and will be removed in a future
2767 2797 release. Please use the rollback command instead. For usage
2768 2798 instructions, see the rollback command.
2769 2799 """
2770 2800 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2771 2801 repo.rollback()
2772 2802
2773 2803 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2774 2804 branch=None, **opts):
2775 2805 """update or merge working directory
2776 2806
2777 2807 Update the working directory to the specified revision.
2778 2808
2779 2809 If there are no outstanding changes in the working directory and
2780 2810 there is a linear relationship between the current version and the
2781 2811 requested version, the result is the requested version.
2782 2812
2783 2813 To merge the working directory with another revision, use the
2784 2814 merge command.
2785 2815
2786 2816 By default, update will refuse to run if doing so would require
2787 2817 merging or discarding local changes.
2788 2818 """
2789 2819 if merge:
2790 2820 ui.warn(_('(the -m/--merge option is deprecated; '
2791 2821 'use the merge command instead)\n'))
2792 2822 return doupdate(ui, repo, node, merge, clean, force, branch, **opts)
2793 2823
2794 2824 def doupdate(ui, repo, node=None, merge=False, clean=False, force=None,
2795 2825 branch=None, **opts):
2796 2826 if branch:
2797 2827 br = repo.branchlookup(branch=branch)
2798 2828 found = []
2799 2829 for x in br:
2800 2830 if branch in br[x]:
2801 2831 found.append(x)
2802 2832 if len(found) > 1:
2803 2833 ui.warn(_("Found multiple heads for %s\n") % branch)
2804 2834 for x in found:
2805 2835 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2806 2836 return 1
2807 2837 if len(found) == 1:
2808 2838 node = found[0]
2809 2839 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2810 2840 else:
2811 2841 ui.warn(_("branch %s not found\n") % (branch))
2812 2842 return 1
2813 2843 else:
2814 2844 node = node and repo.lookup(node) or repo.changelog.tip()
2815 2845 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2816 2846
2817 2847 def verify(ui, repo):
2818 2848 """verify the integrity of the repository
2819 2849
2820 2850 Verify the integrity of the current repository.
2821 2851
2822 2852 This will perform an extensive check of the repository's
2823 2853 integrity, validating the hashes and checksums of each entry in
2824 2854 the changelog, manifest, and tracked files, as well as the
2825 2855 integrity of their crosslinks and indices.
2826 2856 """
2827 2857 return repo.verify()
2828 2858
2829 2859 # Command options and aliases are listed here, alphabetically
2830 2860
2831 2861 table = {
2832 2862 "^add":
2833 2863 (add,
2834 2864 [('I', 'include', [], _('include names matching the given patterns')),
2835 2865 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2836 2866 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2837 2867 _('hg add [OPTION]... [FILE]...')),
2838 2868 "debugaddremove|addremove":
2839 2869 (addremove,
2840 2870 [('I', 'include', [], _('include names matching the given patterns')),
2841 2871 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2842 2872 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2843 2873 _('hg addremove [OPTION]... [FILE]...')),
2844 2874 "^annotate":
2845 2875 (annotate,
2846 2876 [('r', 'rev', '', _('annotate the specified revision')),
2847 2877 ('a', 'text', None, _('treat all files as text')),
2848 2878 ('u', 'user', None, _('list the author')),
2849 2879 ('d', 'date', None, _('list the date')),
2850 2880 ('n', 'number', None, _('list the revision number (default)')),
2851 2881 ('c', 'changeset', None, _('list the changeset')),
2852 2882 ('I', 'include', [], _('include names matching the given patterns')),
2853 2883 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2854 2884 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2855 2885 "archive":
2856 2886 (archive,
2857 2887 [('', 'no-decode', None, _('do not pass files through decoders')),
2858 2888 ('p', 'prefix', '', _('directory prefix for files in archive')),
2859 2889 ('r', 'rev', '', _('revision to distribute')),
2860 2890 ('t', 'type', '', _('type of distribution to create')),
2861 2891 ('I', 'include', [], _('include names matching the given patterns')),
2862 2892 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2863 2893 _('hg archive [OPTION]... DEST')),
2864 2894 "backout":
2865 2895 (backout,
2866 2896 [('', 'merge', None,
2867 2897 _('merge with old dirstate parent after backout')),
2868 2898 ('m', 'message', '', _('use <text> as commit message')),
2869 2899 ('l', 'logfile', '', _('read commit message from <file>')),
2870 2900 ('d', 'date', '', _('record datecode as commit date')),
2871 2901 ('u', 'user', '', _('record user as committer')),
2872 2902 ('I', 'include', [], _('include names matching the given patterns')),
2873 2903 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2874 2904 _('hg backout [OPTION]... REV')),
2875 2905 "bundle":
2876 2906 (bundle,
2877 2907 [('f', 'force', None,
2878 2908 _('run even when remote repository is unrelated'))],
2879 2909 _('hg bundle FILE DEST')),
2880 2910 "cat":
2881 2911 (cat,
2882 2912 [('o', 'output', '', _('print output to file with formatted name')),
2883 2913 ('r', 'rev', '', _('print the given revision')),
2884 2914 ('I', 'include', [], _('include names matching the given patterns')),
2885 2915 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2886 2916 _('hg cat [OPTION]... FILE...')),
2887 2917 "^clone":
2888 2918 (clone,
2889 2919 [('U', 'noupdate', None, _('do not update the new working directory')),
2890 2920 ('r', 'rev', [],
2891 2921 _('a changeset you would like to have after cloning')),
2892 2922 ('', 'pull', None, _('use pull protocol to copy metadata')),
2893 2923 ('e', 'ssh', '', _('specify ssh command to use')),
2894 2924 ('', 'remotecmd', '',
2895 2925 _('specify hg command to run on the remote side'))],
2896 2926 _('hg clone [OPTION]... SOURCE [DEST]')),
2897 2927 "^commit|ci":
2898 2928 (commit,
2899 2929 [('A', 'addremove', None,
2900 2930 _('mark new/missing files as added/removed before committing')),
2901 2931 ('m', 'message', '', _('use <text> as commit message')),
2902 2932 ('l', 'logfile', '', _('read the commit message from <file>')),
2903 2933 ('d', 'date', '', _('record datecode as commit date')),
2904 2934 ('u', 'user', '', _('record user as commiter')),
2905 2935 ('I', 'include', [], _('include names matching the given patterns')),
2906 2936 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2907 2937 _('hg commit [OPTION]... [FILE]...')),
2908 2938 "copy|cp":
2909 2939 (copy,
2910 2940 [('A', 'after', None, _('record a copy that has already occurred')),
2911 2941 ('f', 'force', None,
2912 2942 _('forcibly copy over an existing managed file')),
2913 2943 ('I', 'include', [], _('include names matching the given patterns')),
2914 2944 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2915 2945 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2916 2946 _('hg copy [OPTION]... [SOURCE]... DEST')),
2917 2947 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2918 2948 "debugcomplete":
2919 2949 (debugcomplete,
2920 2950 [('o', 'options', None, _('show the command options'))],
2921 2951 _('debugcomplete [-o] CMD')),
2922 2952 "debugrebuildstate":
2923 2953 (debugrebuildstate,
2924 2954 [('r', 'rev', '', _('revision to rebuild to'))],
2925 2955 _('debugrebuildstate [-r REV] [REV]')),
2926 2956 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2927 2957 "debugconfig": (debugconfig, [], _('debugconfig [NAME]...')),
2928 2958 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2929 2959 "debugstate": (debugstate, [], _('debugstate')),
2930 2960 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2931 2961 "debugindex": (debugindex, [], _('debugindex FILE')),
2932 2962 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2933 2963 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2934 2964 "debugwalk":
2935 2965 (debugwalk,
2936 2966 [('I', 'include', [], _('include names matching the given patterns')),
2937 2967 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2938 2968 _('debugwalk [OPTION]... [FILE]...')),
2939 2969 "^diff":
2940 2970 (diff,
2941 2971 [('r', 'rev', [], _('revision')),
2942 2972 ('a', 'text', None, _('treat all files as text')),
2943 2973 ('p', 'show-function', None,
2944 2974 _('show which function each change is in')),
2945 2975 ('w', 'ignore-all-space', None,
2946 2976 _('ignore white space when comparing lines')),
2947 2977 ('I', 'include', [], _('include names matching the given patterns')),
2948 2978 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2949 2979 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2950 2980 "^export":
2951 2981 (export,
2952 2982 [('o', 'output', '', _('print output to file with formatted name')),
2953 2983 ('a', 'text', None, _('treat all files as text')),
2954 2984 ('', 'switch-parent', None, _('diff against the second parent'))],
2955 2985 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2956 2986 "debugforget|forget":
2957 2987 (forget,
2958 2988 [('I', 'include', [], _('include names matching the given patterns')),
2959 2989 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2960 2990 _('hg forget [OPTION]... FILE...')),
2961 2991 "grep":
2962 2992 (grep,
2963 2993 [('0', 'print0', None, _('end fields with NUL')),
2964 2994 ('', 'all', None, _('print all revisions that match')),
2965 2995 ('i', 'ignore-case', None, _('ignore case when matching')),
2966 2996 ('l', 'files-with-matches', None,
2967 2997 _('print only filenames and revs that match')),
2968 2998 ('n', 'line-number', None, _('print matching line numbers')),
2969 2999 ('r', 'rev', [], _('search in given revision range')),
2970 3000 ('u', 'user', None, _('print user who committed change')),
2971 3001 ('I', 'include', [], _('include names matching the given patterns')),
2972 3002 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2973 3003 _('hg grep [OPTION]... PATTERN [FILE]...')),
2974 3004 "heads":
2975 3005 (heads,
2976 3006 [('b', 'branches', None, _('show branches')),
2977 3007 ('', 'style', '', _('display using template map file')),
2978 3008 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2979 3009 ('', 'template', '', _('display with template'))],
2980 3010 _('hg heads [-b] [-r <rev>]')),
2981 3011 "help": (help_, [], _('hg help [COMMAND]')),
2982 3012 "identify|id": (identify, [], _('hg identify')),
2983 3013 "import|patch":
2984 3014 (import_,
2985 3015 [('p', 'strip', 1,
2986 3016 _('directory strip option for patch. This has the same\n'
2987 3017 'meaning as the corresponding patch option')),
2988 3018 ('m', 'message', '', _('use <text> as commit message')),
2989 3019 ('b', 'base', '', _('base path')),
2990 3020 ('f', 'force', None,
2991 3021 _('skip check for outstanding uncommitted changes'))],
2992 3022 _('hg import [-p NUM] [-b BASE] [-m MESSAGE] [-f] PATCH...')),
2993 3023 "incoming|in": (incoming,
2994 3024 [('M', 'no-merges', None, _('do not show merges')),
2995 3025 ('f', 'force', None,
2996 3026 _('run even when remote repository is unrelated')),
2997 3027 ('', 'style', '', _('display using template map file')),
2998 3028 ('n', 'newest-first', None, _('show newest record first')),
2999 3029 ('', 'bundle', '', _('file to store the bundles into')),
3000 3030 ('p', 'patch', None, _('show patch')),
3031 ('r', 'rev', [], _('a specific revision you would like to pull')),
3001 3032 ('', 'template', '', _('display with template')),
3002 3033 ('e', 'ssh', '', _('specify ssh command to use')),
3003 3034 ('', 'remotecmd', '',
3004 3035 _('specify hg command to run on the remote side'))],
3005 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
3036 _('hg incoming [-p] [-n] [-M] [-r REV]...'
3037 '[--bundle FILENAME] [SOURCE]')),
3006 3038 "^init": (init, [], _('hg init [DEST]')),
3007 3039 "locate":
3008 3040 (locate,
3009 3041 [('r', 'rev', '', _('search the repository as it stood at rev')),
3010 3042 ('0', 'print0', None,
3011 3043 _('end filenames with NUL, for use with xargs')),
3012 3044 ('f', 'fullpath', None,
3013 3045 _('print complete paths from the filesystem root')),
3014 3046 ('I', 'include', [], _('include names matching the given patterns')),
3015 3047 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3016 3048 _('hg locate [OPTION]... [PATTERN]...')),
3017 3049 "^log|history":
3018 3050 (log,
3019 3051 [('b', 'branches', None, _('show branches')),
3020 3052 ('k', 'keyword', [], _('search for a keyword')),
3021 3053 ('l', 'limit', '', _('limit number of changes displayed')),
3022 3054 ('r', 'rev', [], _('show the specified revision or range')),
3023 3055 ('M', 'no-merges', None, _('do not show merges')),
3024 3056 ('', 'style', '', _('display using template map file')),
3025 3057 ('m', 'only-merges', None, _('show only merges')),
3026 3058 ('p', 'patch', None, _('show patch')),
3027 3059 ('', 'template', '', _('display with template')),
3028 3060 ('I', 'include', [], _('include names matching the given patterns')),
3029 3061 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3030 3062 _('hg log [OPTION]... [FILE]')),
3031 3063 "manifest": (manifest, [], _('hg manifest [REV]')),
3032 3064 "merge":
3033 3065 (merge,
3034 3066 [('b', 'branch', '', _('merge with head of a specific branch')),
3035 3067 ('f', 'force', None, _('force a merge with outstanding changes'))],
3036 3068 _('hg merge [-b TAG] [-f] [REV]')),
3037 3069 "outgoing|out": (outgoing,
3038 3070 [('M', 'no-merges', None, _('do not show merges')),
3039 3071 ('f', 'force', None,
3040 3072 _('run even when remote repository is unrelated')),
3041 3073 ('p', 'patch', None, _('show patch')),
3042 3074 ('', 'style', '', _('display using template map file')),
3075 ('r', 'rev', [], _('a specific revision you would like to push')),
3043 3076 ('n', 'newest-first', None, _('show newest record first')),
3044 3077 ('', 'template', '', _('display with template')),
3045 3078 ('e', 'ssh', '', _('specify ssh command to use')),
3046 3079 ('', 'remotecmd', '',
3047 3080 _('specify hg command to run on the remote side'))],
3048 _('hg outgoing [-M] [-p] [-n] [DEST]')),
3081 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
3049 3082 "^parents":
3050 3083 (parents,
3051 3084 [('b', 'branches', None, _('show branches')),
3052 3085 ('', 'style', '', _('display using template map file')),
3053 3086 ('', 'template', '', _('display with template'))],
3054 3087 _('hg parents [-b] [REV]')),
3055 3088 "paths": (paths, [], _('hg paths [NAME]')),
3056 3089 "^pull":
3057 3090 (pull,
3058 3091 [('u', 'update', None,
3059 3092 _('update the working directory to tip after pull')),
3060 3093 ('e', 'ssh', '', _('specify ssh command to use')),
3061 3094 ('f', 'force', None,
3062 3095 _('run even when remote repository is unrelated')),
3063 3096 ('r', 'rev', [], _('a specific revision you would like to pull')),
3064 3097 ('', 'remotecmd', '',
3065 3098 _('specify hg command to run on the remote side'))],
3066 3099 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3067 3100 "^push":
3068 3101 (push,
3069 3102 [('f', 'force', None, _('force push')),
3070 3103 ('e', 'ssh', '', _('specify ssh command to use')),
3071 3104 ('r', 'rev', [], _('a specific revision you would like to push')),
3072 3105 ('', 'remotecmd', '',
3073 3106 _('specify hg command to run on the remote side'))],
3074 3107 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3075 3108 "debugrawcommit|rawcommit":
3076 3109 (rawcommit,
3077 3110 [('p', 'parent', [], _('parent')),
3078 3111 ('d', 'date', '', _('date code')),
3079 3112 ('u', 'user', '', _('user')),
3080 3113 ('F', 'files', '', _('file list')),
3081 3114 ('m', 'message', '', _('commit message')),
3082 3115 ('l', 'logfile', '', _('commit message file'))],
3083 3116 _('hg debugrawcommit [OPTION]... [FILE]...')),
3084 3117 "recover": (recover, [], _('hg recover')),
3085 3118 "^remove|rm":
3086 3119 (remove,
3087 3120 [('A', 'after', None, _('record remove that has already occurred')),
3088 3121 ('f', 'force', None, _('remove file even if modified')),
3089 3122 ('I', 'include', [], _('include names matching the given patterns')),
3090 3123 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3091 3124 _('hg remove [OPTION]... FILE...')),
3092 3125 "rename|mv":
3093 3126 (rename,
3094 3127 [('A', 'after', None, _('record a rename that has already occurred')),
3095 3128 ('f', 'force', None,
3096 3129 _('forcibly copy over an existing managed file')),
3097 3130 ('I', 'include', [], _('include names matching the given patterns')),
3098 3131 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3099 3132 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3100 3133 _('hg rename [OPTION]... SOURCE... DEST')),
3101 3134 "^revert":
3102 3135 (revert,
3103 3136 [('r', 'rev', '', _('revision to revert to')),
3104 3137 ('', 'no-backup', None, _('do not save backup copies of files')),
3105 3138 ('I', 'include', [], _('include names matching given patterns')),
3106 3139 ('X', 'exclude', [], _('exclude names matching given patterns')),
3107 3140 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3108 3141 _('hg revert [-r REV] [NAME]...')),
3109 3142 "rollback": (rollback, [], _('hg rollback')),
3110 3143 "root": (root, [], _('hg root')),
3111 3144 "^serve":
3112 3145 (serve,
3113 3146 [('A', 'accesslog', '', _('name of access log file to write to')),
3114 3147 ('d', 'daemon', None, _('run server in background')),
3115 3148 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3116 3149 ('E', 'errorlog', '', _('name of error log file to write to')),
3117 3150 ('p', 'port', 0, _('port to use (default: 8000)')),
3118 3151 ('a', 'address', '', _('address to use')),
3119 3152 ('n', 'name', '',
3120 3153 _('name to show in web pages (default: working dir)')),
3121 3154 ('', 'webdir-conf', '', _('name of the webdir config file'
3122 3155 ' (serve more than one repo)')),
3123 3156 ('', 'pid-file', '', _('name of file to write process ID to')),
3124 3157 ('', 'stdio', None, _('for remote clients')),
3125 3158 ('t', 'templates', '', _('web templates to use')),
3126 3159 ('', 'style', '', _('template style to use')),
3127 3160 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3128 3161 _('hg serve [OPTION]...')),
3129 3162 "^status|st":
3130 3163 (status,
3131 3164 [('m', 'modified', None, _('show only modified files')),
3132 3165 ('a', 'added', None, _('show only added files')),
3133 3166 ('r', 'removed', None, _('show only removed files')),
3134 3167 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3135 3168 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3136 3169 ('i', 'ignored', None, _('show ignored files')),
3137 3170 ('n', 'no-status', None, _('hide status prefix')),
3138 3171 ('0', 'print0', None,
3139 3172 _('end filenames with NUL, for use with xargs')),
3140 3173 ('I', 'include', [], _('include names matching the given patterns')),
3141 3174 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3142 3175 _('hg status [OPTION]... [FILE]...')),
3143 3176 "tag":
3144 3177 (tag,
3145 3178 [('l', 'local', None, _('make the tag local')),
3146 3179 ('m', 'message', '', _('message for tag commit log entry')),
3147 3180 ('d', 'date', '', _('record datecode as commit date')),
3148 3181 ('u', 'user', '', _('record user as commiter')),
3149 3182 ('r', 'rev', '', _('revision to tag'))],
3150 3183 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3151 3184 "tags": (tags, [], _('hg tags')),
3152 3185 "tip":
3153 3186 (tip,
3154 3187 [('b', 'branches', None, _('show branches')),
3155 3188 ('', 'style', '', _('display using template map file')),
3156 3189 ('p', 'patch', None, _('show patch')),
3157 3190 ('', 'template', '', _('display with template'))],
3158 3191 _('hg tip [-b] [-p]')),
3159 3192 "unbundle":
3160 3193 (unbundle,
3161 3194 [('u', 'update', None,
3162 3195 _('update the working directory to tip after unbundle'))],
3163 3196 _('hg unbundle [-u] FILE')),
3164 3197 "debugundo|undo": (undo, [], _('hg undo')),
3165 3198 "^update|up|checkout|co":
3166 3199 (update,
3167 3200 [('b', 'branch', '', _('checkout the head of a specific branch')),
3168 3201 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3169 3202 ('C', 'clean', None, _('overwrite locally modified files')),
3170 3203 ('f', 'force', None, _('force a merge with outstanding changes'))],
3171 3204 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3172 3205 "verify": (verify, [], _('hg verify')),
3173 3206 "version": (show_version, [], _('hg version')),
3174 3207 }
3175 3208
3176 3209 globalopts = [
3177 3210 ('R', 'repository', '',
3178 3211 _('repository root directory or symbolic path name')),
3179 3212 ('', 'cwd', '', _('change working directory')),
3180 3213 ('y', 'noninteractive', None,
3181 3214 _('do not prompt, assume \'yes\' for any required answers')),
3182 3215 ('q', 'quiet', None, _('suppress output')),
3183 3216 ('v', 'verbose', None, _('enable additional output')),
3184 3217 ('', 'config', [], _('set/override config option')),
3185 3218 ('', 'debug', None, _('enable debugging output')),
3186 3219 ('', 'debugger', None, _('start debugger')),
3187 3220 ('', 'lsprof', None, _('print improved command execution profile')),
3188 3221 ('', 'traceback', None, _('print traceback on exception')),
3189 3222 ('', 'time', None, _('time how long the command takes')),
3190 3223 ('', 'profile', None, _('print command execution profile')),
3191 3224 ('', 'version', None, _('output version information and exit')),
3192 3225 ('h', 'help', None, _('display help and exit')),
3193 3226 ]
3194 3227
3195 3228 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3196 3229 " debugindex debugindexdot")
3197 3230 optionalrepo = ("paths serve debugconfig")
3198 3231
3199 3232 def findpossible(cmd):
3200 3233 """
3201 3234 Return cmd -> (aliases, command table entry)
3202 3235 for each matching command.
3203 3236 Return debug commands (or their aliases) only if no normal command matches.
3204 3237 """
3205 3238 choice = {}
3206 3239 debugchoice = {}
3207 3240 for e in table.keys():
3208 3241 aliases = e.lstrip("^").split("|")
3209 3242 found = None
3210 3243 if cmd in aliases:
3211 3244 found = cmd
3212 3245 else:
3213 3246 for a in aliases:
3214 3247 if a.startswith(cmd):
3215 3248 found = a
3216 3249 break
3217 3250 if found is not None:
3218 3251 if aliases[0].startswith("debug"):
3219 3252 debugchoice[found] = (aliases, table[e])
3220 3253 else:
3221 3254 choice[found] = (aliases, table[e])
3222 3255
3223 3256 if not choice and debugchoice:
3224 3257 choice = debugchoice
3225 3258
3226 3259 return choice
3227 3260
3228 3261 def find(cmd):
3229 3262 """Return (aliases, command table entry) for command string."""
3230 3263 choice = findpossible(cmd)
3231 3264
3232 3265 if choice.has_key(cmd):
3233 3266 return choice[cmd]
3234 3267
3235 3268 if len(choice) > 1:
3236 3269 clist = choice.keys()
3237 3270 clist.sort()
3238 3271 raise AmbiguousCommand(cmd, clist)
3239 3272
3240 3273 if choice:
3241 3274 return choice.values()[0]
3242 3275
3243 3276 raise UnknownCommand(cmd)
3244 3277
3245 3278 def catchterm(*args):
3246 3279 raise util.SignalInterrupt
3247 3280
3248 3281 def run():
3249 3282 sys.exit(dispatch(sys.argv[1:]))
3250 3283
3251 3284 class ParseError(Exception):
3252 3285 """Exception raised on errors in parsing the command line."""
3253 3286
3254 3287 def parse(ui, args):
3255 3288 options = {}
3256 3289 cmdoptions = {}
3257 3290
3258 3291 try:
3259 3292 args = fancyopts.fancyopts(args, globalopts, options)
3260 3293 except fancyopts.getopt.GetoptError, inst:
3261 3294 raise ParseError(None, inst)
3262 3295
3263 3296 if args:
3264 3297 cmd, args = args[0], args[1:]
3265 3298 aliases, i = find(cmd)
3266 3299 cmd = aliases[0]
3267 3300 defaults = ui.config("defaults", cmd)
3268 3301 if defaults:
3269 3302 args = defaults.split() + args
3270 3303 c = list(i[1])
3271 3304 else:
3272 3305 cmd = None
3273 3306 c = []
3274 3307
3275 3308 # combine global options into local
3276 3309 for o in globalopts:
3277 3310 c.append((o[0], o[1], options[o[1]], o[3]))
3278 3311
3279 3312 try:
3280 3313 args = fancyopts.fancyopts(args, c, cmdoptions)
3281 3314 except fancyopts.getopt.GetoptError, inst:
3282 3315 raise ParseError(cmd, inst)
3283 3316
3284 3317 # separate global options back out
3285 3318 for o in globalopts:
3286 3319 n = o[1]
3287 3320 options[n] = cmdoptions[n]
3288 3321 del cmdoptions[n]
3289 3322
3290 3323 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3291 3324
3292 3325 def dispatch(args):
3293 3326 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3294 3327 num = getattr(signal, name, None)
3295 3328 if num: signal.signal(num, catchterm)
3296 3329
3297 3330 try:
3298 3331 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3299 3332 except util.Abort, inst:
3300 3333 sys.stderr.write(_("abort: %s\n") % inst)
3301 3334 return -1
3302 3335
3303 3336 external = []
3304 3337 for x in u.extensions():
3305 3338 try:
3306 3339 if x[1]:
3307 3340 # the module will be loaded in sys.modules
3308 3341 # choose an unique name so that it doesn't
3309 3342 # conflicts with other modules
3310 3343 module_name = "hgext_%s" % x[0].replace('.', '_')
3311 3344 mod = imp.load_source(module_name, x[1])
3312 3345 else:
3313 3346 def importh(name):
3314 3347 mod = __import__(name)
3315 3348 components = name.split('.')
3316 3349 for comp in components[1:]:
3317 3350 mod = getattr(mod, comp)
3318 3351 return mod
3319 3352 try:
3320 3353 mod = importh("hgext." + x[0])
3321 3354 except ImportError:
3322 3355 mod = importh(x[0])
3323 3356 external.append(mod)
3324 3357 except (util.SignalInterrupt, KeyboardInterrupt):
3325 3358 raise
3326 3359 except Exception, inst:
3327 3360 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3328 3361 if u.print_exc():
3329 3362 return 1
3330 3363
3331 3364 for x in external:
3332 3365 uisetup = getattr(x, 'uisetup', None)
3333 3366 if uisetup:
3334 3367 uisetup(u)
3335 3368 cmdtable = getattr(x, 'cmdtable', {})
3336 3369 for t in cmdtable:
3337 3370 if t in table:
3338 3371 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3339 3372 table.update(cmdtable)
3340 3373
3341 3374 try:
3342 3375 cmd, func, args, options, cmdoptions = parse(u, args)
3343 3376 if options["time"]:
3344 3377 def get_times():
3345 3378 t = os.times()
3346 3379 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3347 3380 t = (t[0], t[1], t[2], t[3], time.clock())
3348 3381 return t
3349 3382 s = get_times()
3350 3383 def print_time():
3351 3384 t = get_times()
3352 3385 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3353 3386 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3354 3387 atexit.register(print_time)
3355 3388
3356 3389 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3357 3390 not options["noninteractive"], options["traceback"],
3358 3391 options["config"])
3359 3392
3360 3393 # enter the debugger before command execution
3361 3394 if options['debugger']:
3362 3395 pdb.set_trace()
3363 3396
3364 3397 try:
3365 3398 if options['cwd']:
3366 3399 try:
3367 3400 os.chdir(options['cwd'])
3368 3401 except OSError, inst:
3369 3402 raise util.Abort('%s: %s' %
3370 3403 (options['cwd'], inst.strerror))
3371 3404
3372 3405 path = u.expandpath(options["repository"]) or ""
3373 3406 repo = path and hg.repository(u, path=path) or None
3374 3407
3375 3408 if options['help']:
3376 3409 return help_(u, cmd, options['version'])
3377 3410 elif options['version']:
3378 3411 return show_version(u)
3379 3412 elif not cmd:
3380 3413 return help_(u, 'shortlist')
3381 3414
3382 3415 if cmd not in norepo.split():
3383 3416 try:
3384 3417 if not repo:
3385 3418 repo = hg.repository(u, path=path)
3386 3419 u = repo.ui
3387 3420 for x in external:
3388 3421 if hasattr(x, 'reposetup'):
3389 3422 x.reposetup(u, repo)
3390 3423 except hg.RepoError:
3391 3424 if cmd not in optionalrepo.split():
3392 3425 raise
3393 3426 d = lambda: func(u, repo, *args, **cmdoptions)
3394 3427 else:
3395 3428 d = lambda: func(u, *args, **cmdoptions)
3396 3429
3397 3430 try:
3398 3431 if options['profile']:
3399 3432 import hotshot, hotshot.stats
3400 3433 prof = hotshot.Profile("hg.prof")
3401 3434 try:
3402 3435 try:
3403 3436 return prof.runcall(d)
3404 3437 except:
3405 3438 try:
3406 3439 u.warn(_('exception raised - generating '
3407 3440 'profile anyway\n'))
3408 3441 except:
3409 3442 pass
3410 3443 raise
3411 3444 finally:
3412 3445 prof.close()
3413 3446 stats = hotshot.stats.load("hg.prof")
3414 3447 stats.strip_dirs()
3415 3448 stats.sort_stats('time', 'calls')
3416 3449 stats.print_stats(40)
3417 3450 elif options['lsprof']:
3418 3451 try:
3419 3452 from mercurial import lsprof
3420 3453 except ImportError:
3421 3454 raise util.Abort(_(
3422 3455 'lsprof not available - install from '
3423 3456 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3424 3457 p = lsprof.Profiler()
3425 3458 p.enable(subcalls=True)
3426 3459 try:
3427 3460 return d()
3428 3461 finally:
3429 3462 p.disable()
3430 3463 stats = lsprof.Stats(p.getstats())
3431 3464 stats.sort()
3432 3465 stats.pprint(top=10, file=sys.stderr, climit=5)
3433 3466 else:
3434 3467 return d()
3435 3468 finally:
3436 3469 u.flush()
3437 3470 except:
3438 3471 # enter the debugger when we hit an exception
3439 3472 if options['debugger']:
3440 3473 pdb.post_mortem(sys.exc_info()[2])
3441 3474 u.print_exc()
3442 3475 raise
3443 3476 except ParseError, inst:
3444 3477 if inst.args[0]:
3445 3478 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3446 3479 help_(u, inst.args[0])
3447 3480 else:
3448 3481 u.warn(_("hg: %s\n") % inst.args[1])
3449 3482 help_(u, 'shortlist')
3450 3483 except AmbiguousCommand, inst:
3451 3484 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3452 3485 (inst.args[0], " ".join(inst.args[1])))
3453 3486 except UnknownCommand, inst:
3454 3487 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3455 3488 help_(u, 'shortlist')
3456 3489 except hg.RepoError, inst:
3457 3490 u.warn(_("abort: %s!\n") % inst)
3458 3491 except lock.LockHeld, inst:
3459 3492 if inst.errno == errno.ETIMEDOUT:
3460 3493 reason = _('timed out waiting for lock held by %s') % inst.locker
3461 3494 else:
3462 3495 reason = _('lock held by %s') % inst.locker
3463 3496 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3464 3497 except lock.LockUnavailable, inst:
3465 3498 u.warn(_("abort: could not lock %s: %s\n") %
3466 3499 (inst.desc or inst.filename, inst.strerror))
3467 3500 except revlog.RevlogError, inst:
3468 3501 u.warn(_("abort: "), inst, "!\n")
3469 3502 except util.SignalInterrupt:
3470 3503 u.warn(_("killed!\n"))
3471 3504 except KeyboardInterrupt:
3472 3505 try:
3473 3506 u.warn(_("interrupted!\n"))
3474 3507 except IOError, inst:
3475 3508 if inst.errno == errno.EPIPE:
3476 3509 if u.debugflag:
3477 3510 u.warn(_("\nbroken pipe\n"))
3478 3511 else:
3479 3512 raise
3480 3513 except IOError, inst:
3481 3514 if hasattr(inst, "code"):
3482 3515 u.warn(_("abort: %s\n") % inst)
3483 3516 elif hasattr(inst, "reason"):
3484 3517 u.warn(_("abort: error: %s\n") % inst.reason[1])
3485 3518 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3486 3519 if u.debugflag:
3487 3520 u.warn(_("broken pipe\n"))
3488 3521 elif getattr(inst, "strerror", None):
3489 3522 if getattr(inst, "filename", None):
3490 3523 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3491 3524 else:
3492 3525 u.warn(_("abort: %s\n") % inst.strerror)
3493 3526 else:
3494 3527 raise
3495 3528 except OSError, inst:
3496 3529 if hasattr(inst, "filename"):
3497 3530 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3498 3531 else:
3499 3532 u.warn(_("abort: %s\n") % inst.strerror)
3500 3533 except util.Abort, inst:
3501 3534 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3502 3535 except TypeError, inst:
3503 3536 # was this an argument error?
3504 3537 tb = traceback.extract_tb(sys.exc_info()[2])
3505 3538 if len(tb) > 2: # no
3506 3539 raise
3507 3540 u.debug(inst, "\n")
3508 3541 u.warn(_("%s: invalid arguments\n") % cmd)
3509 3542 help_(u, cmd)
3510 3543 except SystemExit, inst:
3511 3544 # Commands shouldn't sys.exit directly, but give a return code.
3512 3545 # Just in case catch this and and pass exit code to caller.
3513 3546 return inst.code
3514 3547 except:
3515 3548 u.warn(_("** unknown exception encountered, details follow\n"))
3516 3549 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3517 3550 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3518 3551 % version.get_version())
3519 3552 raise
3520 3553
3521 3554 return -1
@@ -1,42 +1,44 b''
1 1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetypes
10 10 import os.path
11 11
12 12 def get_mtime(repo_path):
13 13 hg_path = os.path.join(repo_path, ".hg")
14 14 cl_path = os.path.join(hg_path, "00changelog.i")
15 15 if os.path.exists(os.path.join(cl_path)):
16 16 return os.stat(cl_path).st_mtime
17 17 else:
18 18 return os.stat(hg_path).st_mtime
19 19
20 def staticfile(directory, fname):
20 def staticfile(directory, fname, req):
21 21 """return a file inside directory with guessed content-type header
22 22
23 23 fname always uses '/' as directory separator and isn't allowed to
24 24 contain unusual path components.
25 25 Content-type is guessed using the mimetypes module.
26 26 Return an empty string if fname is illegal or file not found.
27 27
28 28 """
29 29 parts = fname.split('/')
30 30 path = directory
31 31 for part in parts:
32 32 if (part in ('', os.curdir, os.pardir) or
33 33 os.sep in part or os.altsep is not None and os.altsep in part):
34 34 return ""
35 35 path = os.path.join(path, part)
36 36 try:
37 37 os.stat(path)
38 38 ct = mimetypes.guess_type(path)[0] or "text/plain"
39 return "Content-type: %s\n\n%s" % (ct, file(path).read())
39 req.header([('Content-type', ct),
40 ('Content-length', os.path.getsize(path))])
41 return file(path).read()
40 42 except (TypeError, OSError):
41 43 # illegal fname or unreadable file
42 44 return ""
@@ -1,926 +1,927 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 import os.path
11 11 import mimetypes
12 12 from mercurial.demandload import demandload
13 demandload(globals(), "re zlib ConfigParser cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
16 15 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
17 16 from mercurial.node import *
18 17 from mercurial.i18n import gettext as _
19 18
20 19 def _up(p):
21 20 if p[0] != "/":
22 21 p = "/" + p
23 22 if p[-1] == "/":
24 23 p = p[:-1]
25 24 up = os.path.dirname(p)
26 25 if up == "/":
27 26 return "/"
28 27 return up + "/"
29 28
30 29 class hgweb(object):
31 30 def __init__(self, repo, name=None):
32 31 if type(repo) == type(""):
33 32 self.repo = hg.repository(ui.ui(), repo)
34 33 else:
35 34 self.repo = repo
36 35
37 36 self.mtime = -1
38 37 self.reponame = name
39 38 self.archives = 'zip', 'gz', 'bz2'
40 39 self.templatepath = self.repo.ui.config("web", "templates",
41 40 templater.templatepath())
42 41
43 42 def refresh(self):
44 43 mtime = get_mtime(self.repo.root)
45 44 if mtime != self.mtime:
46 45 self.mtime = mtime
47 46 self.repo = hg.repository(self.repo.ui, self.repo.root)
48 47 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
49 48 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
50 49 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
51 50
52 51 def archivelist(self, nodeid):
53 52 allowed = self.repo.ui.configlist("web", "allow_archive")
54 53 for i in self.archives:
55 54 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
56 55 yield {"type" : i, "node" : nodeid, "url": ""}
57 56
58 57 def listfiles(self, files, mf):
59 58 for f in files[:self.maxfiles]:
60 59 yield self.t("filenodelink", node=hex(mf[f]), file=f)
61 60 if len(files) > self.maxfiles:
62 61 yield self.t("fileellipses")
63 62
64 63 def listfilediffs(self, files, changeset):
65 64 for f in files[:self.maxfiles]:
66 65 yield self.t("filedifflink", node=hex(changeset), file=f)
67 66 if len(files) > self.maxfiles:
68 67 yield self.t("fileellipses")
69 68
70 69 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
71 70 if not rev:
72 71 rev = lambda x: ""
73 72 siblings = [s for s in siblings if s != nullid]
74 73 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
75 74 return
76 75 for s in siblings:
77 76 yield dict(node=hex(s), rev=rev(s), **args)
78 77
79 78 def renamelink(self, fl, node):
80 79 r = fl.renamed(node)
81 80 if r:
82 81 return [dict(file=r[0], node=hex(r[1]))]
83 82 return []
84 83
85 84 def showtag(self, t1, node=nullid, **args):
86 85 for t in self.repo.nodetags(node):
87 86 yield self.t(t1, tag=t, **args)
88 87
89 88 def diff(self, node1, node2, files):
90 89 def filterfiles(filters, files):
91 90 l = [x for x in files if x in filters]
92 91
93 92 for t in filters:
94 93 if t and t[-1] != os.sep:
95 94 t += os.sep
96 95 l += [x for x in files if x.startswith(t)]
97 96 return l
98 97
99 98 parity = [0]
100 99 def diffblock(diff, f, fn):
101 100 yield self.t("diffblock",
102 101 lines=prettyprintlines(diff),
103 102 parity=parity[0],
104 103 file=f,
105 104 filenode=hex(fn or nullid))
106 105 parity[0] = 1 - parity[0]
107 106
108 107 def prettyprintlines(diff):
109 108 for l in diff.splitlines(1):
110 109 if l.startswith('+'):
111 110 yield self.t("difflineplus", line=l)
112 111 elif l.startswith('-'):
113 112 yield self.t("difflineminus", line=l)
114 113 elif l.startswith('@'):
115 114 yield self.t("difflineat", line=l)
116 115 else:
117 116 yield self.t("diffline", line=l)
118 117
119 118 r = self.repo
120 119 cl = r.changelog
121 120 mf = r.manifest
122 121 change1 = cl.read(node1)
123 122 change2 = cl.read(node2)
124 123 mmap1 = mf.read(change1[0])
125 124 mmap2 = mf.read(change2[0])
126 125 date1 = util.datestr(change1[2])
127 126 date2 = util.datestr(change2[2])
128 127
129 128 modified, added, removed, deleted, unknown = r.changes(node1, node2)
130 129 if files:
131 130 modified, added, removed = map(lambda x: filterfiles(files, x),
132 131 (modified, added, removed))
133 132
134 133 diffopts = self.repo.ui.diffopts()
135 134 showfunc = diffopts['showfunc']
136 135 ignorews = diffopts['ignorews']
137 136 for f in modified:
138 137 to = r.file(f).read(mmap1[f])
139 138 tn = r.file(f).read(mmap2[f])
140 139 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
141 140 showfunc=showfunc, ignorews=ignorews), f, tn)
142 141 for f in added:
143 142 to = None
144 143 tn = r.file(f).read(mmap2[f])
145 144 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
146 145 showfunc=showfunc, ignorews=ignorews), f, tn)
147 146 for f in removed:
148 147 to = r.file(f).read(mmap1[f])
149 148 tn = None
150 149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
151 150 showfunc=showfunc, ignorews=ignorews), f, tn)
152 151
153 152 def changelog(self, pos):
154 153 def changenav(**map):
155 154 def seq(factor, maxchanges=None):
156 155 if maxchanges:
157 156 yield maxchanges
158 157 if maxchanges >= 20 and maxchanges <= 40:
159 158 yield 50
160 159 else:
161 160 yield 1 * factor
162 161 yield 3 * factor
163 162 for f in seq(factor * 10):
164 163 yield f
165 164
166 165 l = []
167 166 last = 0
168 167 for f in seq(1, self.maxchanges):
169 168 if f < self.maxchanges or f <= last:
170 169 continue
171 170 if f > count:
172 171 break
173 172 last = f
174 173 r = "%d" % f
175 174 if pos + f < count:
176 175 l.append(("+" + r, pos + f))
177 176 if pos - f >= 0:
178 177 l.insert(0, ("-" + r, pos - f))
179 178
180 179 yield {"rev": 0, "label": "(0)"}
181 180
182 181 for label, rev in l:
183 182 yield {"label": label, "rev": rev}
184 183
185 184 yield {"label": "tip", "rev": "tip"}
186 185
187 186 def changelist(**map):
188 187 parity = (start - end) & 1
189 188 cl = self.repo.changelog
190 189 l = [] # build a list in forward order for efficiency
191 190 for i in range(start, end):
192 191 n = cl.node(i)
193 192 changes = cl.read(n)
194 193 hn = hex(n)
195 194
196 195 l.insert(0, {"parity": parity,
197 196 "author": changes[1],
198 197 "parent": self.siblings(cl.parents(n), cl.rev,
199 198 cl.rev(n) - 1),
200 199 "child": self.siblings(cl.children(n), cl.rev,
201 200 cl.rev(n) + 1),
202 201 "changelogtag": self.showtag("changelogtag",n),
203 202 "manifest": hex(changes[0]),
204 203 "desc": changes[4],
205 204 "date": changes[2],
206 205 "files": self.listfilediffs(changes[3], n),
207 206 "rev": i,
208 207 "node": hn})
209 208 parity = 1 - parity
210 209
211 210 for e in l:
212 211 yield e
213 212
214 213 cl = self.repo.changelog
215 214 mf = cl.read(cl.tip())[0]
216 215 count = cl.count()
217 216 start = max(0, pos - self.maxchanges + 1)
218 217 end = min(count, start + self.maxchanges)
219 218 pos = end - 1
220 219
221 220 yield self.t('changelog',
222 221 changenav=changenav,
223 222 manifest=hex(mf),
224 223 rev=pos, changesets=count, entries=changelist,
225 224 archives=self.archivelist("tip"))
226 225
227 226 def search(self, query):
228 227
229 228 def changelist(**map):
230 229 cl = self.repo.changelog
231 230 count = 0
232 231 qw = query.lower().split()
233 232
234 233 def revgen():
235 234 for i in range(cl.count() - 1, 0, -100):
236 235 l = []
237 236 for j in range(max(0, i - 100), i):
238 237 n = cl.node(j)
239 238 changes = cl.read(n)
240 239 l.append((n, j, changes))
241 240 l.reverse()
242 241 for e in l:
243 242 yield e
244 243
245 244 for n, i, changes in revgen():
246 245 miss = 0
247 246 for q in qw:
248 247 if not (q in changes[1].lower() or
249 248 q in changes[4].lower() or
250 249 q in " ".join(changes[3][:20]).lower()):
251 250 miss = 1
252 251 break
253 252 if miss:
254 253 continue
255 254
256 255 count += 1
257 256 hn = hex(n)
258 257
259 258 yield self.t('searchentry',
260 259 parity=count & 1,
261 260 author=changes[1],
262 261 parent=self.siblings(cl.parents(n), cl.rev),
263 262 child=self.siblings(cl.children(n), cl.rev),
264 263 changelogtag=self.showtag("changelogtag",n),
265 264 manifest=hex(changes[0]),
266 265 desc=changes[4],
267 266 date=changes[2],
268 267 files=self.listfilediffs(changes[3], n),
269 268 rev=i,
270 269 node=hn)
271 270
272 271 if count >= self.maxchanges:
273 272 break
274 273
275 274 cl = self.repo.changelog
276 275 mf = cl.read(cl.tip())[0]
277 276
278 277 yield self.t('search',
279 278 query=query,
280 279 manifest=hex(mf),
281 280 entries=changelist)
282 281
283 282 def changeset(self, nodeid):
284 283 cl = self.repo.changelog
285 284 n = self.repo.lookup(nodeid)
286 285 nodeid = hex(n)
287 286 changes = cl.read(n)
288 287 p1 = cl.parents(n)[0]
289 288
290 289 files = []
291 290 mf = self.repo.manifest.read(changes[0])
292 291 for f in changes[3]:
293 292 files.append(self.t("filenodelink",
294 293 filenode=hex(mf.get(f, nullid)), file=f))
295 294
296 295 def diff(**map):
297 296 yield self.diff(p1, n, None)
298 297
299 298 yield self.t('changeset',
300 299 diff=diff,
301 300 rev=cl.rev(n),
302 301 node=nodeid,
303 302 parent=self.siblings(cl.parents(n), cl.rev),
304 303 child=self.siblings(cl.children(n), cl.rev),
305 304 changesettag=self.showtag("changesettag",n),
306 305 manifest=hex(changes[0]),
307 306 author=changes[1],
308 307 desc=changes[4],
309 308 date=changes[2],
310 309 files=files,
311 310 archives=self.archivelist(nodeid))
312 311
313 312 def filelog(self, f, filenode):
314 313 cl = self.repo.changelog
315 314 fl = self.repo.file(f)
316 315 filenode = hex(fl.lookup(filenode))
317 316 count = fl.count()
318 317
319 318 def entries(**map):
320 319 l = []
321 320 parity = (count - 1) & 1
322 321
323 322 for i in range(count):
324 323 n = fl.node(i)
325 324 lr = fl.linkrev(n)
326 325 cn = cl.node(lr)
327 326 cs = cl.read(cl.node(lr))
328 327
329 328 l.insert(0, {"parity": parity,
330 329 "filenode": hex(n),
331 330 "filerev": i,
332 331 "file": f,
333 332 "node": hex(cn),
334 333 "author": cs[1],
335 334 "date": cs[2],
336 335 "rename": self.renamelink(fl, n),
337 336 "parent": self.siblings(fl.parents(n),
338 337 fl.rev, file=f),
339 338 "child": self.siblings(fl.children(n),
340 339 fl.rev, file=f),
341 340 "desc": cs[4]})
342 341 parity = 1 - parity
343 342
344 343 for e in l:
345 344 yield e
346 345
347 346 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
348 347
349 348 def filerevision(self, f, node):
350 349 fl = self.repo.file(f)
351 350 n = fl.lookup(node)
352 351 node = hex(n)
353 352 text = fl.read(n)
354 353 changerev = fl.linkrev(n)
355 354 cl = self.repo.changelog
356 355 cn = cl.node(changerev)
357 356 cs = cl.read(cn)
358 357 mfn = cs[0]
359 358
360 359 mt = mimetypes.guess_type(f)[0]
361 360 rawtext = text
362 361 if util.binary(text):
363 362 mt = mt or 'application/octet-stream'
364 363 text = "(binary:%s)" % mt
365 364 mt = mt or 'text/plain'
366 365
367 366 def lines():
368 367 for l, t in enumerate(text.splitlines(1)):
369 368 yield {"line": t,
370 369 "linenumber": "% 6d" % (l + 1),
371 370 "parity": l & 1}
372 371
373 372 yield self.t("filerevision",
374 373 file=f,
375 374 filenode=node,
376 375 path=_up(f),
377 376 text=lines(),
378 377 raw=rawtext,
379 378 mimetype=mt,
380 379 rev=changerev,
381 380 node=hex(cn),
382 381 manifest=hex(mfn),
383 382 author=cs[1],
384 383 date=cs[2],
385 384 parent=self.siblings(fl.parents(n), fl.rev, file=f),
386 385 child=self.siblings(fl.children(n), fl.rev, file=f),
387 386 rename=self.renamelink(fl, n),
388 387 permissions=self.repo.manifest.readflags(mfn)[f])
389 388
390 389 def fileannotate(self, f, node):
391 390 bcache = {}
392 391 ncache = {}
393 392 fl = self.repo.file(f)
394 393 n = fl.lookup(node)
395 394 node = hex(n)
396 395 changerev = fl.linkrev(n)
397 396
398 397 cl = self.repo.changelog
399 398 cn = cl.node(changerev)
400 399 cs = cl.read(cn)
401 400 mfn = cs[0]
402 401
403 402 def annotate(**map):
404 403 parity = 1
405 404 last = None
406 405 for r, l in fl.annotate(n):
407 406 try:
408 407 cnode = ncache[r]
409 408 except KeyError:
410 409 cnode = ncache[r] = self.repo.changelog.node(r)
411 410
412 411 try:
413 412 name = bcache[r]
414 413 except KeyError:
415 414 cl = self.repo.changelog.read(cnode)
416 415 bcache[r] = name = self.repo.ui.shortuser(cl[1])
417 416
418 417 if last != cnode:
419 418 parity = 1 - parity
420 419 last = cnode
421 420
422 421 yield {"parity": parity,
423 422 "node": hex(cnode),
424 423 "rev": r,
425 424 "author": name,
426 425 "file": f,
427 426 "line": l}
428 427
429 428 yield self.t("fileannotate",
430 429 file=f,
431 430 filenode=node,
432 431 annotate=annotate,
433 432 path=_up(f),
434 433 rev=changerev,
435 434 node=hex(cn),
436 435 manifest=hex(mfn),
437 436 author=cs[1],
438 437 date=cs[2],
439 438 rename=self.renamelink(fl, n),
440 439 parent=self.siblings(fl.parents(n), fl.rev, file=f),
441 440 child=self.siblings(fl.children(n), fl.rev, file=f),
442 441 permissions=self.repo.manifest.readflags(mfn)[f])
443 442
444 443 def manifest(self, mnode, path):
445 444 man = self.repo.manifest
446 445 mn = man.lookup(mnode)
447 446 mnode = hex(mn)
448 447 mf = man.read(mn)
449 448 rev = man.rev(mn)
450 449 changerev = man.linkrev(mn)
451 450 node = self.repo.changelog.node(changerev)
452 451 mff = man.readflags(mn)
453 452
454 453 files = {}
455 454
456 455 p = path[1:]
457 456 if p and p[-1] != "/":
458 457 p += "/"
459 458 l = len(p)
460 459
461 460 for f,n in mf.items():
462 461 if f[:l] != p:
463 462 continue
464 463 remain = f[l:]
465 464 if "/" in remain:
466 465 short = remain[:remain.find("/") + 1] # bleah
467 466 files[short] = (f, None)
468 467 else:
469 468 short = os.path.basename(remain)
470 469 files[short] = (f, n)
471 470
472 471 def filelist(**map):
473 472 parity = 0
474 473 fl = files.keys()
475 474 fl.sort()
476 475 for f in fl:
477 476 full, fnode = files[f]
478 477 if not fnode:
479 478 continue
480 479
481 480 yield {"file": full,
482 481 "manifest": mnode,
483 482 "filenode": hex(fnode),
484 483 "parity": parity,
485 484 "basename": f,
486 485 "permissions": mff[full]}
487 486 parity = 1 - parity
488 487
489 488 def dirlist(**map):
490 489 parity = 0
491 490 fl = files.keys()
492 491 fl.sort()
493 492 for f in fl:
494 493 full, fnode = files[f]
495 494 if fnode:
496 495 continue
497 496
498 497 yield {"parity": parity,
499 498 "path": os.path.join(path, f),
500 499 "manifest": mnode,
501 500 "basename": f[:-1]}
502 501 parity = 1 - parity
503 502
504 503 yield self.t("manifest",
505 504 manifest=mnode,
506 505 rev=rev,
507 506 node=hex(node),
508 507 path=path,
509 508 up=_up(path),
510 509 fentries=filelist,
511 510 dentries=dirlist,
512 511 archives=self.archivelist(hex(node)))
513 512
514 513 def tags(self):
515 514 cl = self.repo.changelog
516 515 mf = cl.read(cl.tip())[0]
517 516
518 517 i = self.repo.tagslist()
519 518 i.reverse()
520 519
521 520 def entries(notip=False, **map):
522 521 parity = 0
523 522 for k,n in i:
524 523 if notip and k == "tip": continue
525 524 yield {"parity": parity,
526 525 "tag": k,
527 526 "tagmanifest": hex(cl.read(n)[0]),
528 527 "date": cl.read(n)[2],
529 528 "node": hex(n)}
530 529 parity = 1 - parity
531 530
532 531 yield self.t("tags",
533 532 manifest=hex(mf),
534 533 entries=lambda **x: entries(False, **x),
535 534 entriesnotip=lambda **x: entries(True, **x))
536 535
537 536 def summary(self):
538 537 cl = self.repo.changelog
539 538 mf = cl.read(cl.tip())[0]
540 539
541 540 i = self.repo.tagslist()
542 541 i.reverse()
543 542
544 543 def tagentries(**map):
545 544 parity = 0
546 545 count = 0
547 546 for k,n in i:
548 547 if k == "tip": # skip tip
549 548 continue;
550 549
551 550 count += 1
552 551 if count > 10: # limit to 10 tags
553 552 break;
554 553
555 554 c = cl.read(n)
556 555 m = c[0]
557 556 t = c[2]
558 557
559 558 yield self.t("tagentry",
560 559 parity = parity,
561 560 tag = k,
562 561 node = hex(n),
563 562 date = t,
564 563 tagmanifest = hex(m))
565 564 parity = 1 - parity
566 565
567 566 def changelist(**map):
568 567 parity = 0
569 568 cl = self.repo.changelog
570 569 l = [] # build a list in forward order for efficiency
571 570 for i in range(start, end):
572 571 n = cl.node(i)
573 572 changes = cl.read(n)
574 573 hn = hex(n)
575 574 t = changes[2]
576 575
577 576 l.insert(0, self.t(
578 577 'shortlogentry',
579 578 parity = parity,
580 579 author = changes[1],
581 580 manifest = hex(changes[0]),
582 581 desc = changes[4],
583 582 date = t,
584 583 rev = i,
585 584 node = hn))
586 585 parity = 1 - parity
587 586
588 587 yield l
589 588
590 589 cl = self.repo.changelog
591 590 mf = cl.read(cl.tip())[0]
592 591 count = cl.count()
593 592 start = max(0, count - self.maxchanges)
594 593 end = min(count, start + self.maxchanges)
595 594
596 595 yield self.t("summary",
597 596 desc = self.repo.ui.config("web", "description", "unknown"),
598 597 owner = (self.repo.ui.config("ui", "username") or # preferred
599 598 self.repo.ui.config("web", "contact") or # deprecated
600 599 self.repo.ui.config("web", "author", "unknown")), # also
601 600 lastchange = (0, 0), # FIXME
602 601 manifest = hex(mf),
603 602 tags = tagentries,
604 603 shortlog = changelist)
605 604
606 605 def filediff(self, file, changeset):
607 606 cl = self.repo.changelog
608 607 n = self.repo.lookup(changeset)
609 608 changeset = hex(n)
610 609 p1 = cl.parents(n)[0]
611 610 cs = cl.read(n)
612 611 mf = self.repo.manifest.read(cs[0])
613 612
614 613 def diff(**map):
615 614 yield self.diff(p1, n, [file])
616 615
617 616 yield self.t("filediff",
618 617 file=file,
619 618 filenode=hex(mf.get(file, nullid)),
620 619 node=changeset,
621 620 rev=self.repo.changelog.rev(n),
622 621 parent=self.siblings(cl.parents(n), cl.rev),
623 622 child=self.siblings(cl.children(n), cl.rev),
624 623 diff=diff)
625 624
626 625 archive_specs = {
627 626 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
628 627 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
629 628 'zip': ('application/zip', 'zip', '.zip', None),
630 629 }
631 630
632 631 def archive(self, req, cnode, type_):
633 632 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
634 633 name = "%s-%s" % (reponame, short(cnode))
635 634 mimetype, artype, extension, encoding = self.archive_specs[type_]
636 635 headers = [('Content-type', mimetype),
637 636 ('Content-disposition', 'attachment; filename=%s%s' %
638 637 (name, extension))]
639 638 if encoding:
640 639 headers.append(('Content-encoding', encoding))
641 640 req.header(headers)
642 641 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
643 642
644 643 # add tags to things
645 644 # tags -> list of changesets corresponding to tags
646 645 # find tag, changeset, file
647 646
648 647 def cleanpath(self, path):
649 648 p = util.normpath(path)
650 649 if p[:2] == "..":
651 650 raise Exception("suspicious path")
652 651 return p
653 652
654 def run(self, req=hgrequest()):
653 def run(self, req):
655 654 def header(**map):
656 yield self.t("header", **map)
655 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
656 msg = mimetools.Message(header_file, 0)
657 req.header(msg.items())
658 yield header_file.read()
657 659
658 660 def footer(**map):
659 661 yield self.t("footer",
660 662 motd=self.repo.ui.config("web", "motd", ""),
661 663 **map)
662 664
663 665 def expand_form(form):
664 666 shortcuts = {
665 667 'cl': [('cmd', ['changelog']), ('rev', None)],
666 668 'cs': [('cmd', ['changeset']), ('node', None)],
667 669 'f': [('cmd', ['file']), ('filenode', None)],
668 670 'fl': [('cmd', ['filelog']), ('filenode', None)],
669 671 'fd': [('cmd', ['filediff']), ('node', None)],
670 672 'fa': [('cmd', ['annotate']), ('filenode', None)],
671 673 'mf': [('cmd', ['manifest']), ('manifest', None)],
672 674 'ca': [('cmd', ['archive']), ('node', None)],
673 675 'tags': [('cmd', ['tags'])],
674 676 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
675 677 'static': [('cmd', ['static']), ('file', None)]
676 678 }
677 679
678 680 for k in shortcuts.iterkeys():
679 681 if form.has_key(k):
680 682 for name, value in shortcuts[k]:
681 683 if value is None:
682 684 value = form[k]
683 685 form[name] = value
684 686 del form[k]
685 687
686 688 self.refresh()
687 689
688 690 expand_form(req.form)
689 691
690 692 m = os.path.join(self.templatepath, "map")
691 693 style = self.repo.ui.config("web", "style", "")
692 694 if req.form.has_key('style'):
693 695 style = req.form['style'][0]
694 696 if style:
695 697 b = os.path.basename("map-" + style)
696 698 p = os.path.join(self.templatepath, b)
697 699 if os.path.isfile(p):
698 700 m = p
699 701
700 702 port = req.env["SERVER_PORT"]
701 703 port = port != "80" and (":" + port) or ""
702 704 uri = req.env["REQUEST_URI"]
703 705 if "?" in uri:
704 706 uri = uri.split("?")[0]
705 707 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
706 708 if not self.reponame:
707 709 self.reponame = (self.repo.ui.config("web", "name")
708 710 or uri.strip('/') or self.repo.root)
709 711
710 712 self.t = templater.templater(m, templater.common_filters,
711 713 defaults={"url": url,
712 714 "repo": self.reponame,
713 715 "header": header,
714 716 "footer": footer,
715 717 })
716 718
717 719 if not req.form.has_key('cmd'):
718 720 req.form['cmd'] = [self.t.cache['default'],]
719 721
720 722 cmd = req.form['cmd'][0]
721 723
722 724 method = getattr(self, 'do_' + cmd, None)
723 725 if method:
724 726 method(req)
725 727 else:
726 728 req.write(self.t("error"))
727 req.done()
728 729
729 730 def do_changelog(self, req):
730 731 hi = self.repo.changelog.count() - 1
731 732 if req.form.has_key('rev'):
732 733 hi = req.form['rev'][0]
733 734 try:
734 735 hi = self.repo.changelog.rev(self.repo.lookup(hi))
735 736 except hg.RepoError:
736 737 req.write(self.search(hi)) # XXX redirect to 404 page?
737 738 return
738 739
739 740 req.write(self.changelog(hi))
740 741
741 742 def do_changeset(self, req):
742 743 req.write(self.changeset(req.form['node'][0]))
743 744
744 745 def do_manifest(self, req):
745 746 req.write(self.manifest(req.form['manifest'][0],
746 747 self.cleanpath(req.form['path'][0])))
747 748
748 749 def do_tags(self, req):
749 750 req.write(self.tags())
750 751
751 752 def do_summary(self, req):
752 753 req.write(self.summary())
753 754
754 755 def do_filediff(self, req):
755 756 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
756 757 req.form['node'][0]))
757 758
758 759 def do_file(self, req):
759 760 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
760 761 req.form['filenode'][0]))
761 762
762 763 def do_annotate(self, req):
763 764 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
764 765 req.form['filenode'][0]))
765 766
766 767 def do_filelog(self, req):
767 768 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
768 769 req.form['filenode'][0]))
769 770
770 771 def do_heads(self, req):
771 772 resp = " ".join(map(hex, self.repo.heads())) + "\n"
772 773 req.httphdr("application/mercurial-0.1", length=len(resp))
773 774 req.write(resp)
774 775
775 776 def do_branches(self, req):
776 777 nodes = []
777 778 if req.form.has_key('nodes'):
778 779 nodes = map(bin, req.form['nodes'][0].split(" "))
779 780 resp = cStringIO.StringIO()
780 781 for b in self.repo.branches(nodes):
781 782 resp.write(" ".join(map(hex, b)) + "\n")
782 783 resp = resp.getvalue()
783 784 req.httphdr("application/mercurial-0.1", length=len(resp))
784 785 req.write(resp)
785 786
786 787 def do_between(self, req):
787 788 nodes = []
788 789 if req.form.has_key('pairs'):
789 790 pairs = [map(bin, p.split("-"))
790 791 for p in req.form['pairs'][0].split(" ")]
791 792 resp = cStringIO.StringIO()
792 793 for b in self.repo.between(pairs):
793 794 resp.write(" ".join(map(hex, b)) + "\n")
794 795 resp = resp.getvalue()
795 796 req.httphdr("application/mercurial-0.1", length=len(resp))
796 797 req.write(resp)
797 798
798 799 def do_changegroup(self, req):
799 800 req.httphdr("application/mercurial-0.1")
800 801 nodes = []
801 802 if not self.allowpull:
802 803 return
803 804
804 805 if req.form.has_key('roots'):
805 806 nodes = map(bin, req.form['roots'][0].split(" "))
806 807
807 808 z = zlib.compressobj()
808 809 f = self.repo.changegroup(nodes, 'serve')
809 810 while 1:
810 811 chunk = f.read(4096)
811 812 if not chunk:
812 813 break
813 814 req.write(z.compress(chunk))
814 815
815 816 req.write(z.flush())
816 817
817 818 def do_archive(self, req):
818 819 changeset = self.repo.lookup(req.form['node'][0])
819 820 type_ = req.form['type'][0]
820 821 allowed = self.repo.ui.configlist("web", "allow_archive")
821 822 if (type_ in self.archives and (type_ in allowed or
822 823 self.repo.ui.configbool("web", "allow" + type_, False))):
823 824 self.archive(req, changeset, type_)
824 825 return
825 826
826 827 req.write(self.t("error"))
827 828
828 829 def do_static(self, req):
829 830 fname = req.form['file'][0]
830 831 static = self.repo.ui.config("web", "static",
831 832 os.path.join(self.templatepath,
832 833 "static"))
833 req.write(staticfile(static, fname)
834 req.write(staticfile(static, fname, req)
834 835 or self.t("error", error="%r not found" % fname))
835 836
836 837 def do_capabilities(self, req):
837 838 resp = 'unbundle'
838 839 req.httphdr("application/mercurial-0.1", length=len(resp))
839 840 req.write(resp)
840 841
841 842 def check_perm(self, req, op, default):
842 843 '''check permission for operation based on user auth.
843 844 return true if op allowed, else false.
844 845 default is policy to use if no config given.'''
845 846
846 847 user = req.env.get('REMOTE_USER')
847 848
848 849 deny = self.repo.ui.configlist('web', 'deny_' + op)
849 850 if deny and (not user or deny == ['*'] or user in deny):
850 851 return False
851 852
852 853 allow = self.repo.ui.configlist('web', 'allow_' + op)
853 854 return (allow and (allow == ['*'] or user in allow)) or default
854 855
855 856 def do_unbundle(self, req):
856 857 def bail(response, headers={}):
857 858 length = int(req.env['CONTENT_LENGTH'])
858 859 for s in util.filechunkiter(req, limit=length):
859 860 # drain incoming bundle, else client will not see
860 861 # response when run outside cgi script
861 862 pass
862 863 req.httphdr("application/mercurial-0.1", headers=headers)
863 864 req.write('0\n')
864 865 req.write(response)
865 866
866 867 # require ssl by default, auth info cannot be sniffed and
867 868 # replayed
868 869 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
869 870 if ssl_req and not req.env.get('HTTPS'):
870 871 bail(_('ssl required\n'))
871 872 return
872 873
873 874 # do not allow push unless explicitly allowed
874 875 if not self.check_perm(req, 'push', False):
875 876 bail(_('push not authorized\n'),
876 877 headers={'status': '401 Unauthorized'})
877 878 return
878 879
879 880 req.httphdr("application/mercurial-0.1")
880 881
881 882 their_heads = req.form['heads'][0].split(' ')
882 883
883 884 def check_heads():
884 885 heads = map(hex, self.repo.heads())
885 886 return their_heads == [hex('force')] or their_heads == heads
886 887
887 888 # fail early if possible
888 889 if not check_heads():
889 890 bail(_('unsynced changes\n'))
890 891 return
891 892
892 893 # do not lock repo until all changegroup data is
893 894 # streamed. save to temporary file.
894 895
895 896 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
896 897 fp = os.fdopen(fd, 'wb+')
897 898 try:
898 899 length = int(req.env['CONTENT_LENGTH'])
899 900 for s in util.filechunkiter(req, limit=length):
900 901 fp.write(s)
901 902
902 903 lock = self.repo.lock()
903 904 try:
904 905 if not check_heads():
905 906 req.write('0\n')
906 907 req.write(_('unsynced changes\n'))
907 908 return
908 909
909 910 fp.seek(0)
910 911
911 912 # send addchangegroup output to client
912 913
913 914 old_stdout = sys.stdout
914 915 sys.stdout = cStringIO.StringIO()
915 916
916 917 try:
917 918 ret = self.repo.addchangegroup(fp, 'serve')
918 919 req.write('%d\n' % ret)
919 920 req.write(sys.stdout.getvalue())
920 921 finally:
921 922 sys.stdout = old_stdout
922 923 finally:
923 924 lock.release()
924 925 finally:
925 926 fp.close()
926 927 os.unlink(tempname)
@@ -1,155 +1,157 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 from mercurial.demandload import demandload
11 demandload(globals(), "ConfigParser")
11 demandload(globals(), "ConfigParser mimetools cStringIO")
12 12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 13 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
14 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 14 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 15 from mercurial.i18n import gettext as _
17 16
18 17 # This is a stopgap
19 18 class hgwebdir(object):
20 19 def __init__(self, config):
21 20 def cleannames(items):
22 21 return [(name.strip(os.sep), path) for name, path in items]
23 22
24 23 self.motd = ""
25 24 self.repos_sorted = ('name', False)
26 25 if isinstance(config, (list, tuple)):
27 26 self.repos = cleannames(config)
28 27 self.repos_sorted = ('', False)
29 28 elif isinstance(config, dict):
30 29 self.repos = cleannames(config.items())
31 30 self.repos.sort()
32 31 else:
33 32 cp = ConfigParser.SafeConfigParser()
34 33 cp.read(config)
35 34 self.repos = []
36 35 if cp.has_section('web') and cp.has_option('web', 'motd'):
37 36 self.motd = cp.get('web', 'motd')
38 37 if cp.has_section('paths'):
39 38 self.repos.extend(cleannames(cp.items('paths')))
40 39 if cp.has_section('collections'):
41 40 for prefix, root in cp.items('collections'):
42 41 for path in util.walkrepos(root):
43 42 repo = os.path.normpath(path)
44 43 name = repo
45 44 if name.startswith(prefix):
46 45 name = name[len(prefix):]
47 46 self.repos.append((name.lstrip(os.sep), repo))
48 47 self.repos.sort()
49 48
50 def run(self, req=hgrequest()):
49 def run(self, req):
51 50 def header(**map):
52 yield tmpl("header", **map)
51 header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
52 msg = mimetools.Message(header_file, 0)
53 req.header(msg.items())
54 yield header_file.read()
53 55
54 56 def footer(**map):
55 57 yield tmpl("footer", motd=self.motd, **map)
56 58
57 59 m = os.path.join(templater.templatepath(), "map")
58 60 tmpl = templater.templater(m, templater.common_filters,
59 61 defaults={"header": header,
60 62 "footer": footer})
61 63
62 64 def archivelist(ui, nodeid, url):
63 65 allowed = ui.configlist("web", "allow_archive")
64 66 for i in ['zip', 'gz', 'bz2']:
65 67 if i in allowed or ui.configbool("web", "allow" + i):
66 68 yield {"type" : i, "node": nodeid, "url": url}
67 69
68 70 def entries(sortcolumn="", descending=False, **map):
69 71 rows = []
70 72 parity = 0
71 73 for name, path in self.repos:
72 74 u = ui.ui()
73 75 try:
74 76 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
75 77 except IOError:
76 78 pass
77 79 get = u.config
78 80
79 81 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
80 82 .replace("//", "/"))
81 83
82 84 # update time with local timezone
83 85 try:
84 86 d = (get_mtime(path), util.makedate()[1])
85 87 except OSError:
86 88 continue
87 89
88 90 contact = (get("ui", "username") or # preferred
89 91 get("web", "contact") or # deprecated
90 92 get("web", "author", "")) # also
91 93 description = get("web", "description", "")
92 94 name = get("web", "name", name)
93 95 row = dict(contact=contact or "unknown",
94 96 contact_sort=contact.upper() or "unknown",
95 97 name=name,
96 98 name_sort=name,
97 99 url=url,
98 100 description=description or "unknown",
99 101 description_sort=description.upper() or "unknown",
100 102 lastchange=d,
101 103 lastchange_sort=d[1]-d[0],
102 104 archives=archivelist(u, "tip", url))
103 105 if (not sortcolumn
104 106 or (sortcolumn, descending) == self.repos_sorted):
105 107 # fast path for unsorted output
106 108 row['parity'] = parity
107 109 parity = 1 - parity
108 110 yield row
109 111 else:
110 112 rows.append((row["%s_sort" % sortcolumn], row))
111 113 if rows:
112 114 rows.sort()
113 115 if descending:
114 116 rows.reverse()
115 117 for key, row in rows:
116 118 row['parity'] = parity
117 119 parity = 1 - parity
118 120 yield row
119 121
120 122 virtual = req.env.get("PATH_INFO", "").strip('/')
121 123 if virtual:
122 124 real = dict(self.repos).get(virtual)
123 125 if real:
124 126 try:
125 127 hgweb(real).run(req)
126 128 except IOError, inst:
127 129 req.write(tmpl("error", error=inst.strerror))
128 130 except hg.RepoError, inst:
129 131 req.write(tmpl("error", error=str(inst)))
130 132 else:
131 133 req.write(tmpl("notfound", repo=virtual))
132 134 else:
133 135 if req.form.has_key('static'):
134 136 static = os.path.join(templater.templatepath(), "static")
135 137 fname = req.form['static'][0]
136 req.write(staticfile(static, fname)
138 req.write(staticfile(static, fname, req)
137 139 or tmpl("error", error="%r not found" % fname))
138 140 else:
139 141 sortable = ["name", "description", "contact", "lastchange"]
140 142 sortcolumn, descending = self.repos_sorted
141 143 if req.form.has_key('sort'):
142 144 sortcolumn = req.form['sort'][0]
143 145 descending = sortcolumn.startswith('-')
144 146 if descending:
145 147 sortcolumn = sortcolumn[1:]
146 148 if sortcolumn not in sortable:
147 149 sortcolumn = ""
148 150
149 151 sort = [("sort_%s" % column,
150 152 "%s%s" % ((not descending and column == sortcolumn)
151 153 and "-" or "", column))
152 154 for column in sortable]
153 155 req.write(tmpl("index", entries=entries,
154 156 sortcolumn=sortcolumn, descending=descending,
155 157 **dict(sort)))
@@ -1,62 +1,90 b''
1 1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from mercurial.demandload import demandload
10 10 demandload(globals(), "socket sys cgi os errno")
11 11 from mercurial.i18n import gettext as _
12 12
13 class hgrequest(object):
14 def __init__(self, inp=None, out=None, env=None):
15 self.inp = inp or sys.stdin
16 self.out = out or sys.stdout
17 self.env = env or os.environ
13 class wsgiapplication(object):
14 def __init__(self, destmaker):
15 self.destmaker = destmaker
16
17 def __call__(self, wsgienv, start_response):
18 return _wsgirequest(self.destmaker(), wsgienv, start_response)
19
20 class _wsgioutputfile(object):
21 def __init__(self, request):
22 self.request = request
23
24 def write(self, data):
25 self.request.write(data)
26 def writelines(self, lines):
27 for line in lines:
28 self.write(line)
29 def flush(self):
30 return None
31 def close(self):
32 return None
33
34 class _wsgirequest(object):
35 def __init__(self, destination, wsgienv, start_response):
36 version = wsgienv['wsgi.version']
37 if (version < (1,0)) or (version >= (2, 0)):
38 raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \
39 % version)
40 self.inp = wsgienv['wsgi.input']
41 self.out = _wsgioutputfile(self)
42 self.server_write = None
43 self.err = wsgienv['wsgi.errors']
44 self.threaded = wsgienv['wsgi.multithread']
45 self.multiprocess = wsgienv['wsgi.multiprocess']
46 self.run_once = wsgienv['wsgi.run_once']
47 self.env = wsgienv
18 48 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
19 self.will_close = True
49 self.start_response = start_response
50 self.headers = []
51 destination.run(self)
52
53 def __iter__(self):
54 return iter([])
20 55
21 56 def read(self, count=-1):
22 57 return self.inp.read(count)
23 58
24 59 def write(self, *things):
25 60 for thing in things:
26 61 if hasattr(thing, "__iter__"):
27 62 for part in thing:
28 63 self.write(part)
29 64 else:
65 thing = str(thing)
66 if self.server_write is None:
67 if not self.headers:
68 raise RuntimeError("request.write called before headers sent (%s)." % thing)
69 self.server_write = self.start_response('200 Script output follows',
70 self.headers)
71 self.start_response = None
72 self.headers = None
30 73 try:
31 self.out.write(str(thing))
74 self.server_write(thing)
32 75 except socket.error, inst:
33 76 if inst[0] != errno.ECONNRESET:
34 77 raise
35 78
36 def done(self):
37 if self.will_close:
38 self.inp.close()
39 self.out.close()
40 else:
41 self.out.flush()
42
43 79 def header(self, headers=[('Content-type','text/html')]):
44 for header in headers:
45 self.out.write("%s: %s\r\n" % header)
46 self.out.write("\r\n")
80 self.headers.extend(headers)
47 81
48 82 def httphdr(self, type, filename=None, length=0, headers={}):
49 83 headers = headers.items()
50 84 headers.append(('Content-type', type))
51 85 if filename:
52 86 headers.append(('Content-disposition', 'attachment; filename=%s' %
53 87 filename))
54 # we do not yet support http 1.1 chunked transfer, so we have
55 # to force connection to close if content-length not known
56 88 if length:
57 89 headers.append(('Content-length', str(length)))
58 self.will_close = False
59 else:
60 headers.append(('Connection', 'close'))
61 self.will_close = True
62 90 self.header(headers)
@@ -1,152 +1,218 b''
1 1 # hgweb/server.py - The standalone hg web server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from mercurial.demandload import demandload
10 10 import os, sys, errno
11 11 demandload(globals(), "urllib BaseHTTPServer socket SocketServer")
12 12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:hgrequest")
13 demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication")
14 14 from mercurial.i18n import gettext as _
15 15
16 16 def _splitURI(uri):
17 17 """ Return path and query splited from uri
18 18
19 19 Just like CGI environment, the path is unquoted, the query is
20 20 not.
21 21 """
22 22 if '?' in uri:
23 23 path, query = uri.split('?', 1)
24 24 else:
25 25 path, query = uri, ''
26 26 return urllib.unquote(path), query
27 27
28 class _error_logger(object):
29 def __init__(self, handler):
30 self.handler = handler
31 def flush(self):
32 pass
33 def write(str):
34 self.writelines(str.split('\n'))
35 def writelines(seq):
36 for msg in seq:
37 self.handler.log_error("HG error: %s", msg)
38
28 39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
29 40 def __init__(self, *args, **kargs):
30 41 self.protocol_version = 'HTTP/1.1'
31 42 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
32 43
33 44 def log_error(self, format, *args):
34 45 errorlog = self.server.errorlog
35 46 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
36 47 self.log_date_time_string(),
37 48 format % args))
38 49
39 50 def log_message(self, format, *args):
40 51 accesslog = self.server.accesslog
41 52 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
42 53 self.log_date_time_string(),
43 54 format % args))
44 55
45 56 def do_POST(self):
46 57 try:
47 58 self.do_hgweb()
48 59 except socket.error, inst:
49 60 if inst[0] != errno.EPIPE:
50 61 raise
51 62
52 63 def do_GET(self):
53 64 self.do_POST()
54 65
55 66 def do_hgweb(self):
56 67 path_info, query = _splitURI(self.path)
57 68
58 69 env = {}
59 70 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
60 71 env['REQUEST_METHOD'] = self.command
61 72 env['SERVER_NAME'] = self.server.server_name
62 73 env['SERVER_PORT'] = str(self.server.server_port)
63 74 env['REQUEST_URI'] = "/"
64 75 env['PATH_INFO'] = path_info
65 76 if query:
66 77 env['QUERY_STRING'] = query
67 78 host = self.address_string()
68 79 if host != self.client_address[0]:
69 80 env['REMOTE_HOST'] = host
70 81 env['REMOTE_ADDR'] = self.client_address[0]
71 82
72 83 if self.headers.typeheader is None:
73 84 env['CONTENT_TYPE'] = self.headers.type
74 85 else:
75 86 env['CONTENT_TYPE'] = self.headers.typeheader
76 87 length = self.headers.getheader('content-length')
77 88 if length:
78 89 env['CONTENT_LENGTH'] = length
79 accept = []
80 for line in self.headers.getallmatchingheaders('accept'):
81 if line[:1] in "\t\n\r ":
82 accept.append(line.strip())
83 else:
84 accept = accept + line[7:].split(',')
85 env['HTTP_ACCEPT'] = ','.join(accept)
90 for header in [h for h in self.headers.keys() \
91 if h not in ('content-type', 'content-length')]:
92 hkey = 'HTTP_' + header.replace('-', '_').upper()
93 hval = self.headers.getheader(header)
94 hval = hval.replace('\n', '').strip()
95 if hval:
96 env[hkey] = hval
97 env['SERVER_PROTOCOL'] = self.request_version
98 env['wsgi.version'] = (1, 0)
99 env['wsgi.url_scheme'] = 'http'
100 env['wsgi.input'] = self.rfile
101 env['wsgi.errors'] = _error_logger(self)
102 env['wsgi.multithread'] = isinstance(self.server,
103 SocketServer.ThreadingMixIn)
104 env['wsgi.multiprocess'] = isinstance(self.server,
105 SocketServer.ForkingMixIn)
106 env['wsgi.run_once'] = 0
107
108 self.close_connection = True
109 self.saved_status = None
110 self.saved_headers = []
111 self.sent_headers = False
112 self.length = None
113 req = self.server.reqmaker(env, self._start_response)
114 for data in req:
115 if data:
116 self._write(data)
86 117
87 req = hgrequest(self.rfile, self.wfile, env)
88 self.send_response(200, "Script output follows")
89 self.close_connection = self.server.make_and_run_handler(req)
118 def send_headers(self):
119 if not self.saved_status:
120 raise AssertionError("Sending headers before start_response() called")
121 saved_status = self.saved_status.split(None, 1)
122 saved_status[0] = int(saved_status[0])
123 self.send_response(*saved_status)
124 should_close = True
125 for h in self.saved_headers:
126 self.send_header(*h)
127 if h[0].lower() == 'content-length':
128 should_close = False
129 self.length = int(h[1])
130 if should_close:
131 self.send_header('Connection', 'close')
132 self.close_connection = should_close
133 self.end_headers()
134 self.sent_headers = True
135
136 def _start_response(self, http_status, headers, exc_info=None):
137 code, msg = http_status.split(None, 1)
138 code = int(code)
139 self.saved_status = http_status
140 bad_headers = ('connection', 'transfer-encoding')
141 self.saved_headers = [ h for h in headers \
142 if h[0].lower() not in bad_headers ]
143 return self._write
144
145 def _write(self, data):
146 if not self.saved_status:
147 raise AssertionError("data written before start_response() called")
148 elif not self.sent_headers:
149 self.send_headers()
150 if self.length is not None:
151 if len(data) > self.length:
152 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
153 self.length = self.length - len(data)
154 self.wfile.write(data)
155 self.wfile.flush()
90 156
91 157 def create_server(ui, repo):
92 158 use_threads = True
93 159
94 160 def openlog(opt, default):
95 161 if opt and opt != '-':
96 162 return open(opt, 'w')
97 163 return default
98 164
99 165 address = ui.config("web", "address", "")
100 166 port = int(ui.config("web", "port", 8000))
101 167 use_ipv6 = ui.configbool("web", "ipv6")
102 168 webdir_conf = ui.config("web", "webdir_conf")
103 169 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
104 170 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
105 171
106 172 if use_threads:
107 173 try:
108 174 from threading import activeCount
109 175 except ImportError:
110 176 use_threads = False
111 177
112 178 if use_threads:
113 179 _mixin = SocketServer.ThreadingMixIn
114 180 else:
115 181 if hasattr(os, "fork"):
116 182 _mixin = SocketServer.ForkingMixIn
117 183 else:
118 184 class _mixin: pass
119 185
120 186 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
121 187 def __init__(self, *args, **kargs):
122 188 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
123 189 self.accesslog = accesslog
124 190 self.errorlog = errorlog
125 191 self.repo = repo
126 192 self.webdir_conf = webdir_conf
127 193 self.webdirmaker = hgwebdir
128 194 self.repoviewmaker = hgweb
195 self.reqmaker = wsgiapplication(self.make_handler)
129 196
130 def make_and_run_handler(self, req):
197 def make_handler(self):
131 198 if self.webdir_conf:
132 199 hgwebobj = self.webdirmaker(self.webdir_conf)
133 200 elif self.repo is not None:
134 201 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
135 202 repo.origroot))
136 203 else:
137 204 raise hg.RepoError(_('no repo found'))
138 hgwebobj.run(req)
139 return req.will_close
205 return hgwebobj
140 206
141 207 class IPv6HTTPServer(MercurialHTTPServer):
142 208 address_family = getattr(socket, 'AF_INET6', None)
143 209
144 210 def __init__(self, *args, **kwargs):
145 211 if self.address_family is None:
146 212 raise hg.RepoError(_('IPv6 not available on this system'))
147 super(IPv6HTTPServer, self).__init__(*args, **kargs)
213 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
148 214
149 215 if use_ipv6:
150 216 return IPv6HTTPServer((address, port), _hgwebhandler)
151 217 else:
152 218 return MercurialHTTPServer((address, port), _hgwebhandler)
@@ -1,519 +1,524 b''
1 1 # templater.py - template expansion for output
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from i18n import gettext as _
10 10 from node import *
11 11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
12 12
13 13 esctable = {
14 14 '\\': '\\',
15 15 'r': '\r',
16 16 't': '\t',
17 17 'n': '\n',
18 18 'v': '\v',
19 19 }
20 20
21 21 def parsestring(s, quoted=True):
22 22 '''parse a string using simple c-like syntax.
23 23 string must be in quotes if quoted is True.'''
24 24 fp = cStringIO.StringIO()
25 25 if quoted:
26 26 first = s[0]
27 27 if len(s) < 2: raise SyntaxError(_('string too short'))
28 28 if first not in "'\"": raise SyntaxError(_('invalid quote'))
29 29 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
30 30 s = s[1:-1]
31 31 escape = False
32 32 for c in s:
33 33 if escape:
34 34 fp.write(esctable.get(c, c))
35 35 escape = False
36 36 elif c == '\\': escape = True
37 37 elif quoted and c == first: raise SyntaxError(_('string ends early'))
38 38 else: fp.write(c)
39 39 if escape: raise SyntaxError(_('unterminated escape'))
40 40 return fp.getvalue()
41 41
42 42 class templater(object):
43 43 '''template expansion engine.
44 44
45 45 template expansion works like this. a map file contains key=value
46 46 pairs. if value is quoted, it is treated as string. otherwise, it
47 47 is treated as name of template file.
48 48
49 49 templater is asked to expand a key in map. it looks up key, and
50 50 looks for atrings like this: {foo}. it expands {foo} by looking up
51 51 foo in map, and substituting it. expansion is recursive: it stops
52 52 when there is no more {foo} to replace.
53 53
54 54 expansion also allows formatting and filtering.
55 55
56 56 format uses key to expand each item in list. syntax is
57 57 {key%format}.
58 58
59 59 filter uses function to transform value. syntax is
60 60 {key|filter1|filter2|...}.'''
61 61
62 62 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
63 63 '''set up template engine.
64 64 mapfile is name of file to read map definitions from.
65 65 filters is dict of functions. each transforms a value into another.
66 66 defaults is dict of default map definitions.'''
67 67 self.mapfile = mapfile or 'template'
68 68 self.cache = cache.copy()
69 69 self.map = {}
70 70 self.base = (mapfile and os.path.dirname(mapfile)) or ''
71 71 self.filters = filters
72 72 self.defaults = defaults
73 73
74 74 if not mapfile:
75 75 return
76 76 i = 0
77 77 for l in file(mapfile):
78 78 l = l.strip()
79 79 i += 1
80 80 if not l or l[0] in '#;': continue
81 81 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
82 82 if m:
83 83 key, val = m.groups()
84 84 if val[0] in "'\"":
85 85 try:
86 86 self.cache[key] = parsestring(val)
87 87 except SyntaxError, inst:
88 88 raise SyntaxError('%s:%s: %s' %
89 89 (mapfile, i, inst.args[0]))
90 90 else:
91 91 self.map[key] = os.path.join(self.base, val)
92 92 else:
93 93 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
94 94
95 95 def __contains__(self, key):
96 96 return key in self.cache
97 97
98 98 def __call__(self, t, **map):
99 99 '''perform expansion.
100 100 t is name of map element to expand.
101 101 map is added elements to use during expansion.'''
102 102 m = self.defaults.copy()
103 103 m.update(map)
104 104 try:
105 105 tmpl = self.cache[t]
106 106 except KeyError:
107 107 try:
108 108 tmpl = self.cache[t] = file(self.map[t]).read()
109 109 except IOError, inst:
110 110 raise IOError(inst.args[0], _('template file %s: %s') %
111 111 (self.map[t], inst.args[1]))
112 112 return self.template(tmpl, self.filters, **m)
113 113
114 114 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
115 115 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
116 116 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
117 117
118 118 def template(self, tmpl, filters={}, **map):
119 119 lm = map.copy()
120 120 while tmpl:
121 121 m = self.template_re.search(tmpl)
122 122 if m:
123 123 start, end = m.span(0)
124 124 s, e = tmpl[start], tmpl[end - 1]
125 125 key = m.group(1)
126 126 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
127 127 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
128 128 (s, e, key))
129 129 if start:
130 130 yield tmpl[:start]
131 131 v = map.get(key, "")
132 132 v = callable(v) and v(**map) or v
133 133
134 134 format = m.group(2)
135 135 fl = m.group(4)
136 136
137 137 if format:
138 138 q = v.__iter__
139 139 for i in q():
140 140 lm.update(i)
141 141 yield self(format[1:], **lm)
142 142
143 143 v = ""
144 144
145 145 elif fl:
146 146 for f in fl.split("|")[1:]:
147 147 v = filters[f](v)
148 148
149 149 yield v
150 150 tmpl = tmpl[end:]
151 151 else:
152 152 yield tmpl
153 153 break
154 154
155 155 agescales = [("second", 1),
156 156 ("minute", 60),
157 157 ("hour", 3600),
158 158 ("day", 3600 * 24),
159 159 ("week", 3600 * 24 * 7),
160 160 ("month", 3600 * 24 * 30),
161 161 ("year", 3600 * 24 * 365)]
162 162
163 163 agescales.reverse()
164 164
165 165 def age(date):
166 166 '''turn a (timestamp, tzoff) tuple into an age string.'''
167 167
168 168 def plural(t, c):
169 169 if c == 1:
170 170 return t
171 171 return t + "s"
172 172 def fmt(t, c):
173 173 return "%d %s" % (c, plural(t, c))
174 174
175 175 now = time.time()
176 176 then = date[0]
177 177 delta = max(1, int(now - then))
178 178
179 179 for t, s in agescales:
180 180 n = delta / s
181 181 if n >= 2 or s == 1:
182 182 return fmt(t, n)
183 183
184 184 def stringify(thing):
185 185 '''turn nested template iterator into string.'''
186 186 cs = cStringIO.StringIO()
187 187 def walk(things):
188 188 for t in things:
189 189 if hasattr(t, '__iter__'):
190 190 walk(t)
191 191 else:
192 192 cs.write(t)
193 193 walk(thing)
194 194 return cs.getvalue()
195 195
196 196 para_re = None
197 197 space_re = None
198 198
199 199 def fill(text, width):
200 200 '''fill many paragraphs.'''
201 201 global para_re, space_re
202 202 if para_re is None:
203 203 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
204 204 space_re = re.compile(r' +')
205 205
206 206 def findparas():
207 207 start = 0
208 208 while True:
209 209 m = para_re.search(text, start)
210 210 if not m:
211 211 w = len(text)
212 212 while w > start and text[w-1].isspace(): w -= 1
213 213 yield text[start:w], text[w:]
214 214 break
215 215 yield text[start:m.start(0)], m.group(1)
216 216 start = m.end(1)
217 217
218 218 fp = cStringIO.StringIO()
219 219 for para, rest in findparas():
220 220 fp.write(space_re.sub(' ', textwrap.fill(para, width)))
221 221 fp.write(rest)
222 222 return fp.getvalue()
223 223
224 224 def isodate(date):
225 225 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
226 226 return util.datestr(date, format='%Y-%m-%d %H:%M')
227 227
228 def hgdate(date):
229 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
230 return "%d %d" % date
231
228 232 def nl2br(text):
229 233 '''replace raw newlines with xhtml line breaks.'''
230 234 return text.replace('\n', '<br/>\n')
231 235
232 236 def obfuscate(text):
233 237 return ''.join(['&#%d;' % ord(c) for c in text])
234 238
235 239 def domain(author):
236 240 '''get domain of author, or empty string if none.'''
237 241 f = author.find('@')
238 242 if f == -1: return ''
239 243 author = author[f+1:]
240 244 f = author.find('>')
241 245 if f >= 0: author = author[:f]
242 246 return author
243 247
244 248 def email(author):
245 249 '''get email of author.'''
246 250 r = author.find('>')
247 251 if r == -1: r = None
248 252 return author[author.find('<')+1:r]
249 253
250 254 def person(author):
251 255 '''get name of author, or else username.'''
252 256 f = author.find('<')
253 257 if f == -1: return util.shortuser(author)
254 258 return author[:f].rstrip()
255 259
256 260 def shortdate(date):
257 261 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
258 262 return util.datestr(date, format='%Y-%m-%d', timezone=False)
259 263
260 264 def indent(text, prefix):
261 265 '''indent each non-empty line of text after first with prefix.'''
262 266 fp = cStringIO.StringIO()
263 267 lines = text.splitlines()
264 268 num_lines = len(lines)
265 269 for i in xrange(num_lines):
266 270 l = lines[i]
267 271 if i and l.strip(): fp.write(prefix)
268 272 fp.write(l)
269 273 if i < num_lines - 1 or text.endswith('\n'):
270 274 fp.write('\n')
271 275 return fp.getvalue()
272 276
273 277 common_filters = {
274 278 "addbreaks": nl2br,
275 279 "basename": os.path.basename,
276 280 "age": age,
277 281 "date": lambda x: util.datestr(x),
278 282 "domain": domain,
279 283 "email": email,
280 284 "escape": lambda x: cgi.escape(x, True),
281 285 "fill68": lambda x: fill(x, width=68),
282 286 "fill76": lambda x: fill(x, width=76),
283 287 "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
284 288 "tabindent": lambda x: indent(x, '\t'),
289 "hgdate": hgdate,
285 290 "isodate": isodate,
286 291 "obfuscate": obfuscate,
287 292 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
288 293 "person": person,
289 294 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
290 295 "short": lambda x: x[:12],
291 296 "shortdate": shortdate,
292 297 "stringify": stringify,
293 298 "strip": lambda x: x.strip(),
294 299 "urlescape": lambda x: urllib.quote(x),
295 300 "user": lambda x: util.shortuser(x),
296 301 }
297 302
298 303 def templatepath(name=None):
299 304 '''return location of template file or directory (if no name).
300 305 returns None if not found.'''
301 306
302 307 # executable version (py2exe) doesn't support __file__
303 308 if hasattr(sys, 'frozen'):
304 309 module = sys.executable
305 310 else:
306 311 module = __file__
307 312 for f in 'templates', '../templates':
308 313 fl = f.split('/')
309 314 if name: fl.append(name)
310 315 p = os.path.join(os.path.dirname(module), *fl)
311 316 if (name and os.path.exists(p)) or os.path.isdir(p):
312 317 return os.path.normpath(p)
313 318
314 319 class changeset_templater(object):
315 320 '''format changeset information.'''
316 321
317 322 def __init__(self, ui, repo, mapfile, dest=None):
318 323 self.t = templater(mapfile, common_filters,
319 324 cache={'parent': '{rev}:{node|short} ',
320 325 'manifest': '{rev}:{node|short}'})
321 326 self.ui = ui
322 327 self.dest = dest
323 328 self.repo = repo
324 329
325 330 def use_template(self, t):
326 331 '''set template string to use'''
327 332 self.t.cache['changeset'] = t
328 333
329 334 def write(self, thing, header=False):
330 335 '''write expanded template.
331 336 uses in-order recursive traverse of iterators.'''
332 337 dest = self.dest or self.ui
333 338 for t in thing:
334 339 if hasattr(t, '__iter__'):
335 340 self.write(t, header=header)
336 341 elif header:
337 342 dest.write_header(t)
338 343 else:
339 344 dest.write(t)
340 345
341 346 def write_header(self, thing):
342 347 self.write(thing, header=True)
343 348
344 349 def show(self, rev=0, changenode=None, brinfo=None, changes=None,
345 350 **props):
346 351 '''show a single changeset or file revision'''
347 352 log = self.repo.changelog
348 353 if changenode is None:
349 354 changenode = log.node(rev)
350 355 elif not rev:
351 356 rev = log.rev(changenode)
352 357 if changes is None:
353 358 changes = log.read(changenode)
354 359
355 360 def showlist(name, values, plural=None, **args):
356 361 '''expand set of values.
357 362 name is name of key in template map.
358 363 values is list of strings or dicts.
359 364 plural is plural of name, if not simply name + 's'.
360 365
361 366 expansion works like this, given name 'foo'.
362 367
363 368 if values is empty, expand 'no_foos'.
364 369
365 370 if 'foo' not in template map, return values as a string,
366 371 joined by space.
367 372
368 373 expand 'start_foos'.
369 374
370 375 for each value, expand 'foo'. if 'last_foo' in template
371 376 map, expand it instead of 'foo' for last key.
372 377
373 378 expand 'end_foos'.
374 379 '''
375 380 if plural: names = plural
376 381 else: names = name + 's'
377 382 if not values:
378 383 noname = 'no_' + names
379 384 if noname in self.t:
380 385 yield self.t(noname, **args)
381 386 return
382 387 if name not in self.t:
383 388 if isinstance(values[0], str):
384 389 yield ' '.join(values)
385 390 else:
386 391 for v in values:
387 392 yield dict(v, **args)
388 393 return
389 394 startname = 'start_' + names
390 395 if startname in self.t:
391 396 yield self.t(startname, **args)
392 397 vargs = args.copy()
393 398 def one(v, tag=name):
394 399 try:
395 400 vargs.update(v)
396 401 except (AttributeError, ValueError):
397 402 try:
398 403 for a, b in v:
399 404 vargs[a] = b
400 405 except ValueError:
401 406 vargs[name] = v
402 407 return self.t(tag, **vargs)
403 408 lastname = 'last_' + name
404 409 if lastname in self.t:
405 410 last = values.pop()
406 411 else:
407 412 last = None
408 413 for v in values:
409 414 yield one(v)
410 415 if last is not None:
411 416 yield one(last, tag=lastname)
412 417 endname = 'end_' + names
413 418 if endname in self.t:
414 419 yield self.t(endname, **args)
415 420
416 421 if brinfo:
417 422 def showbranches(**args):
418 423 if changenode in brinfo:
419 424 for x in showlist('branch', brinfo[changenode],
420 425 plural='branches', **args):
421 426 yield x
422 427 else:
423 428 showbranches = ''
424 429
425 430 if self.ui.debugflag:
426 431 def showmanifest(**args):
427 432 args = args.copy()
428 433 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
429 434 node=hex(changes[0])))
430 435 yield self.t('manifest', **args)
431 436 else:
432 437 showmanifest = ''
433 438
434 439 def showparents(**args):
435 440 parents = [[('rev', log.rev(p)), ('node', hex(p))]
436 441 for p in log.parents(changenode)
437 442 if self.ui.debugflag or p != nullid]
438 443 if (not self.ui.debugflag and len(parents) == 1 and
439 444 parents[0][0][1] == rev - 1):
440 445 return
441 446 for x in showlist('parent', parents, **args):
442 447 yield x
443 448
444 449 def showtags(**args):
445 450 for x in showlist('tag', self.repo.nodetags(changenode), **args):
446 451 yield x
447 452
448 453 if self.ui.debugflag:
449 454 files = self.repo.changes(log.parents(changenode)[0], changenode)
450 455 def showfiles(**args):
451 456 for x in showlist('file', files[0], **args): yield x
452 457 def showadds(**args):
453 458 for x in showlist('file_add', files[1], **args): yield x
454 459 def showdels(**args):
455 460 for x in showlist('file_del', files[2], **args): yield x
456 461 else:
457 462 def showfiles(**args):
458 463 for x in showlist('file', changes[3], **args): yield x
459 464 showadds = ''
460 465 showdels = ''
461 466
462 467 defprops = {
463 468 'author': changes[1],
464 469 'branches': showbranches,
465 470 'date': changes[2],
466 471 'desc': changes[4],
467 472 'file_adds': showadds,
468 473 'file_dels': showdels,
469 474 'files': showfiles,
470 475 'manifest': showmanifest,
471 476 'node': hex(changenode),
472 477 'parents': showparents,
473 478 'rev': rev,
474 479 'tags': showtags,
475 480 }
476 481 props = props.copy()
477 482 props.update(defprops)
478 483
479 484 try:
480 485 if self.ui.debugflag and 'header_debug' in self.t:
481 486 key = 'header_debug'
482 487 elif self.ui.quiet and 'header_quiet' in self.t:
483 488 key = 'header_quiet'
484 489 elif self.ui.verbose and 'header_verbose' in self.t:
485 490 key = 'header_verbose'
486 491 elif 'header' in self.t:
487 492 key = 'header'
488 493 else:
489 494 key = ''
490 495 if key:
491 496 self.write_header(self.t(key, **props))
492 497 if self.ui.debugflag and 'changeset_debug' in self.t:
493 498 key = 'changeset_debug'
494 499 elif self.ui.quiet and 'changeset_quiet' in self.t:
495 500 key = 'changeset_quiet'
496 501 elif self.ui.verbose and 'changeset_verbose' in self.t:
497 502 key = 'changeset_verbose'
498 503 else:
499 504 key = 'changeset'
500 505 self.write(self.t(key, **props))
501 506 except KeyError, inst:
502 507 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
503 508 inst.args[0]))
504 509 except SyntaxError, inst:
505 510 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
506 511
507 512 class stringio(object):
508 513 '''wrap cStringIO for use by changeset_templater.'''
509 514 def __init__(self):
510 515 self.fp = cStringIO.StringIO()
511 516
512 517 def write(self, *args):
513 518 for a in args:
514 519 self.fp.write(a)
515 520
516 521 write_header = write
517 522
518 523 def __getattr__(self, key):
519 524 return getattr(self.fp, key)
@@ -1,907 +1,950 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8
9 9 This contains helper routines that are independent of the SCM core and hide
10 10 platform-specific details from the core.
11 11 """
12 12
13 13 from i18n import gettext as _
14 14 from demandload import *
15 15 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
16 16 demandload(globals(), "os threading time")
17 17
18 18 class SignalInterrupt(Exception):
19 19 """Exception raised on SIGTERM and SIGHUP."""
20 20
21 21 def pipefilter(s, cmd):
22 22 '''filter string S through command CMD, returning its output'''
23 23 (pout, pin) = popen2.popen2(cmd, -1, 'b')
24 24 def writer():
25 25 try:
26 26 pin.write(s)
27 27 pin.close()
28 28 except IOError, inst:
29 29 if inst.errno != errno.EPIPE:
30 30 raise
31 31
32 32 # we should use select instead on UNIX, but this will work on most
33 33 # systems, including Windows
34 34 w = threading.Thread(target=writer)
35 35 w.start()
36 36 f = pout.read()
37 37 pout.close()
38 38 w.join()
39 39 return f
40 40
41 41 def tempfilter(s, cmd):
42 42 '''filter string S through a pair of temporary files with CMD.
43 43 CMD is used as a template to create the real command to be run,
44 44 with the strings INFILE and OUTFILE replaced by the real names of
45 45 the temporary files generated.'''
46 46 inname, outname = None, None
47 47 try:
48 48 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
49 49 fp = os.fdopen(infd, 'wb')
50 50 fp.write(s)
51 51 fp.close()
52 52 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
53 53 os.close(outfd)
54 54 cmd = cmd.replace('INFILE', inname)
55 55 cmd = cmd.replace('OUTFILE', outname)
56 56 code = os.system(cmd)
57 57 if code: raise Abort(_("command '%s' failed: %s") %
58 58 (cmd, explain_exit(code)))
59 59 return open(outname, 'rb').read()
60 60 finally:
61 61 try:
62 62 if inname: os.unlink(inname)
63 63 except: pass
64 64 try:
65 65 if outname: os.unlink(outname)
66 66 except: pass
67 67
68 68 filtertable = {
69 69 'tempfile:': tempfilter,
70 70 'pipe:': pipefilter,
71 71 }
72 72
73 73 def filter(s, cmd):
74 74 "filter a string through a command that transforms its input to its output"
75 75 for name, fn in filtertable.iteritems():
76 76 if cmd.startswith(name):
77 77 return fn(s, cmd[len(name):].lstrip())
78 78 return pipefilter(s, cmd)
79 79
80 80 def find_in_path(name, path, default=None):
81 81 '''find name in search path. path can be string (will be split
82 82 with os.pathsep), or iterable thing that returns strings. if name
83 83 found, return path to name. else return default.'''
84 84 if isinstance(path, str):
85 85 path = path.split(os.pathsep)
86 86 for p in path:
87 87 p_name = os.path.join(p, name)
88 88 if os.path.exists(p_name):
89 89 return p_name
90 90 return default
91 91
92 92 def patch(strip, patchname, ui):
93 93 """apply the patch <patchname> to the working directory.
94 94 a list of patched files is returned"""
95 95 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
96 96 fp = os.popen('%s -p%d < "%s"' % (patcher, strip, patchname))
97 97 files = {}
98 98 for line in fp:
99 99 line = line.rstrip()
100 100 ui.status("%s\n" % line)
101 101 if line.startswith('patching file '):
102 102 pf = parse_patch_output(line)
103 103 files.setdefault(pf, 1)
104 104 code = fp.close()
105 105 if code:
106 106 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
107 107 return files.keys()
108 108
109 109 def binary(s):
110 110 """return true if a string is binary data using diff's heuristic"""
111 111 if s and '\0' in s[:4096]:
112 112 return True
113 113 return False
114 114
115 115 def unique(g):
116 116 """return the uniq elements of iterable g"""
117 117 seen = {}
118 118 for f in g:
119 119 if f not in seen:
120 120 seen[f] = 1
121 121 yield f
122 122
123 123 class Abort(Exception):
124 124 """Raised if a command needs to print an error and exit."""
125 125
126 126 def always(fn): return True
127 127 def never(fn): return False
128 128
129 129 def patkind(name, dflt_pat='glob'):
130 130 """Split a string into an optional pattern kind prefix and the
131 131 actual pattern."""
132 132 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
133 133 if name.startswith(prefix + ':'): return name.split(':', 1)
134 134 return dflt_pat, name
135 135
136 136 def globre(pat, head='^', tail='$'):
137 137 "convert a glob pattern into a regexp"
138 138 i, n = 0, len(pat)
139 139 res = ''
140 140 group = False
141 141 def peek(): return i < n and pat[i]
142 142 while i < n:
143 143 c = pat[i]
144 144 i = i+1
145 145 if c == '*':
146 146 if peek() == '*':
147 147 i += 1
148 148 res += '.*'
149 149 else:
150 150 res += '[^/]*'
151 151 elif c == '?':
152 152 res += '.'
153 153 elif c == '[':
154 154 j = i
155 155 if j < n and pat[j] in '!]':
156 156 j += 1
157 157 while j < n and pat[j] != ']':
158 158 j += 1
159 159 if j >= n:
160 160 res += '\\['
161 161 else:
162 162 stuff = pat[i:j].replace('\\','\\\\')
163 163 i = j + 1
164 164 if stuff[0] == '!':
165 165 stuff = '^' + stuff[1:]
166 166 elif stuff[0] == '^':
167 167 stuff = '\\' + stuff
168 168 res = '%s[%s]' % (res, stuff)
169 169 elif c == '{':
170 170 group = True
171 171 res += '(?:'
172 172 elif c == '}' and group:
173 173 res += ')'
174 174 group = False
175 175 elif c == ',' and group:
176 176 res += '|'
177 177 elif c == '\\':
178 178 p = peek()
179 179 if p:
180 180 i += 1
181 181 res += re.escape(p)
182 182 else:
183 183 res += re.escape(c)
184 184 else:
185 185 res += re.escape(c)
186 186 return head + res + tail
187 187
188 188 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
189 189
190 190 def pathto(n1, n2):
191 191 '''return the relative path from one place to another.
192 192 this returns a path in the form used by the local filesystem, not hg.'''
193 193 if not n1: return localpath(n2)
194 194 a, b = n1.split('/'), n2.split('/')
195 195 a.reverse()
196 196 b.reverse()
197 197 while a and b and a[-1] == b[-1]:
198 198 a.pop()
199 199 b.pop()
200 200 b.reverse()
201 201 return os.sep.join((['..'] * len(a)) + b)
202 202
203 203 def canonpath(root, cwd, myname):
204 204 """return the canonical path of myname, given cwd and root"""
205 205 if root == os.sep:
206 206 rootsep = os.sep
207 207 elif root.endswith(os.sep):
208 208 rootsep = root
209 209 else:
210 210 rootsep = root + os.sep
211 211 name = myname
212 212 if not os.path.isabs(name):
213 213 name = os.path.join(root, cwd, name)
214 214 name = os.path.normpath(name)
215 215 if name != rootsep and name.startswith(rootsep):
216 216 name = name[len(rootsep):]
217 217 audit_path(name)
218 218 return pconvert(name)
219 219 elif name == root:
220 220 return ''
221 221 else:
222 222 # Determine whether `name' is in the hierarchy at or beneath `root',
223 223 # by iterating name=dirname(name) until that causes no change (can't
224 224 # check name == '/', because that doesn't work on windows). For each
225 225 # `name', compare dev/inode numbers. If they match, the list `rel'
226 226 # holds the reversed list of components making up the relative file
227 227 # name we want.
228 228 root_st = os.stat(root)
229 229 rel = []
230 230 while True:
231 231 try:
232 232 name_st = os.stat(name)
233 233 except OSError:
234 234 break
235 235 if samestat(name_st, root_st):
236 236 rel.reverse()
237 237 name = os.path.join(*rel)
238 238 audit_path(name)
239 239 return pconvert(name)
240 240 dirname, basename = os.path.split(name)
241 241 rel.append(basename)
242 242 if dirname == name:
243 243 break
244 244 name = dirname
245 245
246 246 raise Abort('%s not under root' % myname)
247 247
248 248 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
249 249 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
250 250
251 251 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
252 252 if os.name == 'nt':
253 253 dflt_pat = 'glob'
254 254 else:
255 255 dflt_pat = 'relpath'
256 256 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
257 257
258 258 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
259 259 """build a function to match a set of file patterns
260 260
261 261 arguments:
262 262 canonroot - the canonical root of the tree you're matching against
263 263 cwd - the current working directory, if relevant
264 264 names - patterns to find
265 265 inc - patterns to include
266 266 exc - patterns to exclude
267 267 head - a regex to prepend to patterns to control whether a match is rooted
268 268
269 269 a pattern is one of:
270 270 'glob:<rooted glob>'
271 271 're:<rooted regexp>'
272 272 'path:<rooted path>'
273 273 'relglob:<relative glob>'
274 274 'relpath:<relative path>'
275 275 'relre:<relative regexp>'
276 276 '<rooted path or regexp>'
277 277
278 278 returns:
279 279 a 3-tuple containing
280 280 - list of explicit non-pattern names passed in
281 281 - a bool match(filename) function
282 282 - a bool indicating if any patterns were passed in
283 283
284 284 todo:
285 285 make head regex a rooted bool
286 286 """
287 287
288 288 def contains_glob(name):
289 289 for c in name:
290 290 if c in _globchars: return True
291 291 return False
292 292
293 293 def regex(kind, name, tail):
294 294 '''convert a pattern into a regular expression'''
295 295 if kind == 're':
296 296 return name
297 297 elif kind == 'path':
298 298 return '^' + re.escape(name) + '(?:/|$)'
299 299 elif kind == 'relglob':
300 300 return head + globre(name, '(?:|.*/)', tail)
301 301 elif kind == 'relpath':
302 302 return head + re.escape(name) + tail
303 303 elif kind == 'relre':
304 304 if name.startswith('^'):
305 305 return name
306 306 return '.*' + name
307 307 return head + globre(name, '', tail)
308 308
309 309 def matchfn(pats, tail):
310 310 """build a matching function from a set of patterns"""
311 311 if not pats:
312 312 return
313 313 matches = []
314 314 for k, p in pats:
315 315 try:
316 316 pat = '(?:%s)' % regex(k, p, tail)
317 317 matches.append(re.compile(pat).match)
318 318 except re.error:
319 319 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
320 320 else: raise Abort("invalid pattern (%s): %s" % (k, p))
321 321
322 322 def buildfn(text):
323 323 for m in matches:
324 324 r = m(text)
325 325 if r:
326 326 return r
327 327
328 328 return buildfn
329 329
330 330 def globprefix(pat):
331 331 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
332 332 root = []
333 333 for p in pat.split(os.sep):
334 334 if contains_glob(p): break
335 335 root.append(p)
336 336 return '/'.join(root)
337 337
338 338 pats = []
339 339 files = []
340 340 roots = []
341 341 for kind, name in [patkind(p, dflt_pat) for p in names]:
342 342 if kind in ('glob', 'relpath'):
343 343 name = canonpath(canonroot, cwd, name)
344 344 if name == '':
345 345 kind, name = 'glob', '**'
346 346 if kind in ('glob', 'path', 're'):
347 347 pats.append((kind, name))
348 348 if kind == 'glob':
349 349 root = globprefix(name)
350 350 if root: roots.append(root)
351 351 elif kind == 'relpath':
352 352 files.append((kind, name))
353 353 roots.append(name)
354 354
355 355 patmatch = matchfn(pats, '$') or always
356 356 filematch = matchfn(files, '(?:/|$)') or always
357 357 incmatch = always
358 358 if inc:
359 359 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
360 360 incmatch = matchfn(inckinds, '(?:/|$)')
361 361 excmatch = lambda fn: False
362 362 if exc:
363 363 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
364 364 excmatch = matchfn(exckinds, '(?:/|$)')
365 365
366 366 return (roots,
367 367 lambda fn: (incmatch(fn) and not excmatch(fn) and
368 368 (fn.endswith('/') or
369 369 (not pats and not files) or
370 370 (pats and patmatch(fn)) or
371 371 (files and filematch(fn)))),
372 372 (inc or exc or (pats and pats != [('glob', '**')])) and True)
373 373
374 374 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
375 375 '''enhanced shell command execution.
376 376 run with environment maybe modified, maybe in different dir.
377 377
378 378 if command fails and onerr is None, return status. if ui object,
379 379 print error message and return status, else raise onerr object as
380 380 exception.'''
381 381 oldenv = {}
382 382 for k in environ:
383 383 oldenv[k] = os.environ.get(k)
384 384 if cwd is not None:
385 385 oldcwd = os.getcwd()
386 386 try:
387 387 for k, v in environ.iteritems():
388 388 os.environ[k] = str(v)
389 389 if cwd is not None and oldcwd != cwd:
390 390 os.chdir(cwd)
391 391 rc = os.system(cmd)
392 392 if rc and onerr:
393 393 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
394 394 explain_exit(rc)[0])
395 395 if errprefix:
396 396 errmsg = '%s: %s' % (errprefix, errmsg)
397 397 try:
398 398 onerr.warn(errmsg + '\n')
399 399 except AttributeError:
400 400 raise onerr(errmsg)
401 401 return rc
402 402 finally:
403 403 for k, v in oldenv.iteritems():
404 404 if v is None:
405 405 del os.environ[k]
406 406 else:
407 407 os.environ[k] = v
408 408 if cwd is not None and oldcwd != cwd:
409 409 os.chdir(oldcwd)
410 410
411 411 def rename(src, dst):
412 412 """forcibly rename a file"""
413 413 try:
414 414 os.rename(src, dst)
415 415 except OSError, err:
416 416 # on windows, rename to existing file is not allowed, so we
417 417 # must delete destination first. but if file is open, unlink
418 418 # schedules it for delete but does not delete it. rename
419 419 # happens immediately even for open files, so we create
420 420 # temporary file, delete it, rename destination to that name,
421 421 # then delete that. then rename is safe to do.
422 422 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
423 423 os.close(fd)
424 424 os.unlink(temp)
425 425 os.rename(dst, temp)
426 426 os.unlink(temp)
427 427 os.rename(src, dst)
428 428
429 429 def unlink(f):
430 430 """unlink and remove the directory if it is empty"""
431 431 os.unlink(f)
432 432 # try removing directories that might now be empty
433 433 try:
434 434 os.removedirs(os.path.dirname(f))
435 435 except OSError:
436 436 pass
437 437
438 438 def copyfiles(src, dst, hardlink=None):
439 439 """Copy a directory tree using hardlinks if possible"""
440 440
441 441 if hardlink is None:
442 442 hardlink = (os.stat(src).st_dev ==
443 443 os.stat(os.path.dirname(dst)).st_dev)
444 444
445 445 if os.path.isdir(src):
446 446 os.mkdir(dst)
447 447 for name in os.listdir(src):
448 448 srcname = os.path.join(src, name)
449 449 dstname = os.path.join(dst, name)
450 450 copyfiles(srcname, dstname, hardlink)
451 451 else:
452 452 if hardlink:
453 453 try:
454 454 os_link(src, dst)
455 455 except (IOError, OSError):
456 456 hardlink = False
457 457 shutil.copy(src, dst)
458 458 else:
459 459 shutil.copy(src, dst)
460 460
461 461 def audit_path(path):
462 462 """Abort if path contains dangerous components"""
463 463 parts = os.path.normcase(path).split(os.sep)
464 464 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
465 465 or os.pardir in parts):
466 466 raise Abort(_("path contains illegal component: %s\n") % path)
467 467
468 468 def _makelock_file(info, pathname):
469 469 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
470 470 os.write(ld, info)
471 471 os.close(ld)
472 472
473 473 def _readlock_file(pathname):
474 474 return posixfile(pathname).read()
475 475
476 476 def nlinks(pathname):
477 477 """Return number of hardlinks for the given file."""
478 478 return os.lstat(pathname).st_nlink
479 479
480 480 if hasattr(os, 'link'):
481 481 os_link = os.link
482 482 else:
483 483 def os_link(src, dst):
484 484 raise OSError(0, _("Hardlinks not supported"))
485 485
486 486 def fstat(fp):
487 487 '''stat file object that may not have fileno method.'''
488 488 try:
489 489 return os.fstat(fp.fileno())
490 490 except AttributeError:
491 491 return os.stat(fp.name)
492 492
493 493 posixfile = file
494 494
495 495 def is_win_9x():
496 496 '''return true if run on windows 95, 98 or me.'''
497 497 try:
498 498 return sys.getwindowsversion()[3] == 1
499 499 except AttributeError:
500 500 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
501 501
502 502 # Platform specific variants
503 503 if os.name == 'nt':
504 504 demandload(globals(), "msvcrt")
505 505 nulldev = 'NUL:'
506 506
507 507 class winstdout:
508 508 '''stdout on windows misbehaves if sent through a pipe'''
509 509
510 510 def __init__(self, fp):
511 511 self.fp = fp
512 512
513 513 def __getattr__(self, key):
514 514 return getattr(self.fp, key)
515 515
516 516 def close(self):
517 517 try:
518 518 self.fp.close()
519 519 except: pass
520 520
521 521 def write(self, s):
522 522 try:
523 523 return self.fp.write(s)
524 524 except IOError, inst:
525 525 if inst.errno != 0: raise
526 526 self.close()
527 527 raise IOError(errno.EPIPE, 'Broken pipe')
528 528
529 529 sys.stdout = winstdout(sys.stdout)
530 530
531 531 def system_rcpath():
532 532 try:
533 533 return system_rcpath_win32()
534 534 except:
535 535 return [r'c:\mercurial\mercurial.ini']
536 536
537 537 def os_rcpath():
538 538 '''return default os-specific hgrc search path'''
539 539 path = system_rcpath()
540 540 path.append(user_rcpath())
541 541 userprofile = os.environ.get('USERPROFILE')
542 542 if userprofile:
543 543 path.append(os.path.join(userprofile, 'mercurial.ini'))
544 544 return path
545 545
546 546 def user_rcpath():
547 547 '''return os-specific hgrc search path to the user dir'''
548 548 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
549 549
550 550 def parse_patch_output(output_line):
551 551 """parses the output produced by patch and returns the file name"""
552 552 pf = output_line[14:]
553 553 if pf[0] == '`':
554 554 pf = pf[1:-1] # Remove the quotes
555 555 return pf
556 556
557 557 def testpid(pid):
558 558 '''return False if pid dead, True if running or not known'''
559 559 return True
560 560
561 561 def is_exec(f, last):
562 562 return last
563 563
564 564 def set_exec(f, mode):
565 565 pass
566 566
567 567 def set_binary(fd):
568 568 msvcrt.setmode(fd.fileno(), os.O_BINARY)
569 569
570 570 def pconvert(path):
571 571 return path.replace("\\", "/")
572 572
573 573 def localpath(path):
574 574 return path.replace('/', '\\')
575 575
576 576 def normpath(path):
577 577 return pconvert(os.path.normpath(path))
578 578
579 579 makelock = _makelock_file
580 580 readlock = _readlock_file
581 581
582 582 def samestat(s1, s2):
583 583 return False
584 584
585 585 def explain_exit(code):
586 586 return _("exited with status %d") % code, code
587 587
588 588 try:
589 589 # override functions with win32 versions if possible
590 590 from util_win32 import *
591 591 if not is_win_9x():
592 592 posixfile = posixfile_nt
593 593 except ImportError:
594 594 pass
595 595
596 596 else:
597 597 nulldev = '/dev/null'
598 598
599 599 def rcfiles(path):
600 600 rcs = [os.path.join(path, 'hgrc')]
601 601 rcdir = os.path.join(path, 'hgrc.d')
602 602 try:
603 603 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
604 604 if f.endswith(".rc")])
605 605 except OSError, inst: pass
606 606 return rcs
607 607
608 608 def os_rcpath():
609 609 '''return default os-specific hgrc search path'''
610 610 path = []
611 611 # old mod_python does not set sys.argv
612 612 if len(getattr(sys, 'argv', [])) > 0:
613 613 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
614 614 '/../etc/mercurial'))
615 615 path.extend(rcfiles('/etc/mercurial'))
616 616 path.append(os.path.expanduser('~/.hgrc'))
617 617 path = [os.path.normpath(f) for f in path]
618 618 return path
619 619
620 620 def parse_patch_output(output_line):
621 621 """parses the output produced by patch and returns the file name"""
622 622 pf = output_line[14:]
623 623 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
624 624 pf = pf[1:-1] # Remove the quotes
625 625 return pf
626 626
627 627 def is_exec(f, last):
628 628 """check whether a file is executable"""
629 629 return (os.lstat(f).st_mode & 0100 != 0)
630 630
631 631 def set_exec(f, mode):
632 632 s = os.lstat(f).st_mode
633 633 if (s & 0100 != 0) == mode:
634 634 return
635 635 if mode:
636 636 # Turn on +x for every +r bit when making a file executable
637 637 # and obey umask.
638 638 umask = os.umask(0)
639 639 os.umask(umask)
640 640 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
641 641 else:
642 642 os.chmod(f, s & 0666)
643 643
644 644 def set_binary(fd):
645 645 pass
646 646
647 647 def pconvert(path):
648 648 return path
649 649
650 650 def localpath(path):
651 651 return path
652 652
653 653 normpath = os.path.normpath
654 654 samestat = os.path.samestat
655 655
656 656 def makelock(info, pathname):
657 657 try:
658 658 os.symlink(info, pathname)
659 659 except OSError, why:
660 660 if why.errno == errno.EEXIST:
661 661 raise
662 662 else:
663 663 _makelock_file(info, pathname)
664 664
665 665 def readlock(pathname):
666 666 try:
667 667 return os.readlink(pathname)
668 668 except OSError, why:
669 669 if why.errno == errno.EINVAL:
670 670 return _readlock_file(pathname)
671 671 else:
672 672 raise
673 673
674 674 def testpid(pid):
675 675 '''return False if pid dead, True if running or not sure'''
676 676 try:
677 677 os.kill(pid, 0)
678 678 return True
679 679 except OSError, inst:
680 680 return inst.errno != errno.ESRCH
681 681
682 682 def explain_exit(code):
683 683 """return a 2-tuple (desc, code) describing a process's status"""
684 684 if os.WIFEXITED(code):
685 685 val = os.WEXITSTATUS(code)
686 686 return _("exited with status %d") % val, val
687 687 elif os.WIFSIGNALED(code):
688 688 val = os.WTERMSIG(code)
689 689 return _("killed by signal %d") % val, val
690 690 elif os.WIFSTOPPED(code):
691 691 val = os.WSTOPSIG(code)
692 692 return _("stopped by signal %d") % val, val
693 693 raise ValueError(_("invalid exit code"))
694 694
695 695 def opener(base, audit=True):
696 696 """
697 697 return a function that opens files relative to base
698 698
699 699 this function is used to hide the details of COW semantics and
700 700 remote file access from higher level code.
701 701 """
702 702 p = base
703 703 audit_p = audit
704 704
705 705 def mktempcopy(name):
706 706 d, fn = os.path.split(name)
707 707 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
708 708 os.close(fd)
709 709 ofp = posixfile(temp, "wb")
710 710 try:
711 711 try:
712 712 ifp = posixfile(name, "rb")
713 713 except IOError, inst:
714 714 if not getattr(inst, 'filename', None):
715 715 inst.filename = name
716 716 raise
717 717 for chunk in filechunkiter(ifp):
718 718 ofp.write(chunk)
719 719 ifp.close()
720 720 ofp.close()
721 721 except:
722 722 try: os.unlink(temp)
723 723 except: pass
724 724 raise
725 725 st = os.lstat(name)
726 726 os.chmod(temp, st.st_mode)
727 727 return temp
728 728
729 729 class atomictempfile(posixfile):
730 730 """the file will only be copied when rename is called"""
731 731 def __init__(self, name, mode):
732 732 self.__name = name
733 733 self.temp = mktempcopy(name)
734 734 posixfile.__init__(self, self.temp, mode)
735 735 def rename(self):
736 736 if not self.closed:
737 737 posixfile.close(self)
738 738 rename(self.temp, localpath(self.__name))
739 739 def __del__(self):
740 740 if not self.closed:
741 741 try:
742 742 os.unlink(self.temp)
743 743 except: pass
744 744 posixfile.close(self)
745 745
746 746 class atomicfile(atomictempfile):
747 747 """the file will only be copied on close"""
748 748 def __init__(self, name, mode):
749 749 atomictempfile.__init__(self, name, mode)
750 750 def close(self):
751 751 self.rename()
752 752 def __del__(self):
753 753 self.rename()
754 754
755 755 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
756 756 if audit_p:
757 757 audit_path(path)
758 758 f = os.path.join(p, path)
759 759
760 760 if not text:
761 761 mode += "b" # for that other OS
762 762
763 763 if mode[0] != "r":
764 764 try:
765 765 nlink = nlinks(f)
766 766 except OSError:
767 767 d = os.path.dirname(f)
768 768 if not os.path.isdir(d):
769 769 os.makedirs(d)
770 770 else:
771 771 if atomic:
772 772 return atomicfile(f, mode)
773 773 elif atomictemp:
774 774 return atomictempfile(f, mode)
775 775 if nlink > 1:
776 776 rename(mktempcopy(f), f)
777 777 return posixfile(f, mode)
778 778
779 779 return o
780 780
781 781 class chunkbuffer(object):
782 782 """Allow arbitrary sized chunks of data to be efficiently read from an
783 783 iterator over chunks of arbitrary size."""
784 784
785 785 def __init__(self, in_iter, targetsize = 2**16):
786 786 """in_iter is the iterator that's iterating over the input chunks.
787 787 targetsize is how big a buffer to try to maintain."""
788 788 self.in_iter = iter(in_iter)
789 789 self.buf = ''
790 790 self.targetsize = int(targetsize)
791 791 if self.targetsize <= 0:
792 792 raise ValueError(_("targetsize must be greater than 0, was %d") %
793 793 targetsize)
794 794 self.iterempty = False
795 795
796 796 def fillbuf(self):
797 797 """Ignore target size; read every chunk from iterator until empty."""
798 798 if not self.iterempty:
799 799 collector = cStringIO.StringIO()
800 800 collector.write(self.buf)
801 801 for ch in self.in_iter:
802 802 collector.write(ch)
803 803 self.buf = collector.getvalue()
804 804 self.iterempty = True
805 805
806 806 def read(self, l):
807 807 """Read L bytes of data from the iterator of chunks of data.
808 808 Returns less than L bytes if the iterator runs dry."""
809 809 if l > len(self.buf) and not self.iterempty:
810 810 # Clamp to a multiple of self.targetsize
811 811 targetsize = self.targetsize * ((l // self.targetsize) + 1)
812 812 collector = cStringIO.StringIO()
813 813 collector.write(self.buf)
814 814 collected = len(self.buf)
815 815 for chunk in self.in_iter:
816 816 collector.write(chunk)
817 817 collected += len(chunk)
818 818 if collected >= targetsize:
819 819 break
820 820 if collected < targetsize:
821 821 self.iterempty = True
822 822 self.buf = collector.getvalue()
823 823 s, self.buf = self.buf[:l], buffer(self.buf, l)
824 824 return s
825 825
826 826 def filechunkiter(f, size=65536, limit=None):
827 827 """Create a generator that produces the data in the file size
828 828 (default 65536) bytes at a time, up to optional limit (default is
829 829 to read all data). Chunks may be less than size bytes if the
830 830 chunk is the last chunk in the file, or the file is a socket or
831 831 some other type of file that sometimes reads less data than is
832 832 requested."""
833 833 assert size >= 0
834 834 assert limit is None or limit >= 0
835 835 while True:
836 836 if limit is None: nbytes = size
837 837 else: nbytes = min(limit, size)
838 838 s = nbytes and f.read(nbytes)
839 839 if not s: break
840 840 if limit: limit -= len(s)
841 841 yield s
842 842
843 843 def makedate():
844 844 lt = time.localtime()
845 845 if lt[8] == 1 and time.daylight:
846 846 tz = time.altzone
847 847 else:
848 848 tz = time.timezone
849 849 return time.mktime(lt), tz
850 850
851 851 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
852 852 """represent a (unixtime, offset) tuple as a localized time.
853 853 unixtime is seconds since the epoch, and offset is the time zone's
854 854 number of seconds away from UTC. if timezone is false, do not
855 855 append time zone to string."""
856 856 t, tz = date or makedate()
857 857 s = time.strftime(format, time.gmtime(float(t) - tz))
858 858 if timezone:
859 859 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
860 860 return s
861 861
862 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
863 """parse a localized time string and return a (unixtime, offset) tuple.
864 if the string cannot be parsed, ValueError is raised."""
865 def hastimezone(string):
866 return (string[-4:].isdigit() and
867 (string[-5] == '+' or string[-5] == '-') and
868 string[-6].isspace())
869
870 if hastimezone(string):
871 date, tz = string.rsplit(None, 1)
872 tz = int(tz)
873 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
874 else:
875 date, offset = string, 0
876 when = int(time.mktime(time.strptime(date, format))) + offset
877 return when, offset
878
879 def parsedate(string, formats=('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M')):
880 """parse a localized time string and return a (unixtime, offset) tuple.
881 The date may be a "unixtime offset" string or in one of the specified
882 formats."""
883 try:
884 when, offset = map(int, string.split(' '))
885 except ValueError:
886 for format in formats:
887 try:
888 when, offset = strdate(string, format)
889 except ValueError:
890 pass
891 else:
892 break
893 else:
894 raise ValueError(_('invalid date: %r') % string)
895 # validate explicit (probably user-specified) date and
896 # time zone offset. values must fit in signed 32 bits for
897 # current 32-bit linux runtimes. timezones go from UTC-12
898 # to UTC+14
899 if abs(when) > 0x7fffffff:
900 raise ValueError(_('date exceeds 32 bits: %d') % when)
901 if offset < -50400 or offset > 43200:
902 raise ValueError(_('impossible time zone offset: %d') % offset)
903 return when, offset
904
862 905 def shortuser(user):
863 906 """Return a short representation of a user name or email address."""
864 907 f = user.find('@')
865 908 if f >= 0:
866 909 user = user[:f]
867 910 f = user.find('<')
868 911 if f >= 0:
869 912 user = user[f+1:]
870 913 return user
871 914
872 915 def walkrepos(path):
873 916 '''yield every hg repository under path, recursively.'''
874 917 def errhandler(err):
875 918 if err.filename == path:
876 919 raise err
877 920
878 921 for root, dirs, files in os.walk(path, onerror=errhandler):
879 922 for d in dirs:
880 923 if d == '.hg':
881 924 yield root
882 925 dirs[:] = []
883 926 break
884 927
885 928 _rcpath = None
886 929
887 930 def rcpath():
888 931 '''return hgrc search path. if env var HGRCPATH is set, use it.
889 932 for each item in path, if directory, use files ending in .rc,
890 933 else use item.
891 934 make HGRCPATH empty to only look in .hg/hgrc of current repo.
892 935 if no HGRCPATH, use default os-specific path.'''
893 936 global _rcpath
894 937 if _rcpath is None:
895 938 if 'HGRCPATH' in os.environ:
896 939 _rcpath = []
897 940 for p in os.environ['HGRCPATH'].split(os.pathsep):
898 941 if not p: continue
899 942 if os.path.isdir(p):
900 943 for f in os.listdir(p):
901 944 if f.endswith('.rc'):
902 945 _rcpath.append(os.path.join(p, f))
903 946 else:
904 947 _rcpath.append(p)
905 948 else:
906 949 _rcpath = os_rcpath()
907 950 return _rcpath
@@ -1,9 +1,9 b''
1 1 #header#
2 2 # HG changeset patch
3 3 # User #author#
4 # Date #date|date#
4 # Date #date|hgdate#
5 5 # Node ID #node#
6 6 #parent%changesetparent#
7 7 #desc#
8 8
9 9 #diff#
@@ -1,16 +1,16 b''
1 1 header = header-raw.tmpl
2 2 footer = ''
3 3 changeset = changeset-raw.tmpl
4 4 difflineplus = '#line#'
5 5 difflineminus = '#line#'
6 6 difflineat = '#line#'
7 7 diffline = '#line#'
8 changesetparent = '# parent: #node#'
9 changesetchild = '# child: #node#'
8 changesetparent = '# Parent #node#'
9 changesetchild = '# Child #node#'
10 10 filenodelink = ''
11 11 filerevision = 'Content-Type: #mimetype#\nContent-Disposition: filename=#file#\n\n#raw#'
12 12 fileline = '#line#'
13 13 diffblock = '#lines#'
14 14 filediff = filediff-raw.tmpl
15 15 fileannotate = fileannotate-raw.tmpl
16 16 annotateline = '#author#@#rev#: #line#'
@@ -1,46 +1,49 b''
1 1 #!/bin/sh
2 2
3 3 mkdir test
4 4 cd test
5 5 hg init
6 6 for i in 0 1 2 3 4 5 6 7 8; do
7 7 echo $i >> foo
8 8 hg commit -A -m $i -d "1000000 0"
9 9 done
10 10 hg verify
11 11 hg serve -p 20059 -d --pid-file=hg.pid
12 12 cd ..
13 13
14 14 hg init new
15 15 # http incoming
16 16 http_proxy= hg -R new incoming http://localhost:20059/
17 http_proxy= hg -R new incoming -r 4 http://localhost:20059/
17 18 # local incoming
18 19 hg -R new incoming test
20 hg -R new incoming -r 4 test
19 21
20 22 # test with --bundle
21 23 http_proxy= hg -R new incoming --bundle test.hg http://localhost:20059/
22 24 hg -R new incoming --bundle test2.hg test
23 25
24 26 # test the resulting bundles
25 27 hg init temp
26 28 hg init temp2
27 29 hg -R temp unbundle test.hg
28 30 hg -R temp2 unbundle test2.hg
29 31 hg -R temp tip
30 32 hg -R temp2 tip
31 33
32 34 rm -rf temp temp2 new
33 35
34 36 # test outgoing
35 37 hg clone test test-dev
36 38 cd test-dev
37 39 for i in 9 10 11 12 13; do
38 40 echo $i >> foo
39 41 hg commit -A -m $i -d "1000000 0"
40 42 done
41 43 hg verify
42 44 cd ..
43 45 hg -R test-dev outgoing test
44 46 http_proxy= hg -R test-dev outgoing http://localhost:20059/
47 http_proxy= hg -R test-dev outgoing -r 11 http://localhost:20059/
45 48
46 49 kill `cat test/hg.pid`
@@ -1,272 +1,338 b''
1 1 adding foo
2 2 checking changesets
3 3 checking manifests
4 4 crosschecking files in changesets and manifests
5 5 checking files
6 6 1 files, 9 changesets, 9 total revisions
7 7 changeset: 0:9cb21d99fe27
8 8 user: test
9 9 date: Mon Jan 12 13:46:40 1970 +0000
10 10 summary: 0
11 11
12 12 changeset: 1:d717f5dfad6a
13 13 user: test
14 14 date: Mon Jan 12 13:46:40 1970 +0000
15 15 summary: 1
16 16
17 17 changeset: 2:c0d6b86da426
18 18 user: test
19 19 date: Mon Jan 12 13:46:40 1970 +0000
20 20 summary: 2
21 21
22 22 changeset: 3:dfacbd43b3fe
23 23 user: test
24 24 date: Mon Jan 12 13:46:40 1970 +0000
25 25 summary: 3
26 26
27 27 changeset: 4:1f3a964b6022
28 28 user: test
29 29 date: Mon Jan 12 13:46:40 1970 +0000
30 30 summary: 4
31 31
32 32 changeset: 5:c028bcc7a28a
33 33 user: test
34 34 date: Mon Jan 12 13:46:40 1970 +0000
35 35 summary: 5
36 36
37 37 changeset: 6:a0c0095f3389
38 38 user: test
39 39 date: Mon Jan 12 13:46:40 1970 +0000
40 40 summary: 6
41 41
42 42 changeset: 7:d4be65f4e891
43 43 user: test
44 44 date: Mon Jan 12 13:46:40 1970 +0000
45 45 summary: 7
46 46
47 47 changeset: 8:92b83e334ef8
48 48 tag: tip
49 49 user: test
50 50 date: Mon Jan 12 13:46:40 1970 +0000
51 51 summary: 8
52 52
53 53 changeset: 0:9cb21d99fe27
54 54 user: test
55 55 date: Mon Jan 12 13:46:40 1970 +0000
56 56 summary: 0
57 57
58 58 changeset: 1:d717f5dfad6a
59 59 user: test
60 60 date: Mon Jan 12 13:46:40 1970 +0000
61 61 summary: 1
62 62
63 63 changeset: 2:c0d6b86da426
64 64 user: test
65 65 date: Mon Jan 12 13:46:40 1970 +0000
66 66 summary: 2
67 67
68 68 changeset: 3:dfacbd43b3fe
69 69 user: test
70 70 date: Mon Jan 12 13:46:40 1970 +0000
71 71 summary: 3
72 72
73 73 changeset: 4:1f3a964b6022
74 74 user: test
75 75 date: Mon Jan 12 13:46:40 1970 +0000
76 76 summary: 4
77 77
78 changeset: 0:9cb21d99fe27
79 user: test
80 date: Mon Jan 12 13:46:40 1970 +0000
81 summary: 0
82
83 changeset: 1:d717f5dfad6a
84 user: test
85 date: Mon Jan 12 13:46:40 1970 +0000
86 summary: 1
87
88 changeset: 2:c0d6b86da426
89 user: test
90 date: Mon Jan 12 13:46:40 1970 +0000
91 summary: 2
92
93 changeset: 3:dfacbd43b3fe
94 user: test
95 date: Mon Jan 12 13:46:40 1970 +0000
96 summary: 3
97
98 changeset: 4:1f3a964b6022
99 user: test
100 date: Mon Jan 12 13:46:40 1970 +0000
101 summary: 4
102
78 103 changeset: 5:c028bcc7a28a
79 104 user: test
80 105 date: Mon Jan 12 13:46:40 1970 +0000
81 106 summary: 5
82 107
83 108 changeset: 6:a0c0095f3389
84 109 user: test
85 110 date: Mon Jan 12 13:46:40 1970 +0000
86 111 summary: 6
87 112
88 113 changeset: 7:d4be65f4e891
89 114 user: test
90 115 date: Mon Jan 12 13:46:40 1970 +0000
91 116 summary: 7
92 117
93 118 changeset: 8:92b83e334ef8
94 119 tag: tip
95 120 user: test
96 121 date: Mon Jan 12 13:46:40 1970 +0000
97 122 summary: 8
98 123
99 124 changeset: 0:9cb21d99fe27
100 125 user: test
101 126 date: Mon Jan 12 13:46:40 1970 +0000
102 127 summary: 0
103 128
104 129 changeset: 1:d717f5dfad6a
105 130 user: test
106 131 date: Mon Jan 12 13:46:40 1970 +0000
107 132 summary: 1
108 133
109 134 changeset: 2:c0d6b86da426
110 135 user: test
111 136 date: Mon Jan 12 13:46:40 1970 +0000
112 137 summary: 2
113 138
114 139 changeset: 3:dfacbd43b3fe
115 140 user: test
116 141 date: Mon Jan 12 13:46:40 1970 +0000
117 142 summary: 3
118 143
119 144 changeset: 4:1f3a964b6022
120 145 user: test
121 146 date: Mon Jan 12 13:46:40 1970 +0000
122 147 summary: 4
123 148
149 changeset: 0:9cb21d99fe27
150 user: test
151 date: Mon Jan 12 13:46:40 1970 +0000
152 summary: 0
153
154 changeset: 1:d717f5dfad6a
155 user: test
156 date: Mon Jan 12 13:46:40 1970 +0000
157 summary: 1
158
159 changeset: 2:c0d6b86da426
160 user: test
161 date: Mon Jan 12 13:46:40 1970 +0000
162 summary: 2
163
164 changeset: 3:dfacbd43b3fe
165 user: test
166 date: Mon Jan 12 13:46:40 1970 +0000
167 summary: 3
168
169 changeset: 4:1f3a964b6022
170 user: test
171 date: Mon Jan 12 13:46:40 1970 +0000
172 summary: 4
173
124 174 changeset: 5:c028bcc7a28a
125 175 user: test
126 176 date: Mon Jan 12 13:46:40 1970 +0000
127 177 summary: 5
128 178
129 179 changeset: 6:a0c0095f3389
130 180 user: test
131 181 date: Mon Jan 12 13:46:40 1970 +0000
132 182 summary: 6
133 183
134 184 changeset: 7:d4be65f4e891
135 185 user: test
136 186 date: Mon Jan 12 13:46:40 1970 +0000
137 187 summary: 7
138 188
139 189 changeset: 8:92b83e334ef8
140 190 tag: tip
141 191 user: test
142 192 date: Mon Jan 12 13:46:40 1970 +0000
143 193 summary: 8
144 194
145 195 changeset: 0:9cb21d99fe27
146 196 user: test
147 197 date: Mon Jan 12 13:46:40 1970 +0000
148 198 summary: 0
149 199
150 200 changeset: 1:d717f5dfad6a
151 201 user: test
152 202 date: Mon Jan 12 13:46:40 1970 +0000
153 203 summary: 1
154 204
155 205 changeset: 2:c0d6b86da426
156 206 user: test
157 207 date: Mon Jan 12 13:46:40 1970 +0000
158 208 summary: 2
159 209
160 210 changeset: 3:dfacbd43b3fe
161 211 user: test
162 212 date: Mon Jan 12 13:46:40 1970 +0000
163 213 summary: 3
164 214
165 215 changeset: 4:1f3a964b6022
166 216 user: test
167 217 date: Mon Jan 12 13:46:40 1970 +0000
168 218 summary: 4
169 219
170 220 changeset: 5:c028bcc7a28a
171 221 user: test
172 222 date: Mon Jan 12 13:46:40 1970 +0000
173 223 summary: 5
174 224
175 225 changeset: 6:a0c0095f3389
176 226 user: test
177 227 date: Mon Jan 12 13:46:40 1970 +0000
178 228 summary: 6
179 229
180 230 changeset: 7:d4be65f4e891
181 231 user: test
182 232 date: Mon Jan 12 13:46:40 1970 +0000
183 233 summary: 7
184 234
185 235 changeset: 8:92b83e334ef8
186 236 tag: tip
187 237 user: test
188 238 date: Mon Jan 12 13:46:40 1970 +0000
189 239 summary: 8
190 240
191 241 adding changesets
192 242 adding manifests
193 243 adding file changes
194 244 added 9 changesets with 9 changes to 1 files
195 245 (run 'hg update' to get a working copy)
196 246 adding changesets
197 247 adding manifests
198 248 adding file changes
199 249 added 9 changesets with 9 changes to 1 files
200 250 (run 'hg update' to get a working copy)
201 251 changeset: 8:92b83e334ef8
202 252 tag: tip
203 253 user: test
204 254 date: Mon Jan 12 13:46:40 1970 +0000
205 255 summary: 8
206 256
207 257 changeset: 8:92b83e334ef8
208 258 tag: tip
209 259 user: test
210 260 date: Mon Jan 12 13:46:40 1970 +0000
211 261 summary: 8
212 262
213 263 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 264 checking changesets
215 265 checking manifests
216 266 crosschecking files in changesets and manifests
217 267 checking files
218 268 1 files, 14 changesets, 14 total revisions
219 269 searching for changes
220 270 changeset: 9:3741c3ad1096
221 271 user: test
222 272 date: Mon Jan 12 13:46:40 1970 +0000
223 273 summary: 9
224 274
225 275 changeset: 10:de4143c8d9a5
226 276 user: test
227 277 date: Mon Jan 12 13:46:40 1970 +0000
228 278 summary: 10
229 279
230 280 changeset: 11:0e1c188b9a7a
231 281 user: test
232 282 date: Mon Jan 12 13:46:40 1970 +0000
233 283 summary: 11
234 284
235 285 changeset: 12:251354d0fdd3
236 286 user: test
237 287 date: Mon Jan 12 13:46:40 1970 +0000
238 288 summary: 12
239 289
240 290 changeset: 13:bdaadd969642
241 291 tag: tip
242 292 user: test
243 293 date: Mon Jan 12 13:46:40 1970 +0000
244 294 summary: 13
245 295
246 296 searching for changes
247 297 changeset: 9:3741c3ad1096
248 298 user: test
249 299 date: Mon Jan 12 13:46:40 1970 +0000
250 300 summary: 9
251 301
252 302 changeset: 10:de4143c8d9a5
253 303 user: test
254 304 date: Mon Jan 12 13:46:40 1970 +0000
255 305 summary: 10
256 306
257 307 changeset: 11:0e1c188b9a7a
258 308 user: test
259 309 date: Mon Jan 12 13:46:40 1970 +0000
260 310 summary: 11
261 311
262 312 changeset: 12:251354d0fdd3
263 313 user: test
264 314 date: Mon Jan 12 13:46:40 1970 +0000
265 315 summary: 12
266 316
267 317 changeset: 13:bdaadd969642
268 318 tag: tip
269 319 user: test
270 320 date: Mon Jan 12 13:46:40 1970 +0000
271 321 summary: 13
272 322
323 searching for changes
324 changeset: 9:3741c3ad1096
325 user: test
326 date: Mon Jan 12 13:46:40 1970 +0000
327 summary: 9
328
329 changeset: 10:de4143c8d9a5
330 user: test
331 date: Mon Jan 12 13:46:40 1970 +0000
332 summary: 10
333
334 changeset: 11:0e1c188b9a7a
335 user: test
336 date: Mon Jan 12 13:46:40 1970 +0000
337 summary: 11
338
General Comments 0
You need to be logged in to leave comments. Login now