##// END OF EJS Templates
Merge with mpm
Brendan Cully -
r4712:f49fcbb3 merge default
parent child Browse files
Show More
@@ -0,0 +1,127 b''
1 # repair.py - functions for repository repair for mercurial
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 # Copyright 2007 Matt Mackall
5 #
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
8
9 import changegroup, revlog, os, commands
10
11 def strip(ui, repo, rev, backup="all"):
12 def limitheads(chlog, stop):
13 """return the list of all nodes that have no children"""
14 p = {}
15 h = []
16 stoprev = 0
17 if stop in chlog.nodemap:
18 stoprev = chlog.rev(stop)
19
20 for r in xrange(chlog.count() - 1, -1, -1):
21 n = chlog.node(r)
22 if n not in p:
23 h.append(n)
24 if n == stop:
25 break
26 if r < stoprev:
27 break
28 for pn in chlog.parents(n):
29 p[pn] = 1
30 return h
31
32 def bundle(repo, bases, heads, rev, suffix):
33 cg = repo.changegroupsubset(bases, heads, 'strip')
34 backupdir = repo.join("strip-backup")
35 if not os.path.isdir(backupdir):
36 os.mkdir(backupdir)
37 name = os.path.join(backupdir, "%s-%s" % (revlog.short(rev), suffix))
38 ui.warn("saving bundle to %s\n" % name)
39 return changegroup.writebundle(cg, name, "HG10BZ")
40
41 def stripall(revnum):
42 mm = repo.changectx(rev).manifest()
43 seen = {}
44
45 for x in xrange(revnum, repo.changelog.count()):
46 for f in repo.changectx(x).files():
47 if f in seen:
48 continue
49 seen[f] = 1
50 if f in mm:
51 filerev = mm[f]
52 else:
53 filerev = 0
54 seen[f] = filerev
55 # we go in two steps here so the strip loop happens in a
56 # sensible order. When stripping many files, this helps keep
57 # our disk access patterns under control.
58 seen_list = seen.keys()
59 seen_list.sort()
60 for f in seen_list:
61 ff = repo.file(f)
62 filerev = seen[f]
63 if filerev != 0:
64 if filerev in ff.nodemap:
65 filerev = ff.rev(filerev)
66 else:
67 filerev = 0
68 ff.strip(filerev, revnum)
69
70 chlog = repo.changelog
71 # TODO delete the undo files, and handle undo of merge sets
72 pp = chlog.parents(rev)
73 revnum = chlog.rev(rev)
74
75 # save is a list of all the branches we are truncating away
76 # that we actually want to keep. changegroup will be used
77 # to preserve them and add them back after the truncate
78 saveheads = []
79 savebases = {}
80
81 heads = limitheads(chlog, rev)
82 seen = {}
83
84 # search through all the heads, finding those where the revision
85 # we want to strip away is an ancestor. Also look for merges
86 # that might be turned into new heads by the strip.
87 while heads:
88 h = heads.pop()
89 n = h
90 while True:
91 seen[n] = 1
92 pp = chlog.parents(n)
93 if pp[1] != revlog.nullid:
94 for p in pp:
95 if chlog.rev(p) > revnum and p not in seen:
96 heads.append(p)
97 if pp[0] == revlog.nullid:
98 break
99 if chlog.rev(pp[0]) < revnum:
100 break
101 n = pp[0]
102 if n == rev:
103 break
104 r = chlog.reachable(h, rev)
105 if rev not in r:
106 saveheads.append(h)
107 for x in r:
108 if chlog.rev(x) > revnum:
109 savebases[x] = 1
110
111 # create a changegroup for all the branches we need to keep
112 if backup == "all":
113 bundle(repo, [rev], chlog.heads(), rev, 'backup')
114 if saveheads:
115 chgrpfile = bundle(repo, savebases.keys(), saveheads, rev, 'temp')
116
117 stripall(revnum)
118
119 change = chlog.read(rev)
120 chlog.strip(revnum, revnum)
121 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
122 if saveheads:
123 ui.status("adding branch\n")
124 commands.unbundle(ui, repo, "file:%s" % chgrpfile, update=False)
125 if backup != "strip":
126 os.unlink(chgrpfile)
127
@@ -1,77 +1,79 b''
1 1 PREFIX=/usr/local
2 2 export PREFIX
3 3 PYTHON=python
4 4
5 5 help:
6 6 @echo 'Commonly used make targets:'
7 7 @echo ' all - build program and documentation'
8 8 @echo ' install - install program and man pages to PREFIX ($(PREFIX))'
9 9 @echo ' install-home - install with setup.py install --home=HOME ($(HOME))'
10 @echo ' local - build C extensions for inplace usage'
10 @echo ' local - build for inplace usage'
11 11 @echo ' tests - run all tests in the automatic test suite'
12 12 @echo ' test-foo - run only specified tests (e.g. test-merge1)'
13 13 @echo ' dist - run all tests and create a source tarball in dist/'
14 14 @echo ' clean - remove files created by other targets'
15 15 @echo ' (except installed files or dist source tarball)'
16 16 @echo
17 17 @echo 'Example for a system-wide installation under /usr/local:'
18 18 @echo ' make all && su -c "make install" && hg version'
19 19 @echo
20 20 @echo 'Example for a local installation (usable in this directory):'
21 21 @echo ' make local && ./hg version'
22 22
23 23 all: build doc
24 24
25 25 local:
26 26 $(PYTHON) setup.py build_ext -i
27 $(PYTHON) setup.py build_py -c -d .
28 $(PYTHON) hg version
27 29
28 30 build:
29 31 $(PYTHON) setup.py build
30 32
31 33 doc:
32 34 $(MAKE) -C doc
33 35
34 36 clean:
35 37 -$(PYTHON) setup.py clean --all # ignore errors of this command
36 find . -name '*.py[co]' -exec rm -f '{}' ';'
38 find . -name '*.py[cdo]' -exec rm -f '{}' ';'
37 39 rm -f MANIFEST mercurial/__version__.py mercurial/*.so tests/*.err
38 40 $(MAKE) -C doc clean
39 41
40 42 install: install-bin install-doc
41 43
42 44 install-bin: build
43 45 $(PYTHON) setup.py install --prefix="$(PREFIX)" --force
44 46
45 47 install-doc: doc
46 48 cd doc && $(MAKE) $(MFLAGS) install
47 49
48 50 install-home: install-home-bin install-home-doc
49 51
50 52 install-home-bin: build
51 53 $(PYTHON) setup.py install --home="$(HOME)" --force
52 54
53 55 install-home-doc: doc
54 56 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
55 57
56 58 MANIFEST-doc:
57 59 $(MAKE) -C doc MANIFEST
58 60
59 61 MANIFEST: MANIFEST-doc
60 62 hg manifest > MANIFEST
61 63 echo mercurial/__version__.py >> MANIFEST
62 64 cat doc/MANIFEST >> MANIFEST
63 65
64 66 dist: tests dist-notests
65 67
66 68 dist-notests: doc MANIFEST
67 69 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
68 70
69 71 tests:
70 72 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
71 73
72 74 test-%:
73 75 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
74 76
75 77
76 78 .PHONY: help all local build doc clean install install-bin install-doc \
77 79 install-home install-home-bin install-home-doc dist dist-notests tests
@@ -1,574 +1,576 b''
1 1 HGRC(5)
2 2 =======
3 3 Bryan O'Sullivan <bos@serpentine.com>
4 4
5 5 NAME
6 6 ----
7 7 hgrc - configuration files for Mercurial
8 8
9 9 SYNOPSIS
10 10 --------
11 11
12 12 The Mercurial system uses a set of configuration files to control
13 13 aspects of its behaviour.
14 14
15 15 FILES
16 16 -----
17 17
18 18 Mercurial reads configuration data from several files, if they exist.
19 19 The names of these files depend on the system on which Mercurial is
20 20 installed.
21 21
22 22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 23 (Unix) <install-root>/etc/mercurial/hgrc::
24 24 Per-installation configuration files, searched for in the
25 25 directory where Mercurial is installed. For example, if installed
26 26 in /shared/tools, Mercurial will look in
27 27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 28 all Mercurial commands executed by any user in any directory.
29 29
30 30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 31 (Unix) /etc/mercurial/hgrc::
32 32 (Windows) C:\Mercurial\Mercurial.ini::
33 33 Per-system configuration files, for the system on which Mercurial
34 34 is running. Options in these files apply to all Mercurial
35 35 commands executed by any user in any directory. Options in these
36 36 files override per-installation options.
37 37
38 38 (Unix) $HOME/.hgrc::
39 39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
40 40 (Windows) $HOME\Mercurial.ini::
41 41 Per-user configuration file, for the user running Mercurial.
42 42 Options in this file apply to all Mercurial commands executed by
43 43 any user in any directory. Options in this file override
44 44 per-installation and per-system options.
45 45 On Windows system, one of these is chosen exclusively according
46 46 to definition of HOME environment variable.
47 47
48 48 (Unix, Windows) <repo>/.hg/hgrc::
49 49 Per-repository configuration options that only apply in a
50 50 particular repository. This file is not version-controlled, and
51 51 will not get transferred during a "clone" operation. Options in
52 52 this file override options in all other configuration files.
53 53 On Unix, most of this file will be ignored if it doesn't belong
54 54 to a trusted user or to a trusted group. See the documentation
55 55 for the trusted section below for more details.
56 56
57 57 SYNTAX
58 58 ------
59 59
60 60 A configuration file consists of sections, led by a "[section]" header
61 61 and followed by "name: value" entries; "name=value" is also accepted.
62 62
63 63 [spam]
64 64 eggs=ham
65 65 green=
66 66 eggs
67 67
68 68 Each line contains one entry. If the lines that follow are indented,
69 69 they are treated as continuations of that entry.
70 70
71 71 Leading whitespace is removed from values. Empty lines are skipped.
72 72
73 73 The optional values can contain format strings which refer to other
74 74 values in the same section, or values in a special DEFAULT section.
75 75
76 76 Lines beginning with "#" or ";" are ignored and may be used to provide
77 77 comments.
78 78
79 79 SECTIONS
80 80 --------
81 81
82 82 This section describes the different sections that may appear in a
83 83 Mercurial "hgrc" file, the purpose of each section, its possible
84 84 keys, and their possible values.
85 85
86 86 decode/encode::
87 87 Filters for transforming files on checkout/checkin. This would
88 88 typically be used for newline processing or other
89 89 localization/canonicalization of files.
90 90
91 91 Filters consist of a filter pattern followed by a filter command.
92 92 Filter patterns are globs by default, rooted at the repository
93 93 root. For example, to match any file ending in ".txt" in the root
94 94 directory only, use the pattern "*.txt". To match any file ending
95 95 in ".c" anywhere in the repository, use the pattern "**.c".
96 96
97 97 The filter command can start with a specifier, either "pipe:" or
98 98 "tempfile:". If no specifier is given, "pipe:" is used by default.
99 99
100 100 A "pipe:" command must accept data on stdin and return the
101 101 transformed data on stdout.
102 102
103 103 Pipe example:
104 104
105 105 [encode]
106 106 # uncompress gzip files on checkin to improve delta compression
107 107 # note: not necessarily a good idea, just an example
108 108 *.gz = pipe: gunzip
109 109
110 110 [decode]
111 111 # recompress gzip files when writing them to the working dir (we
112 112 # can safely omit "pipe:", because it's the default)
113 113 *.gz = gzip
114 114
115 115 A "tempfile:" command is a template. The string INFILE is replaced
116 116 with the name of a temporary file that contains the data to be
117 117 filtered by the command. The string OUTFILE is replaced with the
118 118 name of an empty temporary file, where the filtered data must be
119 119 written by the command.
120 120
121 121 NOTE: the tempfile mechanism is recommended for Windows systems,
122 122 where the standard shell I/O redirection operators often have
123 123 strange effects. In particular, if you are doing line ending
124 124 conversion on Windows using the popular dos2unix and unix2dos
125 125 programs, you *must* use the tempfile mechanism, as using pipes will
126 126 corrupt the contents of your files.
127 127
128 128 Tempfile example:
129 129
130 130 [encode]
131 131 # convert files to unix line ending conventions on checkin
132 132 **.txt = tempfile: dos2unix -n INFILE OUTFILE
133 133
134 134 [decode]
135 135 # convert files to windows line ending conventions when writing
136 136 # them to the working dir
137 137 **.txt = tempfile: unix2dos -n INFILE OUTFILE
138 138
139 139 defaults::
140 140 Use the [defaults] section to define command defaults, i.e. the
141 141 default options/arguments to pass to the specified commands.
142 142
143 143 The following example makes 'hg log' run in verbose mode, and
144 144 'hg status' show only the modified files, by default.
145 145
146 146 [defaults]
147 147 log = -v
148 148 status = -m
149 149
150 150 The actual commands, instead of their aliases, must be used when
151 151 defining command defaults. The command defaults will also be
152 152 applied to the aliases of the commands defined.
153 153
154 154 diff::
155 155 Settings used when displaying diffs. They are all boolean and
156 156 defaults to False.
157 157 git;;
158 158 Use git extended diff format.
159 159 nodates;;
160 160 Don't include dates in diff headers.
161 161 showfunc;;
162 162 Show which function each change is in.
163 163 ignorews;;
164 164 Ignore white space when comparing lines.
165 165 ignorewsamount;;
166 166 Ignore changes in the amount of white space.
167 167 ignoreblanklines;;
168 168 Ignore changes whose lines are all blank.
169 169
170 170 email::
171 171 Settings for extensions that send email messages.
172 172 from;;
173 173 Optional. Email address to use in "From" header and SMTP envelope
174 174 of outgoing messages.
175 175 to;;
176 176 Optional. Comma-separated list of recipients' email addresses.
177 177 cc;;
178 178 Optional. Comma-separated list of carbon copy recipients'
179 179 email addresses.
180 180 bcc;;
181 181 Optional. Comma-separated list of blind carbon copy
182 182 recipients' email addresses. Cannot be set interactively.
183 183 method;;
184 184 Optional. Method to use to send email messages. If value is
185 185 "smtp" (default), use SMTP (see section "[smtp]" for
186 186 configuration). Otherwise, use as name of program to run that
187 187 acts like sendmail (takes "-f" option for sender, list of
188 188 recipients on command line, message on stdin). Normally, setting
189 189 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
190 190 sendmail to send messages.
191 191
192 192 Email example:
193 193
194 194 [email]
195 195 from = Joseph User <joe.user@example.com>
196 196 method = /usr/sbin/sendmail
197 197
198 198 extensions::
199 199 Mercurial has an extension mechanism for adding new features. To
200 200 enable an extension, create an entry for it in this section.
201 201
202 202 If you know that the extension is already in Python's search path,
203 203 you can give the name of the module, followed by "=", with nothing
204 204 after the "=".
205 205
206 206 Otherwise, give a name that you choose, followed by "=", followed by
207 207 the path to the ".py" file (including the file name extension) that
208 208 defines the extension.
209 209
210 210 Example for ~/.hgrc:
211 211
212 212 [extensions]
213 213 # (the mq extension will get loaded from mercurial's path)
214 214 hgext.mq =
215 215 # (this extension will get loaded from the file specified)
216 216 myfeature = ~/.hgext/myfeature.py
217 217
218 218 format::
219 219
220 220 usestore;;
221 221 Enable or disable the "store" repository format which improves
222 222 compatibility with systems that fold case or otherwise mangle
223 223 filenames. Enabled by default. Disabling this option will allow
224 224 you to store longer filenames in some situations at the expense of
225 225 compatibility.
226 226
227 227 hooks::
228 228 Commands or Python functions that get automatically executed by
229 229 various actions such as starting or finishing a commit. Multiple
230 230 hooks can be run for the same action by appending a suffix to the
231 231 action. Overriding a site-wide hook can be done by changing its
232 232 value or setting it to an empty string.
233 233
234 234 Example .hg/hgrc:
235 235
236 236 [hooks]
237 237 # do not use the site-wide hook
238 238 incoming =
239 239 incoming.email = /my/email/hook
240 240 incoming.autobuild = /my/build/hook
241 241
242 242 Most hooks are run with environment variables set that give added
243 243 useful information. For each hook below, the environment variables
244 244 it is passed are listed with names of the form "$HG_foo".
245 245
246 246 changegroup;;
247 247 Run after a changegroup has been added via push, pull or
248 248 unbundle. ID of the first new changeset is in $HG_NODE. URL from
249 249 which changes came is in $HG_URL.
250 250 commit;;
251 251 Run after a changeset has been created in the local repository.
252 252 ID of the newly created changeset is in $HG_NODE. Parent
253 253 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
254 254 incoming;;
255 255 Run after a changeset has been pulled, pushed, or unbundled into
256 256 the local repository. The ID of the newly arrived changeset is in
257 257 $HG_NODE. URL that was source of changes came is in $HG_URL.
258 258 outgoing;;
259 259 Run after sending changes from local repository to another. ID of
260 260 first changeset sent is in $HG_NODE. Source of operation is in
261 261 $HG_SOURCE; see "preoutgoing" hook for description.
262 262 prechangegroup;;
263 263 Run before a changegroup is added via push, pull or unbundle.
264 264 Exit status 0 allows the changegroup to proceed. Non-zero status
265 265 will cause the push, pull or unbundle to fail. URL from which
266 266 changes will come is in $HG_URL.
267 267 precommit;;
268 268 Run before starting a local commit. Exit status 0 allows the
269 269 commit to proceed. Non-zero status will cause the commit to fail.
270 270 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
271 271 preoutgoing;;
272 272 Run before computing changes to send from the local repository to
273 273 another. Non-zero status will cause failure. This lets you
274 274 prevent pull over http or ssh. Also prevents against local pull,
275 275 push (outbound) or bundle commands, but not effective, since you
276 276 can just copy files instead then. Source of operation is in
277 277 $HG_SOURCE. If "serve", operation is happening on behalf of
278 278 remote ssh or http repository. If "push", "pull" or "bundle",
279 279 operation is happening on behalf of repository on same system.
280 280 pretag;;
281 281 Run before creating a tag. Exit status 0 allows the tag to be
282 282 created. Non-zero status will cause the tag to fail. ID of
283 283 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
284 284 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
285 285 pretxnchangegroup;;
286 286 Run after a changegroup has been added via push, pull or unbundle,
287 287 but before the transaction has been committed. Changegroup is
288 288 visible to hook program. This lets you validate incoming changes
289 289 before accepting them. Passed the ID of the first new changeset
290 290 in $HG_NODE. Exit status 0 allows the transaction to commit.
291 291 Non-zero status will cause the transaction to be rolled back and
292 292 the push, pull or unbundle will fail. URL that was source of
293 293 changes is in $HG_URL.
294 294 pretxncommit;;
295 295 Run after a changeset has been created but the transaction not yet
296 296 committed. Changeset is visible to hook program. This lets you
297 297 validate commit message and changes. Exit status 0 allows the
298 298 commit to proceed. Non-zero status will cause the transaction to
299 299 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
300 300 IDs are in $HG_PARENT1 and $HG_PARENT2.
301 301 preupdate;;
302 302 Run before updating the working directory. Exit status 0 allows
303 303 the update to proceed. Non-zero status will prevent the update.
304 304 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
305 305 of second new parent is in $HG_PARENT2.
306 306 tag;;
307 307 Run after a tag is created. ID of tagged changeset is in
308 308 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
309 309 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
310 310 update;;
311 311 Run after updating the working directory. Changeset ID of first
312 312 new parent is in $HG_PARENT1. If merge, ID of second new parent
313 313 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
314 314 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
315 315 pre-<command>;;
316 316 Run before executing the associated command. The contents of the
317 317 command line are passed as $HG_ARGS. If the hook returns failure,
318 318 the command doesn't execute and Mercurial returns the failure code.
319 319 post-<command>;;
320 320 Run after successful invocations of the associated command. The
321 321 contents of the command line are passed as $HG_ARGS and the result
322 322 code in $HG_RESULT. Hook failure is ignored.
323 323
324 324 Note: it is generally better to use standard hooks rather than the
325 325 generic pre- and post- command hooks as they are guaranteed to be
326 326 called in the appropriate contexts for influencing transactions.
327 327 Also, hooks like "commit" will be called in all contexts that
328 328 generate a commit (eg. tag) and not just the commit command.
329 329
330 330 Note2: Environment variables with empty values may not be passed to
331 331 hooks on platforms like Windows. For instance, $HG_PARENT2 will
332 332 not be available under Windows for non-merge changesets while being
333 333 set to an empty value under Unix-like systems.
334 334
335 335 The syntax for Python hooks is as follows:
336 336
337 337 hookname = python:modulename.submodule.callable
338 338
339 339 Python hooks are run within the Mercurial process. Each hook is
340 340 called with at least three keyword arguments: a ui object (keyword
341 341 "ui"), a repository object (keyword "repo"), and a "hooktype"
342 342 keyword that tells what kind of hook is used. Arguments listed as
343 343 environment variables above are passed as keyword arguments, with no
344 344 "HG_" prefix, and names in lower case.
345 345
346 346 If a Python hook returns a "true" value or raises an exception, this
347 347 is treated as failure of the hook.
348 348
349 349 http_proxy::
350 350 Used to access web-based Mercurial repositories through a HTTP
351 351 proxy.
352 352 host;;
353 353 Host name and (optional) port of the proxy server, for example
354 354 "myproxy:8000".
355 355 no;;
356 356 Optional. Comma-separated list of host names that should bypass
357 357 the proxy.
358 358 passwd;;
359 359 Optional. Password to authenticate with at the proxy server.
360 360 user;;
361 361 Optional. User name to authenticate with at the proxy server.
362 362
363 363 smtp::
364 364 Configuration for extensions that need to send email messages.
365 365 host;;
366 366 Host name of mail server, e.g. "mail.example.com".
367 367 port;;
368 368 Optional. Port to connect to on mail server. Default: 25.
369 369 tls;;
370 370 Optional. Whether to connect to mail server using TLS. True or
371 371 False. Default: False.
372 372 username;;
373 373 Optional. User name to authenticate to SMTP server with.
374 374 If username is specified, password must also be specified.
375 375 Default: none.
376 376 password;;
377 377 Optional. Password to authenticate to SMTP server with.
378 378 If username is specified, password must also be specified.
379 379 Default: none.
380 380 local_hostname;;
381 381 Optional. It's the hostname that the sender can use to identify itself
382 382 to the MTA.
383 383
384 384 paths::
385 385 Assigns symbolic names to repositories. The left side is the
386 386 symbolic name, and the right gives the directory or URL that is the
387 387 location of the repository. Default paths can be declared by
388 388 setting the following entries.
389 389 default;;
390 390 Directory or URL to use when pulling if no source is specified.
391 391 Default is set to repository from which the current repository
392 392 was cloned.
393 393 default-push;;
394 394 Optional. Directory or URL to use when pushing if no destination
395 395 is specified.
396 396
397 397 server::
398 398 Controls generic server settings.
399 399 uncompressed;;
400 400 Whether to allow clients to clone a repo using the uncompressed
401 401 streaming protocol. This transfers about 40% more data than a
402 402 regular clone, but uses less memory and CPU on both server and
403 403 client. Over a LAN (100Mbps or better) or a very fast WAN, an
404 404 uncompressed streaming clone is a lot faster (~10x) than a regular
405 405 clone. Over most WAN connections (anything slower than about
406 406 6Mbps), uncompressed streaming is slower, because of the extra
407 407 data transfer overhead. Default is False.
408 408
409 409 trusted::
410 410 For security reasons, Mercurial will not use the settings in
411 411 the .hg/hgrc file from a repository if it doesn't belong to a
412 412 trusted user or to a trusted group. The main exception is the
413 413 web interface, which automatically uses some safe settings, since
414 414 it's common to serve repositories from different users.
415 415
416 416 This section specifies what users and groups are trusted. The
417 417 current user is always trusted. To trust everybody, list a user
418 418 or a group with name "*".
419 419
420 420 users;;
421 421 Comma-separated list of trusted users.
422 422 groups;;
423 423 Comma-separated list of trusted groups.
424 424
425 425 ui::
426 426 User interface controls.
427 427 debug;;
428 428 Print debugging information. True or False. Default is False.
429 429 editor;;
430 430 The editor to use during a commit. Default is $EDITOR or "vi".
431 431 fallbackencoding;;
432 432 Encoding to try if it's not possible to decode the changelog using
433 433 UTF-8. Default is ISO-8859-1.
434 434 ignore;;
435 435 A file to read per-user ignore patterns from. This file should be in
436 436 the same format as a repository-wide .hgignore file. This option
437 437 supports hook syntax, so if you want to specify multiple ignore
438 438 files, you can do so by setting something like
439 439 "ignore.other = ~/.hgignore2". For details of the ignore file
440 440 format, see the hgignore(5) man page.
441 441 interactive;;
442 442 Allow to prompt the user. True or False. Default is True.
443 443 logtemplate;;
444 444 Template string for commands that print changesets.
445 445 style;;
446 446 Name of style to use for command output.
447 447 merge;;
448 448 The conflict resolution program to use during a manual merge.
449 449 Default is "hgmerge".
450 450 patch;;
451 451 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
452 452 unset.
453 453 quiet;;
454 454 Reduce the amount of output printed. True or False. Default is False.
455 455 remotecmd;;
456 456 remote command to use for clone/push/pull operations. Default is 'hg'.
457 457 slash;;
458 458 Display paths using a slash ("/") as the path separator. This only
459 459 makes a difference on systems where the default path separator is not
460 460 the slash character (e.g. Windows uses the backslash character ("\")).
461 461 Default is False.
462 462 ssh;;
463 463 command to use for SSH connections. Default is 'ssh'.
464 464 strict;;
465 465 Require exact command names, instead of allowing unambiguous
466 466 abbreviations. True or False. Default is False.
467 467 timeout;;
468 468 The timeout used when a lock is held (in seconds), a negative value
469 469 means no timeout. Default is 600.
470 470 username;;
471 471 The committer of a changeset created when running "commit".
472 472 Typically a person's name and email address, e.g. "Fred Widget
473 473 <fred@example.com>". Default is $EMAIL or username@hostname.
474 474 If the username in hgrc is empty, it has to be specified manually or
475 475 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
476 476 in the system hgrc).
477 477 verbose;;
478 478 Increase the amount of output printed. True or False. Default is False.
479 479
480 480
481 481 web::
482 482 Web interface configuration.
483 483 accesslog;;
484 484 Where to output the access log. Default is stdout.
485 485 address;;
486 486 Interface address to bind to. Default is all.
487 487 allow_archive;;
488 488 List of archive format (bz2, gz, zip) allowed for downloading.
489 489 Default is empty.
490 490 allowbz2;;
491 491 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
492 492 Default is false.
493 493 allowgz;;
494 494 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
495 495 Default is false.
496 496 allowpull;;
497 497 Whether to allow pulling from the repository. Default is true.
498 498 allow_push;;
499 499 Whether to allow pushing to the repository. If empty or not set,
500 500 push is not allowed. If the special value "*", any remote user
501 501 can push, including unauthenticated users. Otherwise, the remote
502 502 user must have been authenticated, and the authenticated user name
503 503 must be present in this list (separated by whitespace or ",").
504 504 The contents of the allow_push list are examined after the
505 505 deny_push list.
506 506 allowzip;;
507 507 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
508 508 Default is false. This feature creates temporary files.
509 509 baseurl;;
510 510 Base URL to use when publishing URLs in other locations, so
511 511 third-party tools like email notification hooks can construct URLs.
512 512 Example: "http://hgserver/repos/"
513 513 contact;;
514 514 Name or email address of the person in charge of the repository.
515 515 Default is "unknown".
516 516 deny_push;;
517 517 Whether to deny pushing to the repository. If empty or not set,
518 518 push is not denied. If the special value "*", all remote users
519 519 are denied push. Otherwise, unauthenticated users are all denied,
520 520 and any authenticated user name present in this list (separated by
521 521 whitespace or ",") is also denied. The contents of the deny_push
522 522 list are examined before the allow_push list.
523 523 description;;
524 524 Textual description of the repository's purpose or contents.
525 525 Default is "unknown".
526 526 errorlog;;
527 527 Where to output the error log. Default is stderr.
528 hidden;;
529 Whether to hide the repository in the hgwebdir index. Default is false.
528 530 ipv6;;
529 531 Whether to use IPv6. Default is false.
530 532 name;;
531 533 Repository name to use in the web interface. Default is current
532 534 working directory.
533 535 maxchanges;;
534 536 Maximum number of changes to list on the changelog. Default is 10.
535 537 maxfiles;;
536 538 Maximum number of files to list per changeset. Default is 10.
537 539 port;;
538 540 Port to listen on. Default is 8000.
539 541 push_ssl;;
540 542 Whether to require that inbound pushes be transported over SSL to
541 543 prevent password sniffing. Default is true.
542 544 staticurl;;
543 545 Base URL to use for static files. If unset, static files (e.g.
544 546 the hgicon.png favicon) will be served by the CGI script itself.
545 547 Use this setting to serve them directly with the HTTP server.
546 548 Example: "http://hgserver/static/"
547 549 stripes;;
548 550 How many lines a "zebra stripe" should span in multiline output.
549 551 Default is 1; set to 0 to disable.
550 552 style;;
551 553 Which template map style to use.
552 554 templates;;
553 555 Where to find the HTML templates. Default is install path.
554 556 encoding;;
555 557 Character encoding name.
556 558 Example: "UTF-8"
557 559
558 560
559 561 AUTHOR
560 562 ------
561 563 Bryan O'Sullivan <bos@serpentine.com>.
562 564
563 565 Mercurial was written by Matt Mackall <mpm@selenic.com>.
564 566
565 567 SEE ALSO
566 568 --------
567 569 hg(1), hgignore(5)
568 570
569 571 COPYING
570 572 -------
571 573 This manual page is copyright 2005 Bryan O'Sullivan.
572 574 Mercurial is copyright 2005-2007 Matt Mackall.
573 575 Free use of this software is granted under the terms of the GNU General
574 576 Public License (GPL).
@@ -1,2303 +1,2188 b''
1 1 # queue.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 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 '''patch management and development
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use "hg help command" for more details):
18 18
19 19 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.i18n import _
33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
33 from mercurial import commands, cmdutil, hg, patch, revlog, util
34 from mercurial import repair
34 35 import os, sys, re, errno
35 36
36 37 commands.norepo += " qclone qversion"
37 38
38 39 # Patch names looks like unix-file names.
39 40 # They must be joinable with queue directory and result in the patch path.
40 41 normname = util.normpath
41 42
42 43 class statusentry:
43 44 def __init__(self, rev, name=None):
44 45 if not name:
45 46 fields = rev.split(':', 1)
46 47 if len(fields) == 2:
47 48 self.rev, self.name = fields
48 49 else:
49 50 self.rev, self.name = None, None
50 51 else:
51 52 self.rev, self.name = rev, name
52 53
53 54 def __str__(self):
54 55 return self.rev + ':' + self.name
55 56
56 57 class queue:
57 58 def __init__(self, ui, path, patchdir=None):
58 59 self.basepath = path
59 60 self.path = patchdir or os.path.join(path, "patches")
60 61 self.opener = util.opener(self.path)
61 62 self.ui = ui
62 63 self.applied = []
63 64 self.full_series = []
64 65 self.applied_dirty = 0
65 66 self.series_dirty = 0
66 67 self.series_path = "series"
67 68 self.status_path = "status"
68 69 self.guards_path = "guards"
69 70 self.active_guards = None
70 71 self.guards_dirty = False
71 72 self._diffopts = None
72 73
73 74 if os.path.exists(self.join(self.series_path)):
74 75 self.full_series = self.opener(self.series_path).read().splitlines()
75 76 self.parse_series()
76 77
77 78 if os.path.exists(self.join(self.status_path)):
78 79 lines = self.opener(self.status_path).read().splitlines()
79 80 self.applied = [statusentry(l) for l in lines]
80 81
81 82 def diffopts(self):
82 83 if self._diffopts is None:
83 84 self._diffopts = patch.diffopts(self.ui)
84 85 return self._diffopts
85 86
86 87 def join(self, *p):
87 88 return os.path.join(self.path, *p)
88 89
89 90 def find_series(self, patch):
90 91 pre = re.compile("(\s*)([^#]+)")
91 92 index = 0
92 93 for l in self.full_series:
93 94 m = pre.match(l)
94 95 if m:
95 96 s = m.group(2)
96 97 s = s.rstrip()
97 98 if s == patch:
98 99 return index
99 100 index += 1
100 101 return None
101 102
102 103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
103 104
104 105 def parse_series(self):
105 106 self.series = []
106 107 self.series_guards = []
107 108 for l in self.full_series:
108 109 h = l.find('#')
109 110 if h == -1:
110 111 patch = l
111 112 comment = ''
112 113 elif h == 0:
113 114 continue
114 115 else:
115 116 patch = l[:h]
116 117 comment = l[h:]
117 118 patch = patch.strip()
118 119 if patch:
119 120 if patch in self.series:
120 121 raise util.Abort(_('%s appears more than once in %s') %
121 122 (patch, self.join(self.series_path)))
122 123 self.series.append(patch)
123 124 self.series_guards.append(self.guard_re.findall(comment))
124 125
125 126 def check_guard(self, guard):
126 127 bad_chars = '# \t\r\n\f'
127 128 first = guard[0]
128 129 for c in '-+':
129 130 if first == c:
130 131 return (_('guard %r starts with invalid character: %r') %
131 132 (guard, c))
132 133 for c in bad_chars:
133 134 if c in guard:
134 135 return _('invalid character in guard %r: %r') % (guard, c)
135 136
136 137 def set_active(self, guards):
137 138 for guard in guards:
138 139 bad = self.check_guard(guard)
139 140 if bad:
140 141 raise util.Abort(bad)
141 142 guards = dict.fromkeys(guards).keys()
142 143 guards.sort()
143 144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
144 145 self.active_guards = guards
145 146 self.guards_dirty = True
146 147
147 148 def active(self):
148 149 if self.active_guards is None:
149 150 self.active_guards = []
150 151 try:
151 152 guards = self.opener(self.guards_path).read().split()
152 153 except IOError, err:
153 154 if err.errno != errno.ENOENT: raise
154 155 guards = []
155 156 for i, guard in enumerate(guards):
156 157 bad = self.check_guard(guard)
157 158 if bad:
158 159 self.ui.warn('%s:%d: %s\n' %
159 160 (self.join(self.guards_path), i + 1, bad))
160 161 else:
161 162 self.active_guards.append(guard)
162 163 return self.active_guards
163 164
164 165 def set_guards(self, idx, guards):
165 166 for g in guards:
166 167 if len(g) < 2:
167 168 raise util.Abort(_('guard %r too short') % g)
168 169 if g[0] not in '-+':
169 170 raise util.Abort(_('guard %r starts with invalid char') % g)
170 171 bad = self.check_guard(g[1:])
171 172 if bad:
172 173 raise util.Abort(bad)
173 174 drop = self.guard_re.sub('', self.full_series[idx])
174 175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
175 176 self.parse_series()
176 177 self.series_dirty = True
177 178
178 179 def pushable(self, idx):
179 180 if isinstance(idx, str):
180 181 idx = self.series.index(idx)
181 182 patchguards = self.series_guards[idx]
182 183 if not patchguards:
183 184 return True, None
184 185 default = False
185 186 guards = self.active()
186 187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
187 188 if exactneg:
188 189 return False, exactneg[0]
189 190 pos = [g for g in patchguards if g[0] == '+']
190 191 exactpos = [g for g in pos if g[1:] in guards]
191 192 if pos:
192 193 if exactpos:
193 194 return True, exactpos[0]
194 195 return False, pos
195 196 return True, ''
196 197
197 198 def explain_pushable(self, idx, all_patches=False):
198 199 write = all_patches and self.ui.write or self.ui.warn
199 200 if all_patches or self.ui.verbose:
200 201 if isinstance(idx, str):
201 202 idx = self.series.index(idx)
202 203 pushable, why = self.pushable(idx)
203 204 if all_patches and pushable:
204 205 if why is None:
205 206 write(_('allowing %s - no guards in effect\n') %
206 207 self.series[idx])
207 208 else:
208 209 if not why:
209 210 write(_('allowing %s - no matching negative guards\n') %
210 211 self.series[idx])
211 212 else:
212 213 write(_('allowing %s - guarded by %r\n') %
213 214 (self.series[idx], why))
214 215 if not pushable:
215 216 if why:
216 217 write(_('skipping %s - guarded by %r\n') %
217 218 (self.series[idx], why))
218 219 else:
219 220 write(_('skipping %s - no matching guards\n') %
220 221 self.series[idx])
221 222
222 223 def save_dirty(self):
223 224 def write_list(items, path):
224 225 fp = self.opener(path, 'w')
225 226 for i in items:
226 227 print >> fp, i
227 228 fp.close()
228 229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
229 230 if self.series_dirty: write_list(self.full_series, self.series_path)
230 231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
231 232
232 233 def readheaders(self, patch):
233 234 def eatdiff(lines):
234 235 while lines:
235 236 l = lines[-1]
236 237 if (l.startswith("diff -") or
237 238 l.startswith("Index:") or
238 239 l.startswith("===========")):
239 240 del lines[-1]
240 241 else:
241 242 break
242 243 def eatempty(lines):
243 244 while lines:
244 245 l = lines[-1]
245 246 if re.match('\s*$', l):
246 247 del lines[-1]
247 248 else:
248 249 break
249 250
250 251 pf = self.join(patch)
251 252 message = []
252 253 comments = []
253 254 user = None
254 255 date = None
255 256 format = None
256 257 subject = None
257 258 diffstart = 0
258 259
259 260 for line in file(pf):
260 261 line = line.rstrip()
261 262 if line.startswith('diff --git'):
262 263 diffstart = 2
263 264 break
264 265 if diffstart:
265 266 if line.startswith('+++ '):
266 267 diffstart = 2
267 268 break
268 269 if line.startswith("--- "):
269 270 diffstart = 1
270 271 continue
271 272 elif format == "hgpatch":
272 273 # parse values when importing the result of an hg export
273 274 if line.startswith("# User "):
274 275 user = line[7:]
275 276 elif line.startswith("# Date "):
276 277 date = line[7:]
277 278 elif not line.startswith("# ") and line:
278 279 message.append(line)
279 280 format = None
280 281 elif line == '# HG changeset patch':
281 282 format = "hgpatch"
282 283 elif (format != "tagdone" and (line.startswith("Subject: ") or
283 284 line.startswith("subject: "))):
284 285 subject = line[9:]
285 286 format = "tag"
286 287 elif (format != "tagdone" and (line.startswith("From: ") or
287 288 line.startswith("from: "))):
288 289 user = line[6:]
289 290 format = "tag"
290 291 elif format == "tag" and line == "":
291 292 # when looking for tags (subject: from: etc) they
292 293 # end once you find a blank line in the source
293 294 format = "tagdone"
294 295 elif message or line:
295 296 message.append(line)
296 297 comments.append(line)
297 298
298 299 eatdiff(message)
299 300 eatdiff(comments)
300 301 eatempty(message)
301 302 eatempty(comments)
302 303
303 304 # make sure message isn't empty
304 305 if format and format.startswith("tag") and subject:
305 306 message.insert(0, "")
306 307 message.insert(0, subject)
307 308 return (message, comments, user, date, diffstart > 1)
308 309
309 310 def removeundo(self, repo):
310 311 undo = repo.sjoin('undo')
311 312 if not os.path.exists(undo):
312 313 return
313 314 try:
314 315 os.unlink(undo)
315 316 except OSError, inst:
316 317 self.ui.warn('error removing undo: %s\n' % str(inst))
317 318
318 319 def printdiff(self, repo, node1, node2=None, files=None,
319 320 fp=None, changes=None, opts={}):
320 321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
321 322
322 323 patch.diff(repo, node1, node2, fns, match=matchfn,
323 324 fp=fp, changes=changes, opts=self.diffopts())
324 325
325 326 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
326 327 # first try just applying the patch
327 328 (err, n) = self.apply(repo, [ patch ], update_status=False,
328 329 strict=True, merge=rev, wlock=wlock)
329 330
330 331 if err == 0:
331 332 return (err, n)
332 333
333 334 if n is None:
334 335 raise util.Abort(_("apply failed for patch %s") % patch)
335 336
336 337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
337 338
338 339 # apply failed, strip away that rev and merge.
339 340 hg.clean(repo, head, wlock=wlock)
340 341 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
341 342
342 343 ctx = repo.changectx(rev)
343 344 ret = hg.merge(repo, rev, wlock=wlock)
344 345 if ret:
345 346 raise util.Abort(_("update returned %d") % ret)
346 347 n = repo.commit(None, ctx.description(), ctx.user(),
347 348 force=1, wlock=wlock)
348 349 if n == None:
349 350 raise util.Abort(_("repo commit failed"))
350 351 try:
351 352 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 353 except:
353 354 raise util.Abort(_("unable to read %s") % patch)
354 355
355 356 patchf = self.opener(patch, "w")
356 357 if comments:
357 358 comments = "\n".join(comments) + '\n\n'
358 359 patchf.write(comments)
359 360 self.printdiff(repo, head, n, fp=patchf)
360 361 patchf.close()
361 362 self.removeundo(repo)
362 363 return (0, n)
363 364
364 365 def qparents(self, repo, rev=None):
365 366 if rev is None:
366 367 (p1, p2) = repo.dirstate.parents()
367 368 if p2 == revlog.nullid:
368 369 return p1
369 370 if len(self.applied) == 0:
370 371 return None
371 372 return revlog.bin(self.applied[-1].rev)
372 373 pp = repo.changelog.parents(rev)
373 374 if pp[1] != revlog.nullid:
374 375 arevs = [ x.rev for x in self.applied ]
375 376 p0 = revlog.hex(pp[0])
376 377 p1 = revlog.hex(pp[1])
377 378 if p0 in arevs:
378 379 return pp[0]
379 380 if p1 in arevs:
380 381 return pp[1]
381 382 return pp[0]
382 383
383 384 def mergepatch(self, repo, mergeq, series, wlock):
384 385 if len(self.applied) == 0:
385 386 # each of the patches merged in will have two parents. This
386 387 # can confuse the qrefresh, qdiff, and strip code because it
387 388 # needs to know which parent is actually in the patch queue.
388 389 # so, we insert a merge marker with only one parent. This way
389 390 # the first patch in the queue is never a merge patch
390 391 #
391 392 pname = ".hg.patches.merge.marker"
392 393 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
393 394 wlock=wlock)
394 395 self.removeundo(repo)
395 396 self.applied.append(statusentry(revlog.hex(n), pname))
396 397 self.applied_dirty = 1
397 398
398 399 head = self.qparents(repo)
399 400
400 401 for patch in series:
401 402 patch = mergeq.lookup(patch, strict=True)
402 403 if not patch:
403 404 self.ui.warn("patch %s does not exist\n" % patch)
404 405 return (1, None)
405 406 pushable, reason = self.pushable(patch)
406 407 if not pushable:
407 408 self.explain_pushable(patch, all_patches=True)
408 409 continue
409 410 info = mergeq.isapplied(patch)
410 411 if not info:
411 412 self.ui.warn("patch %s is not applied\n" % patch)
412 413 return (1, None)
413 414 rev = revlog.bin(info[1])
414 415 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
415 416 if head:
416 417 self.applied.append(statusentry(revlog.hex(head), patch))
417 418 self.applied_dirty = 1
418 419 if err:
419 420 return (err, head)
420 421 self.save_dirty()
421 422 return (0, head)
422 423
423 424 def patch(self, repo, patchfile):
424 425 '''Apply patchfile to the working directory.
425 426 patchfile: file name of patch'''
426 427 files = {}
427 428 try:
428 429 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
429 430 files=files)
430 431 except Exception, inst:
431 432 self.ui.note(str(inst) + '\n')
432 433 if not self.ui.verbose:
433 434 self.ui.warn("patch failed, unable to continue (try -v)\n")
434 435 return (False, files, False)
435 436
436 437 return (True, files, fuzz)
437 438
438 439 def apply(self, repo, series, list=False, update_status=True,
439 440 strict=False, patchdir=None, merge=None, wlock=None,
440 441 all_files={}):
441 442 if not wlock:
442 443 wlock = repo.wlock()
443 444 lock = repo.lock()
444 445 tr = repo.transaction()
445 446 try:
446 447 ret = self._apply(tr, repo, series, list, update_status,
447 448 strict, patchdir, merge, wlock,
448 449 lock=lock, all_files=all_files)
449 450 tr.close()
450 451 self.save_dirty()
451 452 return ret
452 453 except:
453 454 try:
454 455 tr.abort()
455 456 finally:
456 457 repo.invalidate()
457 458 repo.dirstate.invalidate()
458 459 raise
459 460
460 461 def _apply(self, tr, repo, series, list=False, update_status=True,
461 462 strict=False, patchdir=None, merge=None, wlock=None,
462 463 lock=None, all_files={}):
463 464 # TODO unify with commands.py
464 465 if not patchdir:
465 466 patchdir = self.path
466 467 err = 0
467 468 n = None
468 469 for patchname in series:
469 470 pushable, reason = self.pushable(patchname)
470 471 if not pushable:
471 472 self.explain_pushable(patchname, all_patches=True)
472 473 continue
473 474 self.ui.warn("applying %s\n" % patchname)
474 475 pf = os.path.join(patchdir, patchname)
475 476
476 477 try:
477 478 message, comments, user, date, patchfound = self.readheaders(patchname)
478 479 except:
479 480 self.ui.warn("Unable to read %s\n" % patchname)
480 481 err = 1
481 482 break
482 483
483 484 if not message:
484 485 message = "imported patch %s\n" % patchname
485 486 else:
486 487 if list:
487 488 message.append("\nimported patch %s" % patchname)
488 489 message = '\n'.join(message)
489 490
490 491 (patcherr, files, fuzz) = self.patch(repo, pf)
491 492 all_files.update(files)
492 493 patcherr = not patcherr
493 494
494 495 if merge and files:
495 496 # Mark as removed/merged and update dirstate parent info
496 497 removed = []
497 498 merged = []
498 499 for f in files:
499 500 if os.path.exists(repo.dirstate.wjoin(f)):
500 501 merged.append(f)
501 502 else:
502 503 removed.append(f)
503 504 repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r')
504 505 repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm')
505 506 p1, p2 = repo.dirstate.parents()
506 507 repo.dirstate.setparents(p1, merge)
507 508 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
508 509 n = repo.commit(files, message, user, date, force=1, lock=lock,
509 510 wlock=wlock)
510 511
511 512 if n == None:
512 513 raise util.Abort(_("repo commit failed"))
513 514
514 515 if update_status:
515 516 self.applied.append(statusentry(revlog.hex(n), patchname))
516 517
517 518 if patcherr:
518 519 if not patchfound:
519 520 self.ui.warn("patch %s is empty\n" % patchname)
520 521 err = 0
521 522 else:
522 523 self.ui.warn("patch failed, rejects left in working dir\n")
523 524 err = 1
524 525 break
525 526
526 527 if fuzz and strict:
527 528 self.ui.warn("fuzz found when applying patch, stopping\n")
528 529 err = 1
529 530 break
530 531 self.removeundo(repo)
531 532 return (err, n)
532 533
533 534 def delete(self, repo, patches, opts):
534 535 realpatches = []
535 536 for patch in patches:
536 537 patch = self.lookup(patch, strict=True)
537 538 info = self.isapplied(patch)
538 539 if info:
539 540 raise util.Abort(_("cannot delete applied patch %s") % patch)
540 541 if patch not in self.series:
541 542 raise util.Abort(_("patch %s not in series file") % patch)
542 543 realpatches.append(patch)
543 544
544 545 appliedbase = 0
545 546 if opts.get('rev'):
546 547 if not self.applied:
547 548 raise util.Abort(_('no patches applied'))
548 549 revs = cmdutil.revrange(repo, opts['rev'])
549 550 if len(revs) > 1 and revs[0] > revs[1]:
550 551 revs.reverse()
551 552 for rev in revs:
552 553 if appliedbase >= len(self.applied):
553 554 raise util.Abort(_("revision %d is not managed") % rev)
554 555
555 556 base = revlog.bin(self.applied[appliedbase].rev)
556 557 node = repo.changelog.node(rev)
557 558 if node != base:
558 559 raise util.Abort(_("cannot delete revision %d above "
559 560 "applied patches") % rev)
560 561 realpatches.append(self.applied[appliedbase].name)
561 562 appliedbase += 1
562 563
563 564 if not opts.get('keep'):
564 565 r = self.qrepo()
565 566 if r:
566 567 r.remove(realpatches, True)
567 568 else:
568 569 for p in realpatches:
569 570 os.unlink(self.join(p))
570 571
571 572 if appliedbase:
572 573 del self.applied[:appliedbase]
573 574 self.applied_dirty = 1
574 575 indices = [self.find_series(p) for p in realpatches]
575 576 indices.sort()
576 577 for i in indices[-1::-1]:
577 578 del self.full_series[i]
578 579 self.parse_series()
579 580 self.series_dirty = 1
580 581
581 582 def check_toppatch(self, repo):
582 583 if len(self.applied) > 0:
583 584 top = revlog.bin(self.applied[-1].rev)
584 585 pp = repo.dirstate.parents()
585 586 if top not in pp:
586 587 raise util.Abort(_("queue top not at same revision as working directory"))
587 588 return top
588 589 return None
589 590 def check_localchanges(self, repo, force=False, refresh=True):
590 591 m, a, r, d = repo.status()[:4]
591 592 if m or a or r or d:
592 593 if not force:
593 594 if refresh:
594 595 raise util.Abort(_("local changes found, refresh first"))
595 596 else:
596 597 raise util.Abort(_("local changes found"))
597 598 return m, a, r, d
598 599 def new(self, repo, patch, msg=None, force=None):
599 600 if os.path.exists(self.join(patch)):
600 601 raise util.Abort(_('patch "%s" already exists') % patch)
601 602 m, a, r, d = self.check_localchanges(repo, force)
602 603 commitfiles = m + a + r
603 604 self.check_toppatch(repo)
604 605 wlock = repo.wlock()
605 606 insert = self.full_series_end()
606 607 if msg:
607 608 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
608 609 wlock=wlock)
609 610 else:
610 611 n = repo.commit(commitfiles,
611 612 "New patch: %s" % patch, force=True, wlock=wlock)
612 613 if n == None:
613 614 raise util.Abort(_("repo commit failed"))
614 615 self.full_series[insert:insert] = [patch]
615 616 self.applied.append(statusentry(revlog.hex(n), patch))
616 617 self.parse_series()
617 618 self.series_dirty = 1
618 619 self.applied_dirty = 1
619 620 p = self.opener(patch, "w")
620 621 if msg:
621 622 msg = msg + "\n"
622 623 p.write(msg)
623 624 p.close()
624 625 wlock = None
625 626 r = self.qrepo()
626 627 if r: r.add([patch])
627 628 if commitfiles:
628 629 self.refresh(repo, short=True)
629 630 self.removeundo(repo)
630 631
631 632 def strip(self, repo, rev, update=True, backup="all", wlock=None):
632 def limitheads(chlog, stop):
633 """return the list of all nodes that have no children"""
634 p = {}
635 h = []
636 stoprev = 0
637 if stop in chlog.nodemap:
638 stoprev = chlog.rev(stop)
639
640 for r in xrange(chlog.count() - 1, -1, -1):
641 n = chlog.node(r)
642 if n not in p:
643 h.append(n)
644 if n == stop:
645 break
646 if r < stoprev:
647 break
648 for pn in chlog.parents(n):
649 p[pn] = 1
650 return h
651
652 def bundle(cg):
653 backupdir = repo.join("strip-backup")
654 if not os.path.isdir(backupdir):
655 os.mkdir(backupdir)
656 name = os.path.join(backupdir, "%s" % revlog.short(rev))
657 name = savename(name)
658 self.ui.warn("saving bundle to %s\n" % name)
659 return changegroup.writebundle(cg, name, "HG10BZ")
660
661 def stripall(revnum):
662 mm = repo.changectx(rev).manifest()
663 seen = {}
664
665 for x in xrange(revnum, repo.changelog.count()):
666 for f in repo.changectx(x).files():
667 if f in seen:
668 continue
669 seen[f] = 1
670 if f in mm:
671 filerev = mm[f]
672 else:
673 filerev = 0
674 seen[f] = filerev
675 # we go in two steps here so the strip loop happens in a
676 # sensible order. When stripping many files, this helps keep
677 # our disk access patterns under control.
678 seen_list = seen.keys()
679 seen_list.sort()
680 for f in seen_list:
681 ff = repo.file(f)
682 filerev = seen[f]
683 if filerev != 0:
684 if filerev in ff.nodemap:
685 filerev = ff.rev(filerev)
686 else:
687 filerev = 0
688 ff.strip(filerev, revnum)
689
690 633 if not wlock:
691 634 wlock = repo.wlock()
692 635 lock = repo.lock()
693 chlog = repo.changelog
694 # TODO delete the undo files, and handle undo of merge sets
695 pp = chlog.parents(rev)
696 revnum = chlog.rev(rev)
697 636
698 637 if update:
699 638 self.check_localchanges(repo, refresh=False)
700 639 urev = self.qparents(repo, rev)
701 640 hg.clean(repo, urev, wlock=wlock)
702 641 repo.dirstate.write()
703 642
704 # save is a list of all the branches we are truncating away
705 # that we actually want to keep. changegroup will be used
706 # to preserve them and add them back after the truncate
707 saveheads = []
708 savebases = {}
709
710 heads = limitheads(chlog, rev)
711 seen = {}
712
713 # search through all the heads, finding those where the revision
714 # we want to strip away is an ancestor. Also look for merges
715 # that might be turned into new heads by the strip.
716 while heads:
717 h = heads.pop()
718 n = h
719 while True:
720 seen[n] = 1
721 pp = chlog.parents(n)
722 if pp[1] != revlog.nullid:
723 for p in pp:
724 if chlog.rev(p) > revnum and p not in seen:
725 heads.append(p)
726 if pp[0] == revlog.nullid:
727 break
728 if chlog.rev(pp[0]) < revnum:
729 break
730 n = pp[0]
731 if n == rev:
732 break
733 r = chlog.reachable(h, rev)
734 if rev not in r:
735 saveheads.append(h)
736 for x in r:
737 if chlog.rev(x) > revnum:
738 savebases[x] = 1
739
740 # create a changegroup for all the branches we need to keep
741 if backup == "all":
742 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
743 bundle(backupch)
744 if saveheads:
745 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
746 chgrpfile = bundle(backupch)
747
748 stripall(revnum)
749
750 change = chlog.read(rev)
751 chlog.strip(revnum, revnum)
752 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
753 643 self.removeundo(repo)
754 if saveheads:
755 self.ui.status("adding branch\n")
756 commands.unbundle(self.ui, repo, "file:%s" % chgrpfile,
757 update=False)
758 if backup != "strip":
759 os.unlink(chgrpfile)
644 repair.strip(self.ui, repo, rev, backup)
760 645
761 646 def isapplied(self, patch):
762 647 """returns (index, rev, patch)"""
763 648 for i in xrange(len(self.applied)):
764 649 a = self.applied[i]
765 650 if a.name == patch:
766 651 return (i, a.rev, a.name)
767 652 return None
768 653
769 654 # if the exact patch name does not exist, we try a few
770 655 # variations. If strict is passed, we try only #1
771 656 #
772 657 # 1) a number to indicate an offset in the series file
773 658 # 2) a unique substring of the patch name was given
774 659 # 3) patchname[-+]num to indicate an offset in the series file
775 660 def lookup(self, patch, strict=False):
776 661 patch = patch and str(patch)
777 662
778 663 def partial_name(s):
779 664 if s in self.series:
780 665 return s
781 666 matches = [x for x in self.series if s in x]
782 667 if len(matches) > 1:
783 668 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
784 669 for m in matches:
785 670 self.ui.warn(' %s\n' % m)
786 671 return None
787 672 if matches:
788 673 return matches[0]
789 674 if len(self.series) > 0 and len(self.applied) > 0:
790 675 if s == 'qtip':
791 676 return self.series[self.series_end(True)-1]
792 677 if s == 'qbase':
793 678 return self.series[0]
794 679 return None
795 680 if patch == None:
796 681 return None
797 682
798 683 # we don't want to return a partial match until we make
799 684 # sure the file name passed in does not exist (checked below)
800 685 res = partial_name(patch)
801 686 if res and res == patch:
802 687 return res
803 688
804 689 if not os.path.isfile(self.join(patch)):
805 690 try:
806 691 sno = int(patch)
807 692 except(ValueError, OverflowError):
808 693 pass
809 694 else:
810 695 if sno < len(self.series):
811 696 return self.series[sno]
812 697 if not strict:
813 698 # return any partial match made above
814 699 if res:
815 700 return res
816 701 minus = patch.rfind('-')
817 702 if minus >= 0:
818 703 res = partial_name(patch[:minus])
819 704 if res:
820 705 i = self.series.index(res)
821 706 try:
822 707 off = int(patch[minus+1:] or 1)
823 708 except(ValueError, OverflowError):
824 709 pass
825 710 else:
826 711 if i - off >= 0:
827 712 return self.series[i - off]
828 713 plus = patch.rfind('+')
829 714 if plus >= 0:
830 715 res = partial_name(patch[:plus])
831 716 if res:
832 717 i = self.series.index(res)
833 718 try:
834 719 off = int(patch[plus+1:] or 1)
835 720 except(ValueError, OverflowError):
836 721 pass
837 722 else:
838 723 if i + off < len(self.series):
839 724 return self.series[i + off]
840 725 raise util.Abort(_("patch %s not in series") % patch)
841 726
842 727 def push(self, repo, patch=None, force=False, list=False,
843 728 mergeq=None, wlock=None):
844 729 if not wlock:
845 730 wlock = repo.wlock()
846 731 patch = self.lookup(patch)
847 732 # Suppose our series file is: A B C and the current 'top' patch is B.
848 733 # qpush C should be performed (moving forward)
849 734 # qpush B is a NOP (no change)
850 735 # qpush A is an error (can't go backwards with qpush)
851 736 if patch:
852 737 info = self.isapplied(patch)
853 738 if info:
854 739 if info[0] < len(self.applied) - 1:
855 740 raise util.Abort(_("cannot push to a previous patch: %s") %
856 741 patch)
857 742 if info[0] < len(self.series) - 1:
858 743 self.ui.warn(_('qpush: %s is already at the top\n') % patch)
859 744 else:
860 745 self.ui.warn(_('all patches are currently applied\n'))
861 746 return
862 747
863 748 # Following the above example, starting at 'top' of B:
864 749 # qpush should be performed (pushes C), but a subsequent qpush without
865 750 # an argument is an error (nothing to apply). This allows a loop
866 751 # of "...while hg qpush..." to work as it detects an error when done
867 752 if self.series_end() == len(self.series):
868 753 self.ui.warn(_('patch series already fully applied\n'))
869 754 return 1
870 755 if not force:
871 756 self.check_localchanges(repo)
872 757
873 758 self.applied_dirty = 1;
874 759 start = self.series_end()
875 760 if start > 0:
876 761 self.check_toppatch(repo)
877 762 if not patch:
878 763 patch = self.series[start]
879 764 end = start + 1
880 765 else:
881 766 end = self.series.index(patch, start) + 1
882 767 s = self.series[start:end]
883 768 all_files = {}
884 769 try:
885 770 if mergeq:
886 771 ret = self.mergepatch(repo, mergeq, s, wlock)
887 772 else:
888 773 ret = self.apply(repo, s, list, wlock=wlock,
889 774 all_files=all_files)
890 775 except:
891 776 self.ui.warn(_('cleaning up working directory...'))
892 777 node = repo.dirstate.parents()[0]
893 778 hg.revert(repo, node, None, wlock)
894 779 unknown = repo.status(wlock=wlock)[4]
895 780 # only remove unknown files that we know we touched or
896 781 # created while patching
897 782 for f in unknown:
898 783 if f in all_files:
899 784 util.unlink(repo.wjoin(f))
900 785 self.ui.warn(_('done\n'))
901 786 raise
902 787 top = self.applied[-1].name
903 788 if ret[0]:
904 789 self.ui.write("Errors during apply, please fix and refresh %s\n" %
905 790 top)
906 791 else:
907 792 self.ui.write("Now at: %s\n" % top)
908 793 return ret[0]
909 794
910 795 def pop(self, repo, patch=None, force=False, update=True, all=False,
911 796 wlock=None):
912 797 def getfile(f, rev):
913 798 t = repo.file(f).read(rev)
914 799 repo.wfile(f, "w").write(t)
915 800
916 801 if not wlock:
917 802 wlock = repo.wlock()
918 803 if patch:
919 804 # index, rev, patch
920 805 info = self.isapplied(patch)
921 806 if not info:
922 807 patch = self.lookup(patch)
923 808 info = self.isapplied(patch)
924 809 if not info:
925 810 raise util.Abort(_("patch %s is not applied") % patch)
926 811
927 812 if len(self.applied) == 0:
928 813 # Allow qpop -a to work repeatedly,
929 814 # but not qpop without an argument
930 815 self.ui.warn(_("no patches applied\n"))
931 816 return not all
932 817
933 818 if not update:
934 819 parents = repo.dirstate.parents()
935 820 rr = [ revlog.bin(x.rev) for x in self.applied ]
936 821 for p in parents:
937 822 if p in rr:
938 823 self.ui.warn("qpop: forcing dirstate update\n")
939 824 update = True
940 825
941 826 if not force and update:
942 827 self.check_localchanges(repo)
943 828
944 829 self.applied_dirty = 1;
945 830 end = len(self.applied)
946 831 if not patch:
947 832 if all:
948 833 popi = 0
949 834 else:
950 835 popi = len(self.applied) - 1
951 836 else:
952 837 popi = info[0] + 1
953 838 if popi >= end:
954 839 self.ui.warn("qpop: %s is already at the top\n" % patch)
955 840 return
956 841 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
957 842
958 843 start = info[0]
959 844 rev = revlog.bin(info[1])
960 845
961 846 # we know there are no local changes, so we can make a simplified
962 847 # form of hg.update.
963 848 if update:
964 849 top = self.check_toppatch(repo)
965 850 qp = self.qparents(repo, rev)
966 851 changes = repo.changelog.read(qp)
967 852 mmap = repo.manifest.read(changes[0])
968 853 m, a, r, d, u = repo.status(qp, top)[:5]
969 854 if d:
970 855 raise util.Abort("deletions found between repo revs")
971 856 for f in m:
972 857 getfile(f, mmap[f])
973 858 for f in r:
974 859 getfile(f, mmap[f])
975 860 util.set_exec(repo.wjoin(f), mmap.execf(f))
976 861 repo.dirstate.update(m + r, 'n')
977 862 for f in a:
978 863 try:
979 864 os.unlink(repo.wjoin(f))
980 865 except OSError, e:
981 866 if e.errno != errno.ENOENT:
982 867 raise
983 868 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
984 869 except: pass
985 870 if a:
986 871 repo.dirstate.forget(a)
987 872 repo.dirstate.setparents(qp, revlog.nullid)
988 873 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
989 874 del self.applied[start:end]
990 875 if len(self.applied):
991 876 self.ui.write("Now at: %s\n" % self.applied[-1].name)
992 877 else:
993 878 self.ui.write("Patch queue now empty\n")
994 879
995 880 def diff(self, repo, pats, opts):
996 881 top = self.check_toppatch(repo)
997 882 if not top:
998 883 self.ui.write("No patches applied\n")
999 884 return
1000 885 qp = self.qparents(repo, top)
1001 886 if opts.get('git'):
1002 887 self.diffopts().git = True
1003 888 self.printdiff(repo, qp, files=pats, opts=opts)
1004 889
1005 890 def refresh(self, repo, pats=None, **opts):
1006 891 if len(self.applied) == 0:
1007 892 self.ui.write("No patches applied\n")
1008 893 return 1
1009 894 wlock = repo.wlock()
1010 895 self.check_toppatch(repo)
1011 896 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1012 897 top = revlog.bin(top)
1013 898 cparents = repo.changelog.parents(top)
1014 899 patchparent = self.qparents(repo, top)
1015 900 message, comments, user, date, patchfound = self.readheaders(patchfn)
1016 901
1017 902 patchf = self.opener(patchfn, "w")
1018 903 msg = opts.get('msg', '').rstrip()
1019 904 if msg:
1020 905 if comments:
1021 906 # Remove existing message.
1022 907 ci = 0
1023 908 subj = None
1024 909 for mi in xrange(len(message)):
1025 910 if comments[ci].lower().startswith('subject: '):
1026 911 subj = comments[ci][9:]
1027 912 while message[mi] != comments[ci] and message[mi] != subj:
1028 913 ci += 1
1029 914 del comments[ci]
1030 915 comments.append(msg)
1031 916 if comments:
1032 917 comments = "\n".join(comments) + '\n\n'
1033 918 patchf.write(comments)
1034 919
1035 920 if opts.get('git'):
1036 921 self.diffopts().git = True
1037 922 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1038 923 tip = repo.changelog.tip()
1039 924 if top == tip:
1040 925 # if the top of our patch queue is also the tip, there is an
1041 926 # optimization here. We update the dirstate in place and strip
1042 927 # off the tip commit. Then just commit the current directory
1043 928 # tree. We can also send repo.commit the list of files
1044 929 # changed to speed up the diff
1045 930 #
1046 931 # in short mode, we only diff the files included in the
1047 932 # patch already
1048 933 #
1049 934 # this should really read:
1050 935 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1051 936 # but we do it backwards to take advantage of manifest/chlog
1052 937 # caching against the next repo.status call
1053 938 #
1054 939 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1055 940 changes = repo.changelog.read(tip)
1056 941 man = repo.manifest.read(changes[0])
1057 942 aaa = aa[:]
1058 943 if opts.get('short'):
1059 944 filelist = mm + aa + dd
1060 945 match = dict.fromkeys(filelist).__contains__
1061 946 else:
1062 947 filelist = None
1063 948 match = util.always
1064 949 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1065 950
1066 951 # we might end up with files that were added between tip and
1067 952 # the dirstate parent, but then changed in the local dirstate.
1068 953 # in this case, we want them to only show up in the added section
1069 954 for x in m:
1070 955 if x not in aa:
1071 956 mm.append(x)
1072 957 # we might end up with files added by the local dirstate that
1073 958 # were deleted by the patch. In this case, they should only
1074 959 # show up in the changed section.
1075 960 for x in a:
1076 961 if x in dd:
1077 962 del dd[dd.index(x)]
1078 963 mm.append(x)
1079 964 else:
1080 965 aa.append(x)
1081 966 # make sure any files deleted in the local dirstate
1082 967 # are not in the add or change column of the patch
1083 968 forget = []
1084 969 for x in d + r:
1085 970 if x in aa:
1086 971 del aa[aa.index(x)]
1087 972 forget.append(x)
1088 973 continue
1089 974 elif x in mm:
1090 975 del mm[mm.index(x)]
1091 976 dd.append(x)
1092 977
1093 978 m = util.unique(mm)
1094 979 r = util.unique(dd)
1095 980 a = util.unique(aa)
1096 981 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1097 982 filelist = util.unique(c[0] + c[1] + c[2])
1098 983 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1099 984 fp=patchf, changes=c, opts=self.diffopts())
1100 985 patchf.close()
1101 986
1102 987 repo.dirstate.setparents(*cparents)
1103 988 copies = {}
1104 989 for dst in a:
1105 990 src = repo.dirstate.copied(dst)
1106 991 if src is None:
1107 992 continue
1108 993 copies.setdefault(src, []).append(dst)
1109 994 repo.dirstate.update(a, 'a')
1110 995 # remember the copies between patchparent and tip
1111 996 # this may be slow, so don't do it if we're not tracking copies
1112 997 if self.diffopts().git:
1113 998 for dst in aaa:
1114 999 f = repo.file(dst)
1115 1000 src = f.renamed(man[dst])
1116 1001 if src:
1117 1002 copies[src[0]] = copies.get(dst, [])
1118 1003 if dst in a:
1119 1004 copies[src[0]].append(dst)
1120 1005 # we can't copy a file created by the patch itself
1121 1006 if dst in copies:
1122 1007 del copies[dst]
1123 1008 for src, dsts in copies.iteritems():
1124 1009 for dst in dsts:
1125 1010 repo.dirstate.copy(src, dst)
1126 1011 repo.dirstate.update(r, 'r')
1127 1012 # if the patch excludes a modified file, mark that file with mtime=0
1128 1013 # so status can see it.
1129 1014 mm = []
1130 1015 for i in xrange(len(m)-1, -1, -1):
1131 1016 if not matchfn(m[i]):
1132 1017 mm.append(m[i])
1133 1018 del m[i]
1134 1019 repo.dirstate.update(m, 'n')
1135 1020 repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
1136 1021 repo.dirstate.forget(forget)
1137 1022
1138 1023 if not msg:
1139 1024 if not message:
1140 1025 message = "patch queue: %s\n" % patchfn
1141 1026 else:
1142 1027 message = "\n".join(message)
1143 1028 else:
1144 1029 message = msg
1145 1030
1146 1031 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1147 1032 n = repo.commit(filelist, message, changes[1], match=matchfn,
1148 1033 force=1, wlock=wlock)
1149 1034 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1150 1035 self.applied_dirty = 1
1151 1036 self.removeundo(repo)
1152 1037 else:
1153 1038 self.printdiff(repo, patchparent, fp=patchf)
1154 1039 patchf.close()
1155 1040 added = repo.status()[1]
1156 1041 for a in added:
1157 1042 f = repo.wjoin(a)
1158 1043 try:
1159 1044 os.unlink(f)
1160 1045 except OSError, e:
1161 1046 if e.errno != errno.ENOENT:
1162 1047 raise
1163 1048 try: os.removedirs(os.path.dirname(f))
1164 1049 except: pass
1165 1050 # forget the file copies in the dirstate
1166 1051 # push should readd the files later on
1167 1052 repo.dirstate.forget(added)
1168 1053 self.pop(repo, force=True, wlock=wlock)
1169 1054 self.push(repo, force=True, wlock=wlock)
1170 1055
1171 1056 def init(self, repo, create=False):
1172 1057 if not create and os.path.isdir(self.path):
1173 1058 raise util.Abort(_("patch queue directory already exists"))
1174 1059 try:
1175 1060 os.mkdir(self.path)
1176 1061 except OSError, inst:
1177 1062 if inst.errno != errno.EEXIST or not create:
1178 1063 raise
1179 1064 if create:
1180 1065 return self.qrepo(create=True)
1181 1066
1182 1067 def unapplied(self, repo, patch=None):
1183 1068 if patch and patch not in self.series:
1184 1069 raise util.Abort(_("patch %s is not in series file") % patch)
1185 1070 if not patch:
1186 1071 start = self.series_end()
1187 1072 else:
1188 1073 start = self.series.index(patch) + 1
1189 1074 unapplied = []
1190 1075 for i in xrange(start, len(self.series)):
1191 1076 pushable, reason = self.pushable(i)
1192 1077 if pushable:
1193 1078 unapplied.append((i, self.series[i]))
1194 1079 self.explain_pushable(i)
1195 1080 return unapplied
1196 1081
1197 1082 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1198 1083 summary=False):
1199 1084 def displayname(patchname):
1200 1085 if summary:
1201 1086 msg = self.readheaders(patchname)[0]
1202 1087 msg = msg and ': ' + msg[0] or ': '
1203 1088 else:
1204 1089 msg = ''
1205 1090 return '%s%s' % (patchname, msg)
1206 1091
1207 1092 applied = dict.fromkeys([p.name for p in self.applied])
1208 1093 if length is None:
1209 1094 length = len(self.series) - start
1210 1095 if not missing:
1211 1096 for i in xrange(start, start+length):
1212 1097 patch = self.series[i]
1213 1098 if patch in applied:
1214 1099 stat = 'A'
1215 1100 elif self.pushable(i)[0]:
1216 1101 stat = 'U'
1217 1102 else:
1218 1103 stat = 'G'
1219 1104 pfx = ''
1220 1105 if self.ui.verbose:
1221 1106 pfx = '%d %s ' % (i, stat)
1222 1107 elif status and status != stat:
1223 1108 continue
1224 1109 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1225 1110 else:
1226 1111 msng_list = []
1227 1112 for root, dirs, files in os.walk(self.path):
1228 1113 d = root[len(self.path) + 1:]
1229 1114 for f in files:
1230 1115 fl = os.path.join(d, f)
1231 1116 if (fl not in self.series and
1232 1117 fl not in (self.status_path, self.series_path,
1233 1118 self.guards_path)
1234 1119 and not fl.startswith('.')):
1235 1120 msng_list.append(fl)
1236 1121 msng_list.sort()
1237 1122 for x in msng_list:
1238 1123 pfx = self.ui.verbose and ('D ') or ''
1239 1124 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1240 1125
1241 1126 def issaveline(self, l):
1242 1127 if l.name == '.hg.patches.save.line':
1243 1128 return True
1244 1129
1245 1130 def qrepo(self, create=False):
1246 1131 if create or os.path.isdir(self.join(".hg")):
1247 1132 return hg.repository(self.ui, path=self.path, create=create)
1248 1133
1249 1134 def restore(self, repo, rev, delete=None, qupdate=None):
1250 1135 c = repo.changelog.read(rev)
1251 1136 desc = c[4].strip()
1252 1137 lines = desc.splitlines()
1253 1138 i = 0
1254 1139 datastart = None
1255 1140 series = []
1256 1141 applied = []
1257 1142 qpp = None
1258 1143 for i in xrange(0, len(lines)):
1259 1144 if lines[i] == 'Patch Data:':
1260 1145 datastart = i + 1
1261 1146 elif lines[i].startswith('Dirstate:'):
1262 1147 l = lines[i].rstrip()
1263 1148 l = l[10:].split(' ')
1264 1149 qpp = [ hg.bin(x) for x in l ]
1265 1150 elif datastart != None:
1266 1151 l = lines[i].rstrip()
1267 1152 se = statusentry(l)
1268 1153 file_ = se.name
1269 1154 if se.rev:
1270 1155 applied.append(se)
1271 1156 else:
1272 1157 series.append(file_)
1273 1158 if datastart == None:
1274 1159 self.ui.warn("No saved patch data found\n")
1275 1160 return 1
1276 1161 self.ui.warn("restoring status: %s\n" % lines[0])
1277 1162 self.full_series = series
1278 1163 self.applied = applied
1279 1164 self.parse_series()
1280 1165 self.series_dirty = 1
1281 1166 self.applied_dirty = 1
1282 1167 heads = repo.changelog.heads()
1283 1168 if delete:
1284 1169 if rev not in heads:
1285 1170 self.ui.warn("save entry has children, leaving it alone\n")
1286 1171 else:
1287 1172 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1288 1173 pp = repo.dirstate.parents()
1289 1174 if rev in pp:
1290 1175 update = True
1291 1176 else:
1292 1177 update = False
1293 1178 self.strip(repo, rev, update=update, backup='strip')
1294 1179 if qpp:
1295 1180 self.ui.warn("saved queue repository parents: %s %s\n" %
1296 1181 (hg.short(qpp[0]), hg.short(qpp[1])))
1297 1182 if qupdate:
1298 1183 print "queue directory updating"
1299 1184 r = self.qrepo()
1300 1185 if not r:
1301 1186 self.ui.warn("Unable to load queue repository\n")
1302 1187 return 1
1303 1188 hg.clean(r, qpp[0])
1304 1189
1305 1190 def save(self, repo, msg=None):
1306 1191 if len(self.applied) == 0:
1307 1192 self.ui.warn("save: no patches applied, exiting\n")
1308 1193 return 1
1309 1194 if self.issaveline(self.applied[-1]):
1310 1195 self.ui.warn("status is already saved\n")
1311 1196 return 1
1312 1197
1313 1198 ar = [ ':' + x for x in self.full_series ]
1314 1199 if not msg:
1315 1200 msg = "hg patches saved state"
1316 1201 else:
1317 1202 msg = "hg patches: " + msg.rstrip('\r\n')
1318 1203 r = self.qrepo()
1319 1204 if r:
1320 1205 pp = r.dirstate.parents()
1321 1206 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1322 1207 msg += "\n\nPatch Data:\n"
1323 1208 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1324 1209 "\n".join(ar) + '\n' or "")
1325 1210 n = repo.commit(None, text, user=None, force=1)
1326 1211 if not n:
1327 1212 self.ui.warn("repo commit failed\n")
1328 1213 return 1
1329 1214 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1330 1215 self.applied_dirty = 1
1331 1216 self.removeundo(repo)
1332 1217
1333 1218 def full_series_end(self):
1334 1219 if len(self.applied) > 0:
1335 1220 p = self.applied[-1].name
1336 1221 end = self.find_series(p)
1337 1222 if end == None:
1338 1223 return len(self.full_series)
1339 1224 return end + 1
1340 1225 return 0
1341 1226
1342 1227 def series_end(self, all_patches=False):
1343 1228 """If all_patches is False, return the index of the next pushable patch
1344 1229 in the series, or the series length. If all_patches is True, return the
1345 1230 index of the first patch past the last applied one.
1346 1231 """
1347 1232 end = 0
1348 1233 def next(start):
1349 1234 if all_patches:
1350 1235 return start
1351 1236 i = start
1352 1237 while i < len(self.series):
1353 1238 p, reason = self.pushable(i)
1354 1239 if p:
1355 1240 break
1356 1241 self.explain_pushable(i)
1357 1242 i += 1
1358 1243 return i
1359 1244 if len(self.applied) > 0:
1360 1245 p = self.applied[-1].name
1361 1246 try:
1362 1247 end = self.series.index(p)
1363 1248 except ValueError:
1364 1249 return 0
1365 1250 return next(end + 1)
1366 1251 return next(end)
1367 1252
1368 1253 def appliedname(self, index):
1369 1254 pname = self.applied[index].name
1370 1255 if not self.ui.verbose:
1371 1256 p = pname
1372 1257 else:
1373 1258 p = str(self.series.index(pname)) + " " + pname
1374 1259 return p
1375 1260
1376 1261 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1377 1262 force=None, git=False):
1378 1263 def checkseries(patchname):
1379 1264 if patchname in self.series:
1380 1265 raise util.Abort(_('patch %s is already in the series file')
1381 1266 % patchname)
1382 1267 def checkfile(patchname):
1383 1268 if not force and os.path.exists(self.join(patchname)):
1384 1269 raise util.Abort(_('patch "%s" already exists')
1385 1270 % patchname)
1386 1271
1387 1272 if rev:
1388 1273 if files:
1389 1274 raise util.Abort(_('option "-r" not valid when importing '
1390 1275 'files'))
1391 1276 rev = cmdutil.revrange(repo, rev)
1392 1277 rev.sort(lambda x, y: cmp(y, x))
1393 1278 if (len(files) > 1 or len(rev) > 1) and patchname:
1394 1279 raise util.Abort(_('option "-n" not valid when importing multiple '
1395 1280 'patches'))
1396 1281 i = 0
1397 1282 added = []
1398 1283 if rev:
1399 1284 # If mq patches are applied, we can only import revisions
1400 1285 # that form a linear path to qbase.
1401 1286 # Otherwise, they should form a linear path to a head.
1402 1287 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1403 1288 if len(heads) > 1:
1404 1289 raise util.Abort(_('revision %d is the root of more than one '
1405 1290 'branch') % rev[-1])
1406 1291 if self.applied:
1407 1292 base = revlog.hex(repo.changelog.node(rev[0]))
1408 1293 if base in [n.rev for n in self.applied]:
1409 1294 raise util.Abort(_('revision %d is already managed')
1410 1295 % rev[0])
1411 1296 if heads != [revlog.bin(self.applied[-1].rev)]:
1412 1297 raise util.Abort(_('revision %d is not the parent of '
1413 1298 'the queue') % rev[0])
1414 1299 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1415 1300 lastparent = repo.changelog.parentrevs(base)[0]
1416 1301 else:
1417 1302 if heads != [repo.changelog.node(rev[0])]:
1418 1303 raise util.Abort(_('revision %d has unmanaged children')
1419 1304 % rev[0])
1420 1305 lastparent = None
1421 1306
1422 1307 if git:
1423 1308 self.diffopts().git = True
1424 1309
1425 1310 for r in rev:
1426 1311 p1, p2 = repo.changelog.parentrevs(r)
1427 1312 n = repo.changelog.node(r)
1428 1313 if p2 != revlog.nullrev:
1429 1314 raise util.Abort(_('cannot import merge revision %d') % r)
1430 1315 if lastparent and lastparent != r:
1431 1316 raise util.Abort(_('revision %d is not the parent of %d')
1432 1317 % (r, lastparent))
1433 1318 lastparent = p1
1434 1319
1435 1320 if not patchname:
1436 1321 patchname = normname('%d.diff' % r)
1437 1322 checkseries(patchname)
1438 1323 checkfile(patchname)
1439 1324 self.full_series.insert(0, patchname)
1440 1325
1441 1326 patchf = self.opener(patchname, "w")
1442 1327 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1443 1328 patchf.close()
1444 1329
1445 1330 se = statusentry(revlog.hex(n), patchname)
1446 1331 self.applied.insert(0, se)
1447 1332
1448 1333 added.append(patchname)
1449 1334 patchname = None
1450 1335 self.parse_series()
1451 1336 self.applied_dirty = 1
1452 1337
1453 1338 for filename in files:
1454 1339 if existing:
1455 1340 if filename == '-':
1456 1341 raise util.Abort(_('-e is incompatible with import from -'))
1457 1342 if not patchname:
1458 1343 patchname = normname(filename)
1459 1344 if not os.path.isfile(self.join(patchname)):
1460 1345 raise util.Abort(_("patch %s does not exist") % patchname)
1461 1346 else:
1462 1347 try:
1463 1348 if filename == '-':
1464 1349 if not patchname:
1465 1350 raise util.Abort(_('need --name to import a patch from -'))
1466 1351 text = sys.stdin.read()
1467 1352 else:
1468 1353 text = file(filename).read()
1469 1354 except IOError:
1470 1355 raise util.Abort(_("unable to read %s") % patchname)
1471 1356 if not patchname:
1472 1357 patchname = normname(os.path.basename(filename))
1473 1358 checkfile(patchname)
1474 1359 patchf = self.opener(patchname, "w")
1475 1360 patchf.write(text)
1476 1361 checkseries(patchname)
1477 1362 index = self.full_series_end() + i
1478 1363 self.full_series[index:index] = [patchname]
1479 1364 self.parse_series()
1480 1365 self.ui.warn("adding %s to series file\n" % patchname)
1481 1366 i += 1
1482 1367 added.append(patchname)
1483 1368 patchname = None
1484 1369 self.series_dirty = 1
1485 1370 qrepo = self.qrepo()
1486 1371 if qrepo:
1487 1372 qrepo.add(added)
1488 1373
1489 1374 def delete(ui, repo, *patches, **opts):
1490 1375 """remove patches from queue
1491 1376
1492 1377 With --rev, mq will stop managing the named revisions. The
1493 1378 patches must be applied and at the base of the stack. This option
1494 1379 is useful when the patches have been applied upstream.
1495 1380
1496 1381 Otherwise, the patches must not be applied.
1497 1382
1498 1383 With --keep, the patch files are preserved in the patch directory."""
1499 1384 q = repo.mq
1500 1385 q.delete(repo, patches, opts)
1501 1386 q.save_dirty()
1502 1387 return 0
1503 1388
1504 1389 def applied(ui, repo, patch=None, **opts):
1505 1390 """print the patches already applied"""
1506 1391 q = repo.mq
1507 1392 if patch:
1508 1393 if patch not in q.series:
1509 1394 raise util.Abort(_("patch %s is not in series file") % patch)
1510 1395 end = q.series.index(patch) + 1
1511 1396 else:
1512 1397 end = q.series_end(True)
1513 1398 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1514 1399
1515 1400 def unapplied(ui, repo, patch=None, **opts):
1516 1401 """print the patches not yet applied"""
1517 1402 q = repo.mq
1518 1403 if patch:
1519 1404 if patch not in q.series:
1520 1405 raise util.Abort(_("patch %s is not in series file") % patch)
1521 1406 start = q.series.index(patch) + 1
1522 1407 else:
1523 1408 start = q.series_end(True)
1524 1409 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1525 1410
1526 1411 def qimport(ui, repo, *filename, **opts):
1527 1412 """import a patch
1528 1413
1529 1414 The patch will have the same name as its source file unless you
1530 1415 give it a new one with --name.
1531 1416
1532 1417 You can register an existing patch inside the patch directory
1533 1418 with the --existing flag.
1534 1419
1535 1420 With --force, an existing patch of the same name will be overwritten.
1536 1421
1537 1422 An existing changeset may be placed under mq control with --rev
1538 1423 (e.g. qimport --rev tip -n patch will place tip under mq control).
1539 1424 With --git, patches imported with --rev will use the git diff
1540 1425 format.
1541 1426 """
1542 1427 q = repo.mq
1543 1428 q.qimport(repo, filename, patchname=opts['name'],
1544 1429 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1545 1430 git=opts['git'])
1546 1431 q.save_dirty()
1547 1432 return 0
1548 1433
1549 1434 def init(ui, repo, **opts):
1550 1435 """init a new queue repository
1551 1436
1552 1437 The queue repository is unversioned by default. If -c is
1553 1438 specified, qinit will create a separate nested repository
1554 1439 for patches (qinit -c may also be run later to convert
1555 1440 an unversioned patch repository into a versioned one).
1556 1441 You can use qcommit to commit changes to this queue repository."""
1557 1442 q = repo.mq
1558 1443 r = q.init(repo, create=opts['create_repo'])
1559 1444 q.save_dirty()
1560 1445 if r:
1561 1446 if not os.path.exists(r.wjoin('.hgignore')):
1562 1447 fp = r.wopener('.hgignore', 'w')
1563 1448 fp.write('syntax: glob\n')
1564 1449 fp.write('status\n')
1565 1450 fp.write('guards\n')
1566 1451 fp.close()
1567 1452 if not os.path.exists(r.wjoin('series')):
1568 1453 r.wopener('series', 'w').close()
1569 1454 r.add(['.hgignore', 'series'])
1570 1455 commands.add(ui, r)
1571 1456 return 0
1572 1457
1573 1458 def clone(ui, source, dest=None, **opts):
1574 1459 '''clone main and patch repository at same time
1575 1460
1576 1461 If source is local, destination will have no patches applied. If
1577 1462 source is remote, this command can not check if patches are
1578 1463 applied in source, so cannot guarantee that patches are not
1579 1464 applied in destination. If you clone remote repository, be sure
1580 1465 before that it has no patches applied.
1581 1466
1582 1467 Source patch repository is looked for in <src>/.hg/patches by
1583 1468 default. Use -p <url> to change.
1584 1469 '''
1585 1470 cmdutil.setremoteconfig(ui, opts)
1586 1471 if dest is None:
1587 1472 dest = hg.defaultdest(source)
1588 1473 sr = hg.repository(ui, ui.expandpath(source))
1589 1474 qbase, destrev = None, None
1590 1475 if sr.local():
1591 1476 if sr.mq.applied:
1592 1477 qbase = revlog.bin(sr.mq.applied[0].rev)
1593 1478 if not hg.islocal(dest):
1594 1479 heads = dict.fromkeys(sr.heads())
1595 1480 for h in sr.heads(qbase):
1596 1481 del heads[h]
1597 1482 destrev = heads.keys()
1598 1483 destrev.append(sr.changelog.parents(qbase)[0])
1599 1484 ui.note(_('cloning main repo\n'))
1600 1485 sr, dr = hg.clone(ui, sr.url(), dest,
1601 1486 pull=opts['pull'],
1602 1487 rev=destrev,
1603 1488 update=False,
1604 1489 stream=opts['uncompressed'])
1605 1490 ui.note(_('cloning patch repo\n'))
1606 1491 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1607 1492 dr.url() + '/.hg/patches',
1608 1493 pull=opts['pull'],
1609 1494 update=not opts['noupdate'],
1610 1495 stream=opts['uncompressed'])
1611 1496 if dr.local():
1612 1497 if qbase:
1613 1498 ui.note(_('stripping applied patches from destination repo\n'))
1614 1499 dr.mq.strip(dr, qbase, update=False, backup=None)
1615 1500 if not opts['noupdate']:
1616 1501 ui.note(_('updating destination repo\n'))
1617 1502 hg.update(dr, dr.changelog.tip())
1618 1503
1619 1504 def commit(ui, repo, *pats, **opts):
1620 1505 """commit changes in the queue repository"""
1621 1506 q = repo.mq
1622 1507 r = q.qrepo()
1623 1508 if not r: raise util.Abort('no queue repository')
1624 1509 commands.commit(r.ui, r, *pats, **opts)
1625 1510
1626 1511 def series(ui, repo, **opts):
1627 1512 """print the entire series file"""
1628 1513 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1629 1514 return 0
1630 1515
1631 1516 def top(ui, repo, **opts):
1632 1517 """print the name of the current patch"""
1633 1518 q = repo.mq
1634 1519 t = q.applied and q.series_end(True) or 0
1635 1520 if t:
1636 1521 return q.qseries(repo, start=t-1, length=1, status='A',
1637 1522 summary=opts.get('summary'))
1638 1523 else:
1639 1524 ui.write("No patches applied\n")
1640 1525 return 1
1641 1526
1642 1527 def next(ui, repo, **opts):
1643 1528 """print the name of the next patch"""
1644 1529 q = repo.mq
1645 1530 end = q.series_end()
1646 1531 if end == len(q.series):
1647 1532 ui.write("All patches applied\n")
1648 1533 return 1
1649 1534 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1650 1535
1651 1536 def prev(ui, repo, **opts):
1652 1537 """print the name of the previous patch"""
1653 1538 q = repo.mq
1654 1539 l = len(q.applied)
1655 1540 if l == 1:
1656 1541 ui.write("Only one patch applied\n")
1657 1542 return 1
1658 1543 if not l:
1659 1544 ui.write("No patches applied\n")
1660 1545 return 1
1661 1546 return q.qseries(repo, start=l-2, length=1, status='A',
1662 1547 summary=opts.get('summary'))
1663 1548
1664 1549 def new(ui, repo, patch, **opts):
1665 1550 """create a new patch
1666 1551
1667 1552 qnew creates a new patch on top of the currently-applied patch
1668 1553 (if any). It will refuse to run if there are any outstanding
1669 1554 changes unless -f is specified, in which case the patch will
1670 1555 be initialised with them.
1671 1556
1672 1557 -e, -m or -l set the patch header as well as the commit message.
1673 1558 If none is specified, the patch header is empty and the
1674 1559 commit message is 'New patch: PATCH'"""
1675 1560 q = repo.mq
1676 1561 message = cmdutil.logmessage(opts)
1677 1562 if opts['edit']:
1678 1563 message = ui.edit(message, ui.username())
1679 1564 q.new(repo, patch, msg=message, force=opts['force'])
1680 1565 q.save_dirty()
1681 1566 return 0
1682 1567
1683 1568 def refresh(ui, repo, *pats, **opts):
1684 1569 """update the current patch
1685 1570
1686 1571 If any file patterns are provided, the refreshed patch will contain only
1687 1572 the modifications that match those patterns; the remaining modifications
1688 1573 will remain in the working directory.
1689 1574
1690 1575 hg add/remove/copy/rename work as usual, though you might want to use
1691 1576 git-style patches (--git or [diff] git=1) to track copies and renames.
1692 1577 """
1693 1578 q = repo.mq
1694 1579 message = cmdutil.logmessage(opts)
1695 1580 if opts['edit']:
1696 1581 if message:
1697 1582 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1698 1583 patch = q.applied[-1].name
1699 1584 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1700 1585 message = ui.edit('\n'.join(message), user or ui.username())
1701 1586 ret = q.refresh(repo, pats, msg=message, **opts)
1702 1587 q.save_dirty()
1703 1588 return ret
1704 1589
1705 1590 def diff(ui, repo, *pats, **opts):
1706 1591 """diff of the current patch"""
1707 1592 repo.mq.diff(repo, pats, opts)
1708 1593 return 0
1709 1594
1710 1595 def fold(ui, repo, *files, **opts):
1711 1596 """fold the named patches into the current patch
1712 1597
1713 1598 Patches must not yet be applied. Each patch will be successively
1714 1599 applied to the current patch in the order given. If all the
1715 1600 patches apply successfully, the current patch will be refreshed
1716 1601 with the new cumulative patch, and the folded patches will
1717 1602 be deleted. With -k/--keep, the folded patch files will not
1718 1603 be removed afterwards.
1719 1604
1720 1605 The header for each folded patch will be concatenated with
1721 1606 the current patch header, separated by a line of '* * *'."""
1722 1607
1723 1608 q = repo.mq
1724 1609
1725 1610 if not files:
1726 1611 raise util.Abort(_('qfold requires at least one patch name'))
1727 1612 if not q.check_toppatch(repo):
1728 1613 raise util.Abort(_('No patches applied'))
1729 1614
1730 1615 message = cmdutil.logmessage(opts)
1731 1616 if opts['edit']:
1732 1617 if message:
1733 1618 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1734 1619
1735 1620 parent = q.lookup('qtip')
1736 1621 patches = []
1737 1622 messages = []
1738 1623 for f in files:
1739 1624 p = q.lookup(f)
1740 1625 if p in patches or p == parent:
1741 1626 ui.warn(_('Skipping already folded patch %s') % p)
1742 1627 if q.isapplied(p):
1743 1628 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1744 1629 patches.append(p)
1745 1630
1746 1631 for p in patches:
1747 1632 if not message:
1748 1633 messages.append(q.readheaders(p)[0])
1749 1634 pf = q.join(p)
1750 1635 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1751 1636 if not patchsuccess:
1752 1637 raise util.Abort(_('Error folding patch %s') % p)
1753 1638 patch.updatedir(ui, repo, files)
1754 1639
1755 1640 if not message:
1756 1641 message, comments, user = q.readheaders(parent)[0:3]
1757 1642 for msg in messages:
1758 1643 message.append('* * *')
1759 1644 message.extend(msg)
1760 1645 message = '\n'.join(message)
1761 1646
1762 1647 if opts['edit']:
1763 1648 message = ui.edit(message, user or ui.username())
1764 1649
1765 1650 q.refresh(repo, msg=message)
1766 1651 q.delete(repo, patches, opts)
1767 1652 q.save_dirty()
1768 1653
1769 1654 def goto(ui, repo, patch, **opts):
1770 1655 '''push or pop patches until named patch is at top of stack'''
1771 1656 q = repo.mq
1772 1657 patch = q.lookup(patch)
1773 1658 if q.isapplied(patch):
1774 1659 ret = q.pop(repo, patch, force=opts['force'])
1775 1660 else:
1776 1661 ret = q.push(repo, patch, force=opts['force'])
1777 1662 q.save_dirty()
1778 1663 return ret
1779 1664
1780 1665 def guard(ui, repo, *args, **opts):
1781 1666 '''set or print guards for a patch
1782 1667
1783 1668 Guards control whether a patch can be pushed. A patch with no
1784 1669 guards is always pushed. A patch with a positive guard ("+foo") is
1785 1670 pushed only if the qselect command has activated it. A patch with
1786 1671 a negative guard ("-foo") is never pushed if the qselect command
1787 1672 has activated it.
1788 1673
1789 1674 With no arguments, print the currently active guards.
1790 1675 With arguments, set guards for the named patch.
1791 1676
1792 1677 To set a negative guard "-foo" on topmost patch ("--" is needed so
1793 1678 hg will not interpret "-foo" as an option):
1794 1679 hg qguard -- -foo
1795 1680
1796 1681 To set guards on another patch:
1797 1682 hg qguard other.patch +2.6.17 -stable
1798 1683 '''
1799 1684 def status(idx):
1800 1685 guards = q.series_guards[idx] or ['unguarded']
1801 1686 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1802 1687 q = repo.mq
1803 1688 patch = None
1804 1689 args = list(args)
1805 1690 if opts['list']:
1806 1691 if args or opts['none']:
1807 1692 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1808 1693 for i in xrange(len(q.series)):
1809 1694 status(i)
1810 1695 return
1811 1696 if not args or args[0][0:1] in '-+':
1812 1697 if not q.applied:
1813 1698 raise util.Abort(_('no patches applied'))
1814 1699 patch = q.applied[-1].name
1815 1700 if patch is None and args[0][0:1] not in '-+':
1816 1701 patch = args.pop(0)
1817 1702 if patch is None:
1818 1703 raise util.Abort(_('no patch to work with'))
1819 1704 if args or opts['none']:
1820 1705 idx = q.find_series(patch)
1821 1706 if idx is None:
1822 1707 raise util.Abort(_('no patch named %s') % patch)
1823 1708 q.set_guards(idx, args)
1824 1709 q.save_dirty()
1825 1710 else:
1826 1711 status(q.series.index(q.lookup(patch)))
1827 1712
1828 1713 def header(ui, repo, patch=None):
1829 1714 """Print the header of the topmost or specified patch"""
1830 1715 q = repo.mq
1831 1716
1832 1717 if patch:
1833 1718 patch = q.lookup(patch)
1834 1719 else:
1835 1720 if not q.applied:
1836 1721 ui.write('No patches applied\n')
1837 1722 return 1
1838 1723 patch = q.lookup('qtip')
1839 1724 message = repo.mq.readheaders(patch)[0]
1840 1725
1841 1726 ui.write('\n'.join(message) + '\n')
1842 1727
1843 1728 def lastsavename(path):
1844 1729 (directory, base) = os.path.split(path)
1845 1730 names = os.listdir(directory)
1846 1731 namere = re.compile("%s.([0-9]+)" % base)
1847 1732 maxindex = None
1848 1733 maxname = None
1849 1734 for f in names:
1850 1735 m = namere.match(f)
1851 1736 if m:
1852 1737 index = int(m.group(1))
1853 1738 if maxindex == None or index > maxindex:
1854 1739 maxindex = index
1855 1740 maxname = f
1856 1741 if maxname:
1857 1742 return (os.path.join(directory, maxname), maxindex)
1858 1743 return (None, None)
1859 1744
1860 1745 def savename(path):
1861 1746 (last, index) = lastsavename(path)
1862 1747 if last is None:
1863 1748 index = 0
1864 1749 newpath = path + ".%d" % (index + 1)
1865 1750 return newpath
1866 1751
1867 1752 def push(ui, repo, patch=None, **opts):
1868 1753 """push the next patch onto the stack"""
1869 1754 q = repo.mq
1870 1755 mergeq = None
1871 1756
1872 1757 if opts['all']:
1873 1758 if not q.series:
1874 1759 ui.warn(_('no patches in series\n'))
1875 1760 return 0
1876 1761 patch = q.series[-1]
1877 1762 if opts['merge']:
1878 1763 if opts['name']:
1879 1764 newpath = opts['name']
1880 1765 else:
1881 1766 newpath, i = lastsavename(q.path)
1882 1767 if not newpath:
1883 1768 ui.warn("no saved queues found, please use -n\n")
1884 1769 return 1
1885 1770 mergeq = queue(ui, repo.join(""), newpath)
1886 1771 ui.warn("merging with queue at: %s\n" % mergeq.path)
1887 1772 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1888 1773 mergeq=mergeq)
1889 1774 return ret
1890 1775
1891 1776 def pop(ui, repo, patch=None, **opts):
1892 1777 """pop the current patch off the stack"""
1893 1778 localupdate = True
1894 1779 if opts['name']:
1895 1780 q = queue(ui, repo.join(""), repo.join(opts['name']))
1896 1781 ui.warn('using patch queue: %s\n' % q.path)
1897 1782 localupdate = False
1898 1783 else:
1899 1784 q = repo.mq
1900 1785 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1901 1786 all=opts['all'])
1902 1787 q.save_dirty()
1903 1788 return ret
1904 1789
1905 1790 def rename(ui, repo, patch, name=None, **opts):
1906 1791 """rename a patch
1907 1792
1908 1793 With one argument, renames the current patch to PATCH1.
1909 1794 With two arguments, renames PATCH1 to PATCH2."""
1910 1795
1911 1796 q = repo.mq
1912 1797
1913 1798 if not name:
1914 1799 name = patch
1915 1800 patch = None
1916 1801
1917 1802 if patch:
1918 1803 patch = q.lookup(patch)
1919 1804 else:
1920 1805 if not q.applied:
1921 1806 ui.write(_('No patches applied\n'))
1922 1807 return
1923 1808 patch = q.lookup('qtip')
1924 1809 absdest = q.join(name)
1925 1810 if os.path.isdir(absdest):
1926 1811 name = normname(os.path.join(name, os.path.basename(patch)))
1927 1812 absdest = q.join(name)
1928 1813 if os.path.exists(absdest):
1929 1814 raise util.Abort(_('%s already exists') % absdest)
1930 1815
1931 1816 if name in q.series:
1932 1817 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1933 1818
1934 1819 if ui.verbose:
1935 1820 ui.write('Renaming %s to %s\n' % (patch, name))
1936 1821 i = q.find_series(patch)
1937 1822 guards = q.guard_re.findall(q.full_series[i])
1938 1823 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1939 1824 q.parse_series()
1940 1825 q.series_dirty = 1
1941 1826
1942 1827 info = q.isapplied(patch)
1943 1828 if info:
1944 1829 q.applied[info[0]] = statusentry(info[1], name)
1945 1830 q.applied_dirty = 1
1946 1831
1947 1832 util.rename(q.join(patch), absdest)
1948 1833 r = q.qrepo()
1949 1834 if r:
1950 1835 wlock = r.wlock()
1951 1836 if r.dirstate.state(name) == 'r':
1952 1837 r.undelete([name], wlock)
1953 1838 r.copy(patch, name, wlock)
1954 1839 r.remove([patch], False, wlock)
1955 1840
1956 1841 q.save_dirty()
1957 1842
1958 1843 def restore(ui, repo, rev, **opts):
1959 1844 """restore the queue state saved by a rev"""
1960 1845 rev = repo.lookup(rev)
1961 1846 q = repo.mq
1962 1847 q.restore(repo, rev, delete=opts['delete'],
1963 1848 qupdate=opts['update'])
1964 1849 q.save_dirty()
1965 1850 return 0
1966 1851
1967 1852 def save(ui, repo, **opts):
1968 1853 """save current queue state"""
1969 1854 q = repo.mq
1970 1855 message = cmdutil.logmessage(opts)
1971 1856 ret = q.save(repo, msg=message)
1972 1857 if ret:
1973 1858 return ret
1974 1859 q.save_dirty()
1975 1860 if opts['copy']:
1976 1861 path = q.path
1977 1862 if opts['name']:
1978 1863 newpath = os.path.join(q.basepath, opts['name'])
1979 1864 if os.path.exists(newpath):
1980 1865 if not os.path.isdir(newpath):
1981 1866 raise util.Abort(_('destination %s exists and is not '
1982 1867 'a directory') % newpath)
1983 1868 if not opts['force']:
1984 1869 raise util.Abort(_('destination %s exists, '
1985 1870 'use -f to force') % newpath)
1986 1871 else:
1987 1872 newpath = savename(path)
1988 1873 ui.warn("copy %s to %s\n" % (path, newpath))
1989 1874 util.copyfiles(path, newpath)
1990 1875 if opts['empty']:
1991 1876 try:
1992 1877 os.unlink(q.join(q.status_path))
1993 1878 except:
1994 1879 pass
1995 1880 return 0
1996 1881
1997 1882 def strip(ui, repo, rev, **opts):
1998 1883 """strip a revision and all later revs on the same branch"""
1999 1884 rev = repo.lookup(rev)
2000 1885 backup = 'all'
2001 1886 if opts['backup']:
2002 1887 backup = 'strip'
2003 1888 elif opts['nobackup']:
2004 1889 backup = 'none'
2005 1890 update = repo.dirstate.parents()[0] != revlog.nullid
2006 1891 repo.mq.strip(repo, rev, backup=backup, update=update)
2007 1892 return 0
2008 1893
2009 1894 def select(ui, repo, *args, **opts):
2010 1895 '''set or print guarded patches to push
2011 1896
2012 1897 Use the qguard command to set or print guards on patch, then use
2013 1898 qselect to tell mq which guards to use. A patch will be pushed if it
2014 1899 has no guards or any positive guards match the currently selected guard,
2015 1900 but will not be pushed if any negative guards match the current guard.
2016 1901 For example:
2017 1902
2018 1903 qguard foo.patch -stable (negative guard)
2019 1904 qguard bar.patch +stable (positive guard)
2020 1905 qselect stable
2021 1906
2022 1907 This activates the "stable" guard. mq will skip foo.patch (because
2023 1908 it has a negative match) but push bar.patch (because it
2024 1909 has a positive match).
2025 1910
2026 1911 With no arguments, prints the currently active guards.
2027 1912 With one argument, sets the active guard.
2028 1913
2029 1914 Use -n/--none to deactivate guards (no other arguments needed).
2030 1915 When no guards are active, patches with positive guards are skipped
2031 1916 and patches with negative guards are pushed.
2032 1917
2033 1918 qselect can change the guards on applied patches. It does not pop
2034 1919 guarded patches by default. Use --pop to pop back to the last applied
2035 1920 patch that is not guarded. Use --reapply (which implies --pop) to push
2036 1921 back to the current patch afterwards, but skip guarded patches.
2037 1922
2038 1923 Use -s/--series to print a list of all guards in the series file (no
2039 1924 other arguments needed). Use -v for more information.'''
2040 1925
2041 1926 q = repo.mq
2042 1927 guards = q.active()
2043 1928 if args or opts['none']:
2044 1929 old_unapplied = q.unapplied(repo)
2045 1930 old_guarded = [i for i in xrange(len(q.applied)) if
2046 1931 not q.pushable(i)[0]]
2047 1932 q.set_active(args)
2048 1933 q.save_dirty()
2049 1934 if not args:
2050 1935 ui.status(_('guards deactivated\n'))
2051 1936 if not opts['pop'] and not opts['reapply']:
2052 1937 unapplied = q.unapplied(repo)
2053 1938 guarded = [i for i in xrange(len(q.applied))
2054 1939 if not q.pushable(i)[0]]
2055 1940 if len(unapplied) != len(old_unapplied):
2056 1941 ui.status(_('number of unguarded, unapplied patches has '
2057 1942 'changed from %d to %d\n') %
2058 1943 (len(old_unapplied), len(unapplied)))
2059 1944 if len(guarded) != len(old_guarded):
2060 1945 ui.status(_('number of guarded, applied patches has changed '
2061 1946 'from %d to %d\n') %
2062 1947 (len(old_guarded), len(guarded)))
2063 1948 elif opts['series']:
2064 1949 guards = {}
2065 1950 noguards = 0
2066 1951 for gs in q.series_guards:
2067 1952 if not gs:
2068 1953 noguards += 1
2069 1954 for g in gs:
2070 1955 guards.setdefault(g, 0)
2071 1956 guards[g] += 1
2072 1957 if ui.verbose:
2073 1958 guards['NONE'] = noguards
2074 1959 guards = guards.items()
2075 1960 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2076 1961 if guards:
2077 1962 ui.note(_('guards in series file:\n'))
2078 1963 for guard, count in guards:
2079 1964 ui.note('%2d ' % count)
2080 1965 ui.write(guard, '\n')
2081 1966 else:
2082 1967 ui.note(_('no guards in series file\n'))
2083 1968 else:
2084 1969 if guards:
2085 1970 ui.note(_('active guards:\n'))
2086 1971 for g in guards:
2087 1972 ui.write(g, '\n')
2088 1973 else:
2089 1974 ui.write(_('no active guards\n'))
2090 1975 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2091 1976 popped = False
2092 1977 if opts['pop'] or opts['reapply']:
2093 1978 for i in xrange(len(q.applied)):
2094 1979 pushable, reason = q.pushable(i)
2095 1980 if not pushable:
2096 1981 ui.status(_('popping guarded patches\n'))
2097 1982 popped = True
2098 1983 if i == 0:
2099 1984 q.pop(repo, all=True)
2100 1985 else:
2101 1986 q.pop(repo, i-1)
2102 1987 break
2103 1988 if popped:
2104 1989 try:
2105 1990 if reapply:
2106 1991 ui.status(_('reapplying unguarded patches\n'))
2107 1992 q.push(repo, reapply)
2108 1993 finally:
2109 1994 q.save_dirty()
2110 1995
2111 1996 def reposetup(ui, repo):
2112 1997 class mqrepo(repo.__class__):
2113 1998 def abort_if_wdir_patched(self, errmsg, force=False):
2114 1999 if self.mq.applied and not force:
2115 2000 parent = revlog.hex(self.dirstate.parents()[0])
2116 2001 if parent in [s.rev for s in self.mq.applied]:
2117 2002 raise util.Abort(errmsg)
2118 2003
2119 2004 def commit(self, *args, **opts):
2120 2005 if len(args) >= 6:
2121 2006 force = args[5]
2122 2007 else:
2123 2008 force = opts.get('force')
2124 2009 self.abort_if_wdir_patched(
2125 2010 _('cannot commit over an applied mq patch'),
2126 2011 force)
2127 2012
2128 2013 return super(mqrepo, self).commit(*args, **opts)
2129 2014
2130 2015 def push(self, remote, force=False, revs=None):
2131 2016 if self.mq.applied and not force and not revs:
2132 2017 raise util.Abort(_('source has mq patches applied'))
2133 2018 return super(mqrepo, self).push(remote, force, revs)
2134 2019
2135 2020 def tags(self):
2136 2021 if self.tagscache:
2137 2022 return self.tagscache
2138 2023
2139 2024 tagscache = super(mqrepo, self).tags()
2140 2025
2141 2026 q = self.mq
2142 2027 if not q.applied:
2143 2028 return tagscache
2144 2029
2145 2030 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2146 2031 mqtags.append((mqtags[-1][0], 'qtip'))
2147 2032 mqtags.append((mqtags[0][0], 'qbase'))
2148 2033 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2149 2034 for patch in mqtags:
2150 2035 if patch[1] in tagscache:
2151 2036 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2152 2037 else:
2153 2038 tagscache[patch[1]] = patch[0]
2154 2039
2155 2040 return tagscache
2156 2041
2157 2042 def _branchtags(self):
2158 2043 q = self.mq
2159 2044 if not q.applied:
2160 2045 return super(mqrepo, self)._branchtags()
2161 2046
2162 2047 self.branchcache = {} # avoid recursion in changectx
2163 2048 cl = self.changelog
2164 2049 partial, last, lrev = self._readbranchcache()
2165 2050
2166 2051 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2167 2052 start = lrev + 1
2168 2053 if start < qbase:
2169 2054 # update the cache (excluding the patches) and save it
2170 2055 self._updatebranchcache(partial, lrev+1, qbase)
2171 2056 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2172 2057 start = qbase
2173 2058 # if start = qbase, the cache is as updated as it should be.
2174 2059 # if start > qbase, the cache includes (part of) the patches.
2175 2060 # we might as well use it, but we won't save it.
2176 2061
2177 2062 # update the cache up to the tip
2178 2063 self._updatebranchcache(partial, start, cl.count())
2179 2064
2180 2065 return partial
2181 2066
2182 2067 if repo.local():
2183 2068 repo.__class__ = mqrepo
2184 2069 repo.mq = queue(ui, repo.join(""))
2185 2070
2186 2071 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2187 2072
2188 2073 cmdtable = {
2189 2074 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2190 2075 "qclone": (clone,
2191 2076 [('', 'pull', None, _('use pull protocol to copy metadata')),
2192 2077 ('U', 'noupdate', None, _('do not update the new working directories')),
2193 2078 ('', 'uncompressed', None,
2194 2079 _('use uncompressed transfer (fast over LAN)')),
2195 2080 ('e', 'ssh', '', _('specify ssh command to use')),
2196 2081 ('p', 'patches', '', _('location of source patch repo')),
2197 2082 ('', 'remotecmd', '',
2198 2083 _('specify hg command to run on the remote side'))],
2199 2084 'hg qclone [OPTION]... SOURCE [DEST]'),
2200 2085 "qcommit|qci":
2201 2086 (commit,
2202 2087 commands.table["^commit|ci"][1],
2203 2088 'hg qcommit [OPTION]... [FILE]...'),
2204 2089 "^qdiff": (diff,
2205 2090 [('g', 'git', None, _('use git extended diff format')),
2206 2091 ('I', 'include', [], _('include names matching the given patterns')),
2207 2092 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2208 2093 'hg qdiff [-I] [-X] [FILE]...'),
2209 2094 "qdelete|qremove|qrm":
2210 2095 (delete,
2211 2096 [('k', 'keep', None, _('keep patch file')),
2212 2097 ('r', 'rev', [], _('stop managing a revision'))],
2213 2098 'hg qdelete [-k] [-r REV]... PATCH...'),
2214 2099 'qfold':
2215 2100 (fold,
2216 2101 [('e', 'edit', None, _('edit patch header')),
2217 2102 ('k', 'keep', None, _('keep folded patch files'))
2218 2103 ] + commands.commitopts,
2219 2104 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2220 2105 'qgoto': (goto, [('f', 'force', None, _('overwrite any local changes'))],
2221 2106 'hg qgoto [OPT]... PATCH'),
2222 2107 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2223 2108 ('n', 'none', None, _('drop all guards'))],
2224 2109 'hg qguard [PATCH] [+GUARD]... [-GUARD]...'),
2225 2110 'qheader': (header, [],
2226 2111 _('hg qheader [PATCH]')),
2227 2112 "^qimport":
2228 2113 (qimport,
2229 2114 [('e', 'existing', None, 'import file in patch dir'),
2230 2115 ('n', 'name', '', 'patch file name'),
2231 2116 ('f', 'force', None, 'overwrite existing files'),
2232 2117 ('r', 'rev', [], 'place existing revisions under mq control'),
2233 2118 ('g', 'git', None, _('use git extended diff format'))],
2234 2119 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2235 2120 "^qinit":
2236 2121 (init,
2237 2122 [('c', 'create-repo', None, 'create queue repository')],
2238 2123 'hg qinit [-c]'),
2239 2124 "qnew":
2240 2125 (new,
2241 2126 [('e', 'edit', None, _('edit commit message')),
2242 2127 ('f', 'force', None, _('import uncommitted changes into patch'))
2243 2128 ] + commands.commitopts,
2244 2129 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2245 2130 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2246 2131 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2247 2132 "^qpop":
2248 2133 (pop,
2249 2134 [('a', 'all', None, 'pop all patches'),
2250 2135 ('n', 'name', '', 'queue name to pop'),
2251 2136 ('f', 'force', None, 'forget any local changes')],
2252 2137 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2253 2138 "^qpush":
2254 2139 (push,
2255 2140 [('f', 'force', None, 'apply if the patch has rejects'),
2256 2141 ('l', 'list', None, 'list patch name in commit text'),
2257 2142 ('a', 'all', None, 'apply all patches'),
2258 2143 ('m', 'merge', None, 'merge from another queue'),
2259 2144 ('n', 'name', '', 'merge queue name')],
2260 2145 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2261 2146 "^qrefresh":
2262 2147 (refresh,
2263 2148 [('e', 'edit', None, _('edit commit message')),
2264 2149 ('g', 'git', None, _('use git extended diff format')),
2265 2150 ('s', 'short', None, 'refresh only files already in the patch'),
2266 2151 ('I', 'include', [], _('include names matching the given patterns')),
2267 2152 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2268 2153 ] + commands.commitopts,
2269 2154 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2270 2155 'qrename|qmv':
2271 2156 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2272 2157 "qrestore":
2273 2158 (restore,
2274 2159 [('d', 'delete', None, 'delete save entry'),
2275 2160 ('u', 'update', None, 'update queue working dir')],
2276 2161 'hg qrestore [-d] [-u] REV'),
2277 2162 "qsave":
2278 2163 (save,
2279 2164 [('c', 'copy', None, 'copy patch directory'),
2280 2165 ('n', 'name', '', 'copy directory name'),
2281 2166 ('e', 'empty', None, 'clear queue status file'),
2282 2167 ('f', 'force', None, 'force copy')] + commands.commitopts,
2283 2168 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2284 2169 "qselect": (select,
2285 2170 [('n', 'none', None, _('disable all guards')),
2286 2171 ('s', 'series', None, _('list all guards in series file')),
2287 2172 ('', 'pop', None,
2288 2173 _('pop to before first guarded applied patch')),
2289 2174 ('', 'reapply', None, _('pop, then reapply patches'))],
2290 2175 'hg qselect [OPTION]... [GUARD]...'),
2291 2176 "qseries":
2292 2177 (series,
2293 2178 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2294 2179 'hg qseries [-ms]'),
2295 2180 "^strip":
2296 2181 (strip,
2297 2182 [('f', 'force', None, 'force multi-head removal'),
2298 2183 ('b', 'backup', None, 'bundle unrelated changesets'),
2299 2184 ('n', 'nobackup', None, 'no backups')],
2300 2185 'hg strip [-f] [-b] [-n] REV'),
2301 2186 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2302 2187 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2303 2188 }
@@ -1,237 +1,240 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, 2006 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 import demandimport; demandimport.enable()
10 10 import os, mimetools, cStringIO
11 11 from mercurial.i18n import gettext as _
12 12 from mercurial import ui, hg, util, templater
13 13 from common import get_mtime, staticfile, style_map, paritygen
14 14 from hgweb_mod import hgweb
15 15
16 16 # This is a stopgap
17 17 class hgwebdir(object):
18 18 def __init__(self, config, parentui=None):
19 19 def cleannames(items):
20 20 return [(name.strip(os.sep), path) for name, path in items]
21 21
22 22 self.parentui = parentui
23 23 self.motd = None
24 24 self.style = None
25 25 self.stripecount = None
26 26 self.repos_sorted = ('name', False)
27 27 if isinstance(config, (list, tuple)):
28 28 self.repos = cleannames(config)
29 29 self.repos_sorted = ('', False)
30 30 elif isinstance(config, dict):
31 31 self.repos = cleannames(config.items())
32 32 self.repos.sort()
33 33 else:
34 34 if isinstance(config, util.configparser):
35 35 cp = config
36 36 else:
37 37 cp = util.configparser()
38 38 cp.read(config)
39 39 self.repos = []
40 40 if cp.has_section('web'):
41 41 if cp.has_option('web', 'motd'):
42 42 self.motd = cp.get('web', 'motd')
43 43 if cp.has_option('web', 'style'):
44 44 self.style = cp.get('web', 'style')
45 45 if cp.has_option('web', 'stripes'):
46 46 self.stripecount = int(cp.get('web', 'stripes'))
47 47 if cp.has_section('paths'):
48 48 self.repos.extend(cleannames(cp.items('paths')))
49 49 if cp.has_section('collections'):
50 50 for prefix, root in cp.items('collections'):
51 51 for path in util.walkrepos(root):
52 52 repo = os.path.normpath(path)
53 53 name = repo
54 54 if name.startswith(prefix):
55 55 name = name[len(prefix):]
56 56 self.repos.append((name.lstrip(os.sep), repo))
57 57 self.repos.sort()
58 58
59 59 def run(self):
60 60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
61 61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
62 62 import mercurial.hgweb.wsgicgi as wsgicgi
63 63 from request import wsgiapplication
64 64 def make_web_app():
65 65 return self
66 66 wsgicgi.launch(wsgiapplication(make_web_app))
67 67
68 68 def run_wsgi(self, req):
69 69 def header(**map):
70 70 header_file = cStringIO.StringIO(
71 71 ''.join(tmpl("header", encoding=util._encoding, **map)))
72 72 msg = mimetools.Message(header_file, 0)
73 73 req.header(msg.items())
74 74 yield header_file.read()
75 75
76 76 def footer(**map):
77 77 yield tmpl("footer", **map)
78 78
79 79 def motd(**map):
80 80 if self.motd is not None:
81 81 yield self.motd
82 82 else:
83 83 yield config('web', 'motd', '')
84 84
85 85 parentui = self.parentui or ui.ui(report_untrusted=False)
86 86
87 87 def config(section, name, default=None, untrusted=True):
88 88 return parentui.config(section, name, default, untrusted)
89 89
90 90 url = req.env['REQUEST_URI'].split('?')[0]
91 91 if not url.endswith('/'):
92 92 url += '/'
93 93
94 94 staticurl = config('web', 'staticurl') or url + 'static/'
95 95 if not staticurl.endswith('/'):
96 96 staticurl += '/'
97 97
98 98 style = self.style
99 99 if style is None:
100 100 style = config('web', 'style', '')
101 101 if req.form.has_key('style'):
102 102 style = req.form['style'][0]
103 103 if self.stripecount is None:
104 104 self.stripecount = int(config('web', 'stripes', 1))
105 105 mapfile = style_map(templater.templatepath(), style)
106 106 tmpl = templater.templater(mapfile, templater.common_filters,
107 107 defaults={"header": header,
108 108 "footer": footer,
109 109 "motd": motd,
110 110 "url": url,
111 111 "staticurl": staticurl})
112 112
113 113 def archivelist(ui, nodeid, url):
114 114 allowed = ui.configlist("web", "allow_archive", untrusted=True)
115 115 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
116 116 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
117 117 untrusted=True):
118 118 yield {"type" : i[0], "extension": i[1],
119 119 "node": nodeid, "url": url}
120 120
121 121 def entries(sortcolumn="", descending=False, **map):
122 122 def sessionvars(**map):
123 123 fields = []
124 124 if req.form.has_key('style'):
125 125 style = req.form['style'][0]
126 126 if style != get('web', 'style', ''):
127 127 fields.append(('style', style))
128 128
129 129 separator = url[-1] == '?' and ';' or '?'
130 130 for name, value in fields:
131 131 yield dict(name=name, value=value, separator=separator)
132 132 separator = ';'
133 133
134 134 rows = []
135 135 parity = paritygen(self.stripecount)
136 136 for name, path in self.repos:
137 137 u = ui.ui(parentui=parentui)
138 138 try:
139 139 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
140 140 except IOError:
141 141 pass
142 142 def get(section, name, default=None):
143 143 return u.config(section, name, default, untrusted=True)
144 144
145 if u.configbool("web", "hidden", untrusted=True):
146 continue
147
145 148 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
146 149 .replace("//", "/")) + '/'
147 150
148 151 # update time with local timezone
149 152 try:
150 153 d = (get_mtime(path), util.makedate()[1])
151 154 except OSError:
152 155 continue
153 156
154 157 contact = (get("ui", "username") or # preferred
155 158 get("web", "contact") or # deprecated
156 159 get("web", "author", "")) # also
157 160 description = get("web", "description", "")
158 161 name = get("web", "name", name)
159 162 row = dict(contact=contact or "unknown",
160 163 contact_sort=contact.upper() or "unknown",
161 164 name=name,
162 165 name_sort=name,
163 166 url=url,
164 167 description=description or "unknown",
165 168 description_sort=description.upper() or "unknown",
166 169 lastchange=d,
167 170 lastchange_sort=d[1]-d[0],
168 171 sessionvars=sessionvars,
169 172 archives=archivelist(u, "tip", url))
170 173 if (not sortcolumn
171 174 or (sortcolumn, descending) == self.repos_sorted):
172 175 # fast path for unsorted output
173 176 row['parity'] = parity.next()
174 177 yield row
175 178 else:
176 179 rows.append((row["%s_sort" % sortcolumn], row))
177 180 if rows:
178 181 rows.sort()
179 182 if descending:
180 183 rows.reverse()
181 184 for key, row in rows:
182 185 row['parity'] = parity.next()
183 186 yield row
184 187
185 188 try:
186 189 virtual = req.env.get("PATH_INFO", "").strip('/')
187 190 if virtual.startswith('static/'):
188 191 static = os.path.join(templater.templatepath(), 'static')
189 192 fname = virtual[7:]
190 193 req.write(staticfile(static, fname, req) or
191 194 tmpl('error', error='%r not found' % fname))
192 195 elif virtual:
193 196 while virtual:
194 197 real = dict(self.repos).get(virtual)
195 198 if real:
196 199 break
197 200 up = virtual.rfind('/')
198 201 if up < 0:
199 202 break
200 203 virtual = virtual[:up]
201 204 if real:
202 205 req.env['REPO_NAME'] = virtual
203 206 try:
204 207 repo = hg.repository(parentui, real)
205 208 hgweb(repo).run_wsgi(req)
206 209 except IOError, inst:
207 210 req.write(tmpl("error", error=inst.strerror))
208 211 except hg.RepoError, inst:
209 212 req.write(tmpl("error", error=str(inst)))
210 213 else:
211 214 req.write(tmpl("notfound", repo=virtual))
212 215 else:
213 216 if req.form.has_key('static'):
214 217 static = os.path.join(templater.templatepath(), "static")
215 218 fname = req.form['static'][0]
216 219 req.write(staticfile(static, fname, req)
217 220 or tmpl("error", error="%r not found" % fname))
218 221 else:
219 222 sortable = ["name", "description", "contact", "lastchange"]
220 223 sortcolumn, descending = self.repos_sorted
221 224 if req.form.has_key('sort'):
222 225 sortcolumn = req.form['sort'][0]
223 226 descending = sortcolumn.startswith('-')
224 227 if descending:
225 228 sortcolumn = sortcolumn[1:]
226 229 if sortcolumn not in sortable:
227 230 sortcolumn = ""
228 231
229 232 sort = [("sort_%s" % column,
230 233 "%s%s" % ((not descending and column == sortcolumn)
231 234 and "-" or "", column))
232 235 for column in sortable]
233 236 req.write(tmpl("index", entries=entries,
234 237 sortcolumn=sortcolumn, descending=descending,
235 238 **dict(sort)))
236 239 finally:
237 240 tmpl = None
@@ -1,1550 +1,1551 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 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
17 17 import os, threading, time, calendar, ConfigParser, locale, glob
18 18
19 19 try:
20 20 set = set
21 21 frozenset = frozenset
22 22 except NameError:
23 23 from sets import Set as set, ImmutableSet as frozenset
24 24
25 25 try:
26 26 _encoding = os.environ.get("HGENCODING")
27 27 if sys.platform == 'darwin' and not _encoding:
28 28 # On darwin, getpreferredencoding ignores the locale environment and
29 29 # always returns mac-roman. We override this if the environment is
30 30 # not C (has been customized by the user).
31 31 locale.setlocale(locale.LC_CTYPE, '')
32 32 _encoding = locale.getlocale()[1]
33 33 if not _encoding:
34 34 _encoding = locale.getpreferredencoding() or 'ascii'
35 35 except locale.Error:
36 36 _encoding = 'ascii'
37 37 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
38 38 _fallbackencoding = 'ISO-8859-1'
39 39
40 40 def tolocal(s):
41 41 """
42 42 Convert a string from internal UTF-8 to local encoding
43 43
44 44 All internal strings should be UTF-8 but some repos before the
45 45 implementation of locale support may contain latin1 or possibly
46 46 other character sets. We attempt to decode everything strictly
47 47 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
48 48 replace unknown characters.
49 49 """
50 50 for e in ('UTF-8', _fallbackencoding):
51 51 try:
52 52 u = s.decode(e) # attempt strict decoding
53 53 return u.encode(_encoding, "replace")
54 54 except LookupError, k:
55 55 raise Abort(_("%s, please check your locale settings") % k)
56 56 except UnicodeDecodeError:
57 57 pass
58 58 u = s.decode("utf-8", "replace") # last ditch
59 59 return u.encode(_encoding, "replace")
60 60
61 61 def fromlocal(s):
62 62 """
63 63 Convert a string from the local character encoding to UTF-8
64 64
65 65 We attempt to decode strings using the encoding mode set by
66 66 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
67 67 characters will cause an error message. Other modes include
68 68 'replace', which replaces unknown characters with a special
69 69 Unicode character, and 'ignore', which drops the character.
70 70 """
71 71 try:
72 72 return s.decode(_encoding, _encodingmode).encode("utf-8")
73 73 except UnicodeDecodeError, inst:
74 74 sub = s[max(0, inst.start-10):inst.start+10]
75 75 raise Abort("decoding near '%s': %s!" % (sub, inst))
76 76 except LookupError, k:
77 77 raise Abort(_("%s, please check your locale settings") % k)
78 78
79 79 def locallen(s):
80 80 """Find the length in characters of a local string"""
81 81 return len(s.decode(_encoding, "replace"))
82 82
83 83 def localsub(s, a, b=None):
84 84 try:
85 85 u = s.decode(_encoding, _encodingmode)
86 86 if b is not None:
87 87 u = u[a:b]
88 88 else:
89 89 u = u[:a]
90 90 return u.encode(_encoding, _encodingmode)
91 91 except UnicodeDecodeError, inst:
92 92 sub = s[max(0, inst.start-10), inst.start+10]
93 93 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
94 94
95 95 # used by parsedate
96 96 defaultdateformats = (
97 97 '%Y-%m-%d %H:%M:%S',
98 98 '%Y-%m-%d %I:%M:%S%p',
99 99 '%Y-%m-%d %H:%M',
100 100 '%Y-%m-%d %I:%M%p',
101 101 '%Y-%m-%d',
102 102 '%m-%d',
103 103 '%m/%d',
104 104 '%m/%d/%y',
105 105 '%m/%d/%Y',
106 106 '%a %b %d %H:%M:%S %Y',
107 107 '%a %b %d %I:%M:%S%p %Y',
108 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
108 109 '%b %d %H:%M:%S %Y',
109 110 '%b %d %I:%M:%S%p %Y',
110 111 '%b %d %H:%M:%S',
111 112 '%b %d %I:%M:%S%p',
112 113 '%b %d %H:%M',
113 114 '%b %d %I:%M%p',
114 115 '%b %d %Y',
115 116 '%b %d',
116 117 '%H:%M:%S',
117 118 '%I:%M:%SP',
118 119 '%H:%M',
119 120 '%I:%M%p',
120 121 )
121 122
122 123 extendeddateformats = defaultdateformats + (
123 124 "%Y",
124 125 "%Y-%m",
125 126 "%b",
126 127 "%b %Y",
127 128 )
128 129
129 130 class SignalInterrupt(Exception):
130 131 """Exception raised on SIGTERM and SIGHUP."""
131 132
132 133 # differences from SafeConfigParser:
133 134 # - case-sensitive keys
134 135 # - allows values that are not strings (this means that you may not
135 136 # be able to save the configuration to a file)
136 137 class configparser(ConfigParser.SafeConfigParser):
137 138 def optionxform(self, optionstr):
138 139 return optionstr
139 140
140 141 def set(self, section, option, value):
141 142 return ConfigParser.ConfigParser.set(self, section, option, value)
142 143
143 144 def _interpolate(self, section, option, rawval, vars):
144 145 if not isinstance(rawval, basestring):
145 146 return rawval
146 147 return ConfigParser.SafeConfigParser._interpolate(self, section,
147 148 option, rawval, vars)
148 149
149 150 def cachefunc(func):
150 151 '''cache the result of function calls'''
151 152 # XXX doesn't handle keywords args
152 153 cache = {}
153 154 if func.func_code.co_argcount == 1:
154 155 # we gain a small amount of time because
155 156 # we don't need to pack/unpack the list
156 157 def f(arg):
157 158 if arg not in cache:
158 159 cache[arg] = func(arg)
159 160 return cache[arg]
160 161 else:
161 162 def f(*args):
162 163 if args not in cache:
163 164 cache[args] = func(*args)
164 165 return cache[args]
165 166
166 167 return f
167 168
168 169 def pipefilter(s, cmd):
169 170 '''filter string S through command CMD, returning its output'''
170 171 (pin, pout) = os.popen2(cmd, 'b')
171 172 def writer():
172 173 try:
173 174 pin.write(s)
174 175 pin.close()
175 176 except IOError, inst:
176 177 if inst.errno != errno.EPIPE:
177 178 raise
178 179
179 180 # we should use select instead on UNIX, but this will work on most
180 181 # systems, including Windows
181 182 w = threading.Thread(target=writer)
182 183 w.start()
183 184 f = pout.read()
184 185 pout.close()
185 186 w.join()
186 187 return f
187 188
188 189 def tempfilter(s, cmd):
189 190 '''filter string S through a pair of temporary files with CMD.
190 191 CMD is used as a template to create the real command to be run,
191 192 with the strings INFILE and OUTFILE replaced by the real names of
192 193 the temporary files generated.'''
193 194 inname, outname = None, None
194 195 try:
195 196 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
196 197 fp = os.fdopen(infd, 'wb')
197 198 fp.write(s)
198 199 fp.close()
199 200 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
200 201 os.close(outfd)
201 202 cmd = cmd.replace('INFILE', inname)
202 203 cmd = cmd.replace('OUTFILE', outname)
203 204 code = os.system(cmd)
204 205 if code: raise Abort(_("command '%s' failed: %s") %
205 206 (cmd, explain_exit(code)))
206 207 return open(outname, 'rb').read()
207 208 finally:
208 209 try:
209 210 if inname: os.unlink(inname)
210 211 except: pass
211 212 try:
212 213 if outname: os.unlink(outname)
213 214 except: pass
214 215
215 216 filtertable = {
216 217 'tempfile:': tempfilter,
217 218 'pipe:': pipefilter,
218 219 }
219 220
220 221 def filter(s, cmd):
221 222 "filter a string through a command that transforms its input to its output"
222 223 for name, fn in filtertable.iteritems():
223 224 if cmd.startswith(name):
224 225 return fn(s, cmd[len(name):].lstrip())
225 226 return pipefilter(s, cmd)
226 227
227 228 def binary(s):
228 229 """return true if a string is binary data using diff's heuristic"""
229 230 if s and '\0' in s[:4096]:
230 231 return True
231 232 return False
232 233
233 234 def unique(g):
234 235 """return the uniq elements of iterable g"""
235 236 seen = {}
236 237 l = []
237 238 for f in g:
238 239 if f not in seen:
239 240 seen[f] = 1
240 241 l.append(f)
241 242 return l
242 243
243 244 class Abort(Exception):
244 245 """Raised if a command needs to print an error and exit."""
245 246
246 247 class UnexpectedOutput(Abort):
247 248 """Raised to print an error with part of output and exit."""
248 249
249 250 def always(fn): return True
250 251 def never(fn): return False
251 252
252 253 def expand_glob(pats):
253 254 '''On Windows, expand the implicit globs in a list of patterns'''
254 255 if os.name != 'nt':
255 256 return list(pats)
256 257 ret = []
257 258 for p in pats:
258 259 kind, name = patkind(p, None)
259 260 if kind is None:
260 261 globbed = glob.glob(name)
261 262 if globbed:
262 263 ret.extend(globbed)
263 264 continue
264 265 # if we couldn't expand the glob, just keep it around
265 266 ret.append(p)
266 267 return ret
267 268
268 269 def patkind(name, dflt_pat='glob'):
269 270 """Split a string into an optional pattern kind prefix and the
270 271 actual pattern."""
271 272 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
272 273 if name.startswith(prefix + ':'): return name.split(':', 1)
273 274 return dflt_pat, name
274 275
275 276 def globre(pat, head='^', tail='$'):
276 277 "convert a glob pattern into a regexp"
277 278 i, n = 0, len(pat)
278 279 res = ''
279 280 group = False
280 281 def peek(): return i < n and pat[i]
281 282 while i < n:
282 283 c = pat[i]
283 284 i = i+1
284 285 if c == '*':
285 286 if peek() == '*':
286 287 i += 1
287 288 res += '.*'
288 289 else:
289 290 res += '[^/]*'
290 291 elif c == '?':
291 292 res += '.'
292 293 elif c == '[':
293 294 j = i
294 295 if j < n and pat[j] in '!]':
295 296 j += 1
296 297 while j < n and pat[j] != ']':
297 298 j += 1
298 299 if j >= n:
299 300 res += '\\['
300 301 else:
301 302 stuff = pat[i:j].replace('\\','\\\\')
302 303 i = j + 1
303 304 if stuff[0] == '!':
304 305 stuff = '^' + stuff[1:]
305 306 elif stuff[0] == '^':
306 307 stuff = '\\' + stuff
307 308 res = '%s[%s]' % (res, stuff)
308 309 elif c == '{':
309 310 group = True
310 311 res += '(?:'
311 312 elif c == '}' and group:
312 313 res += ')'
313 314 group = False
314 315 elif c == ',' and group:
315 316 res += '|'
316 317 elif c == '\\':
317 318 p = peek()
318 319 if p:
319 320 i += 1
320 321 res += re.escape(p)
321 322 else:
322 323 res += re.escape(c)
323 324 else:
324 325 res += re.escape(c)
325 326 return head + res + tail
326 327
327 328 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
328 329
329 330 def pathto(root, n1, n2):
330 331 '''return the relative path from one place to another.
331 332 root should use os.sep to separate directories
332 333 n1 should use os.sep to separate directories
333 334 n2 should use "/" to separate directories
334 335 returns an os.sep-separated path.
335 336
336 337 If n1 is a relative path, it's assumed it's
337 338 relative to root.
338 339 n2 should always be relative to root.
339 340 '''
340 341 if not n1: return localpath(n2)
341 342 if os.path.isabs(n1):
342 343 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
343 344 return os.path.join(root, localpath(n2))
344 345 n2 = '/'.join((pconvert(root), n2))
345 346 a, b = n1.split(os.sep), n2.split('/')
346 347 a.reverse()
347 348 b.reverse()
348 349 while a and b and a[-1] == b[-1]:
349 350 a.pop()
350 351 b.pop()
351 352 b.reverse()
352 353 return os.sep.join((['..'] * len(a)) + b)
353 354
354 355 def canonpath(root, cwd, myname):
355 356 """return the canonical path of myname, given cwd and root"""
356 357 if root == os.sep:
357 358 rootsep = os.sep
358 359 elif root.endswith(os.sep):
359 360 rootsep = root
360 361 else:
361 362 rootsep = root + os.sep
362 363 name = myname
363 364 if not os.path.isabs(name):
364 365 name = os.path.join(root, cwd, name)
365 366 name = os.path.normpath(name)
366 367 if name != rootsep and name.startswith(rootsep):
367 368 name = name[len(rootsep):]
368 369 audit_path(name)
369 370 return pconvert(name)
370 371 elif name == root:
371 372 return ''
372 373 else:
373 374 # Determine whether `name' is in the hierarchy at or beneath `root',
374 375 # by iterating name=dirname(name) until that causes no change (can't
375 376 # check name == '/', because that doesn't work on windows). For each
376 377 # `name', compare dev/inode numbers. If they match, the list `rel'
377 378 # holds the reversed list of components making up the relative file
378 379 # name we want.
379 380 root_st = os.stat(root)
380 381 rel = []
381 382 while True:
382 383 try:
383 384 name_st = os.stat(name)
384 385 except OSError:
385 386 break
386 387 if samestat(name_st, root_st):
387 388 if not rel:
388 389 # name was actually the same as root (maybe a symlink)
389 390 return ''
390 391 rel.reverse()
391 392 name = os.path.join(*rel)
392 393 audit_path(name)
393 394 return pconvert(name)
394 395 dirname, basename = os.path.split(name)
395 396 rel.append(basename)
396 397 if dirname == name:
397 398 break
398 399 name = dirname
399 400
400 401 raise Abort('%s not under root' % myname)
401 402
402 403 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
403 404 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
404 405
405 406 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
406 407 globbed=False, default=None):
407 408 default = default or 'relpath'
408 409 if default == 'relpath' and not globbed:
409 410 names = expand_glob(names)
410 411 return _matcher(canonroot, cwd, names, inc, exc, default, src)
411 412
412 413 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
413 414 """build a function to match a set of file patterns
414 415
415 416 arguments:
416 417 canonroot - the canonical root of the tree you're matching against
417 418 cwd - the current working directory, if relevant
418 419 names - patterns to find
419 420 inc - patterns to include
420 421 exc - patterns to exclude
421 422 dflt_pat - if a pattern in names has no explicit type, assume this one
422 423 src - where these patterns came from (e.g. .hgignore)
423 424
424 425 a pattern is one of:
425 426 'glob:<glob>' - a glob relative to cwd
426 427 're:<regexp>' - a regular expression
427 428 'path:<path>' - a path relative to canonroot
428 429 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
429 430 'relpath:<path>' - a path relative to cwd
430 431 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
431 432 '<something>' - one of the cases above, selected by the dflt_pat argument
432 433
433 434 returns:
434 435 a 3-tuple containing
435 436 - list of roots (places where one should start a recursive walk of the fs);
436 437 this often matches the explicit non-pattern names passed in, but also
437 438 includes the initial part of glob: patterns that has no glob characters
438 439 - a bool match(filename) function
439 440 - a bool indicating if any patterns were passed in
440 441 """
441 442
442 443 # a common case: no patterns at all
443 444 if not names and not inc and not exc:
444 445 return [], always, False
445 446
446 447 def contains_glob(name):
447 448 for c in name:
448 449 if c in _globchars: return True
449 450 return False
450 451
451 452 def regex(kind, name, tail):
452 453 '''convert a pattern into a regular expression'''
453 454 if not name:
454 455 return ''
455 456 if kind == 're':
456 457 return name
457 458 elif kind == 'path':
458 459 return '^' + re.escape(name) + '(?:/|$)'
459 460 elif kind == 'relglob':
460 461 return globre(name, '(?:|.*/)', tail)
461 462 elif kind == 'relpath':
462 463 return re.escape(name) + '(?:/|$)'
463 464 elif kind == 'relre':
464 465 if name.startswith('^'):
465 466 return name
466 467 return '.*' + name
467 468 return globre(name, '', tail)
468 469
469 470 def matchfn(pats, tail):
470 471 """build a matching function from a set of patterns"""
471 472 if not pats:
472 473 return
473 474 try:
474 475 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
475 476 return re.compile(pat).match
476 477 except re.error:
477 478 for k, p in pats:
478 479 try:
479 480 re.compile('(?:%s)' % regex(k, p, tail))
480 481 except re.error:
481 482 if src:
482 483 raise Abort("%s: invalid pattern (%s): %s" %
483 484 (src, k, p))
484 485 else:
485 486 raise Abort("invalid pattern (%s): %s" % (k, p))
486 487 raise Abort("invalid pattern")
487 488
488 489 def globprefix(pat):
489 490 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
490 491 root = []
491 492 for p in pat.split('/'):
492 493 if contains_glob(p): break
493 494 root.append(p)
494 495 return '/'.join(root) or '.'
495 496
496 497 def normalizepats(names, default):
497 498 pats = []
498 499 roots = []
499 500 anypats = False
500 501 for kind, name in [patkind(p, default) for p in names]:
501 502 if kind in ('glob', 'relpath'):
502 503 name = canonpath(canonroot, cwd, name)
503 504 elif kind in ('relglob', 'path'):
504 505 name = normpath(name)
505 506
506 507 pats.append((kind, name))
507 508
508 509 if kind in ('glob', 're', 'relglob', 'relre'):
509 510 anypats = True
510 511
511 512 if kind == 'glob':
512 513 root = globprefix(name)
513 514 roots.append(root)
514 515 elif kind in ('relpath', 'path'):
515 516 roots.append(name or '.')
516 517 elif kind == 'relglob':
517 518 roots.append('.')
518 519 return roots, pats, anypats
519 520
520 521 roots, pats, anypats = normalizepats(names, dflt_pat)
521 522
522 523 patmatch = matchfn(pats, '$') or always
523 524 incmatch = always
524 525 if inc:
525 526 dummy, inckinds, dummy = normalizepats(inc, 'glob')
526 527 incmatch = matchfn(inckinds, '(?:/|$)')
527 528 excmatch = lambda fn: False
528 529 if exc:
529 530 dummy, exckinds, dummy = normalizepats(exc, 'glob')
530 531 excmatch = matchfn(exckinds, '(?:/|$)')
531 532
532 533 if not names and inc and not exc:
533 534 # common case: hgignore patterns
534 535 match = incmatch
535 536 else:
536 537 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
537 538
538 539 return (roots, match, (inc or exc or anypats) and True)
539 540
540 541 _hgexecutable = None
541 542
542 543 def set_hgexecutable(path):
543 544 """remember location of the 'hg' executable if easily possible
544 545
545 546 path might be None or empty if hg was loaded as a module,
546 547 fall back to 'hg' in this case.
547 548 """
548 549 global _hgexecutable
549 550 _hgexecutable = path and os.path.abspath(path) or 'hg'
550 551
551 552 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
552 553 '''enhanced shell command execution.
553 554 run with environment maybe modified, maybe in different dir.
554 555
555 556 if command fails and onerr is None, return status. if ui object,
556 557 print error message and return status, else raise onerr object as
557 558 exception.'''
558 559 def py2shell(val):
559 560 'convert python object into string that is useful to shell'
560 561 if val in (None, False):
561 562 return '0'
562 563 if val == True:
563 564 return '1'
564 565 return str(val)
565 566 oldenv = {}
566 567 for k in environ:
567 568 oldenv[k] = os.environ.get(k)
568 569 if cwd is not None:
569 570 oldcwd = os.getcwd()
570 571 origcmd = cmd
571 572 if os.name == 'nt':
572 573 cmd = '"%s"' % cmd
573 574 try:
574 575 for k, v in environ.iteritems():
575 576 os.environ[k] = py2shell(v)
576 577 if 'HG' not in os.environ:
577 578 os.environ['HG'] = _hgexecutable
578 579 if cwd is not None and oldcwd != cwd:
579 580 os.chdir(cwd)
580 581 rc = os.system(cmd)
581 582 if rc and onerr:
582 583 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
583 584 explain_exit(rc)[0])
584 585 if errprefix:
585 586 errmsg = '%s: %s' % (errprefix, errmsg)
586 587 try:
587 588 onerr.warn(errmsg + '\n')
588 589 except AttributeError:
589 590 raise onerr(errmsg)
590 591 return rc
591 592 finally:
592 593 for k, v in oldenv.iteritems():
593 594 if v is None:
594 595 del os.environ[k]
595 596 else:
596 597 os.environ[k] = v
597 598 if cwd is not None and oldcwd != cwd:
598 599 os.chdir(oldcwd)
599 600
600 601 # os.path.lexists is not available on python2.3
601 602 def lexists(filename):
602 603 "test whether a file with this name exists. does not follow symlinks"
603 604 try:
604 605 os.lstat(filename)
605 606 except:
606 607 return False
607 608 return True
608 609
609 610 def rename(src, dst):
610 611 """forcibly rename a file"""
611 612 try:
612 613 os.rename(src, dst)
613 614 except OSError, err:
614 615 # on windows, rename to existing file is not allowed, so we
615 616 # must delete destination first. but if file is open, unlink
616 617 # schedules it for delete but does not delete it. rename
617 618 # happens immediately even for open files, so we create
618 619 # temporary file, delete it, rename destination to that name,
619 620 # then delete that. then rename is safe to do.
620 621 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
621 622 os.close(fd)
622 623 os.unlink(temp)
623 624 os.rename(dst, temp)
624 625 os.unlink(temp)
625 626 os.rename(src, dst)
626 627
627 628 def unlink(f):
628 629 """unlink and remove the directory if it is empty"""
629 630 os.unlink(f)
630 631 # try removing directories that might now be empty
631 632 try:
632 633 os.removedirs(os.path.dirname(f))
633 634 except OSError:
634 635 pass
635 636
636 637 def copyfile(src, dest):
637 638 "copy a file, preserving mode"
638 639 if os.path.islink(src):
639 640 try:
640 641 os.unlink(dest)
641 642 except:
642 643 pass
643 644 os.symlink(os.readlink(src), dest)
644 645 else:
645 646 try:
646 647 shutil.copyfile(src, dest)
647 648 shutil.copymode(src, dest)
648 649 except shutil.Error, inst:
649 650 raise Abort(str(inst))
650 651
651 652 def copyfiles(src, dst, hardlink=None):
652 653 """Copy a directory tree using hardlinks if possible"""
653 654
654 655 if hardlink is None:
655 656 hardlink = (os.stat(src).st_dev ==
656 657 os.stat(os.path.dirname(dst)).st_dev)
657 658
658 659 if os.path.isdir(src):
659 660 os.mkdir(dst)
660 661 for name in os.listdir(src):
661 662 srcname = os.path.join(src, name)
662 663 dstname = os.path.join(dst, name)
663 664 copyfiles(srcname, dstname, hardlink)
664 665 else:
665 666 if hardlink:
666 667 try:
667 668 os_link(src, dst)
668 669 except (IOError, OSError):
669 670 hardlink = False
670 671 shutil.copy(src, dst)
671 672 else:
672 673 shutil.copy(src, dst)
673 674
674 675 def audit_path(path):
675 676 """Abort if path contains dangerous components"""
676 677 parts = os.path.normcase(path).split(os.sep)
677 678 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
678 679 or os.pardir in parts):
679 680 raise Abort(_("path contains illegal component: %s") % path)
680 681
681 682 def _makelock_file(info, pathname):
682 683 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
683 684 os.write(ld, info)
684 685 os.close(ld)
685 686
686 687 def _readlock_file(pathname):
687 688 return posixfile(pathname).read()
688 689
689 690 def nlinks(pathname):
690 691 """Return number of hardlinks for the given file."""
691 692 return os.lstat(pathname).st_nlink
692 693
693 694 if hasattr(os, 'link'):
694 695 os_link = os.link
695 696 else:
696 697 def os_link(src, dst):
697 698 raise OSError(0, _("Hardlinks not supported"))
698 699
699 700 def fstat(fp):
700 701 '''stat file object that may not have fileno method.'''
701 702 try:
702 703 return os.fstat(fp.fileno())
703 704 except AttributeError:
704 705 return os.stat(fp.name)
705 706
706 707 posixfile = file
707 708
708 709 def is_win_9x():
709 710 '''return true if run on windows 95, 98 or me.'''
710 711 try:
711 712 return sys.getwindowsversion()[3] == 1
712 713 except AttributeError:
713 714 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
714 715
715 716 getuser_fallback = None
716 717
717 718 def getuser():
718 719 '''return name of current user'''
719 720 try:
720 721 return getpass.getuser()
721 722 except ImportError:
722 723 # import of pwd will fail on windows - try fallback
723 724 if getuser_fallback:
724 725 return getuser_fallback()
725 726 # raised if win32api not available
726 727 raise Abort(_('user name not available - set USERNAME '
727 728 'environment variable'))
728 729
729 730 def username(uid=None):
730 731 """Return the name of the user with the given uid.
731 732
732 733 If uid is None, return the name of the current user."""
733 734 try:
734 735 import pwd
735 736 if uid is None:
736 737 uid = os.getuid()
737 738 try:
738 739 return pwd.getpwuid(uid)[0]
739 740 except KeyError:
740 741 return str(uid)
741 742 except ImportError:
742 743 return None
743 744
744 745 def groupname(gid=None):
745 746 """Return the name of the group with the given gid.
746 747
747 748 If gid is None, return the name of the current group."""
748 749 try:
749 750 import grp
750 751 if gid is None:
751 752 gid = os.getgid()
752 753 try:
753 754 return grp.getgrgid(gid)[0]
754 755 except KeyError:
755 756 return str(gid)
756 757 except ImportError:
757 758 return None
758 759
759 760 # File system features
760 761
761 762 def checkfolding(path):
762 763 """
763 764 Check whether the given path is on a case-sensitive filesystem
764 765
765 766 Requires a path (like /foo/.hg) ending with a foldable final
766 767 directory component.
767 768 """
768 769 s1 = os.stat(path)
769 770 d, b = os.path.split(path)
770 771 p2 = os.path.join(d, b.upper())
771 772 if path == p2:
772 773 p2 = os.path.join(d, b.lower())
773 774 try:
774 775 s2 = os.stat(p2)
775 776 if s2 == s1:
776 777 return False
777 778 return True
778 779 except:
779 780 return True
780 781
781 782 def checkexec(path):
782 783 """
783 784 Check whether the given path is on a filesystem with UNIX-like exec flags
784 785
785 786 Requires a directory (like /foo/.hg)
786 787 """
787 788 fh, fn = tempfile.mkstemp("", "", path)
788 789 os.close(fh)
789 790 m = os.stat(fn).st_mode
790 791 os.chmod(fn, m ^ 0111)
791 792 r = (os.stat(fn).st_mode != m)
792 793 os.unlink(fn)
793 794 return r
794 795
795 796 def execfunc(path, fallback):
796 797 '''return an is_exec() function with default to fallback'''
797 798 if checkexec(path):
798 799 return lambda x: is_exec(os.path.join(path, x))
799 800 return fallback
800 801
801 802 def checklink(path):
802 803 """check whether the given path is on a symlink-capable filesystem"""
803 804 # mktemp is not racy because symlink creation will fail if the
804 805 # file already exists
805 806 name = tempfile.mktemp(dir=path)
806 807 try:
807 808 os.symlink(".", name)
808 809 os.unlink(name)
809 810 return True
810 811 except (OSError, AttributeError):
811 812 return False
812 813
813 814 def linkfunc(path, fallback):
814 815 '''return an is_link() function with default to fallback'''
815 816 if checklink(path):
816 817 return lambda x: os.path.islink(os.path.join(path, x))
817 818 return fallback
818 819
819 820 _umask = os.umask(0)
820 821 os.umask(_umask)
821 822
822 823 def needbinarypatch():
823 824 """return True if patches should be applied in binary mode by default."""
824 825 return os.name == 'nt'
825 826
826 827 # Platform specific variants
827 828 if os.name == 'nt':
828 829 import msvcrt
829 830 nulldev = 'NUL:'
830 831
831 832 class winstdout:
832 833 '''stdout on windows misbehaves if sent through a pipe'''
833 834
834 835 def __init__(self, fp):
835 836 self.fp = fp
836 837
837 838 def __getattr__(self, key):
838 839 return getattr(self.fp, key)
839 840
840 841 def close(self):
841 842 try:
842 843 self.fp.close()
843 844 except: pass
844 845
845 846 def write(self, s):
846 847 try:
847 848 return self.fp.write(s)
848 849 except IOError, inst:
849 850 if inst.errno != 0: raise
850 851 self.close()
851 852 raise IOError(errno.EPIPE, 'Broken pipe')
852 853
853 854 def flush(self):
854 855 try:
855 856 return self.fp.flush()
856 857 except IOError, inst:
857 858 if inst.errno != errno.EINVAL: raise
858 859 self.close()
859 860 raise IOError(errno.EPIPE, 'Broken pipe')
860 861
861 862 sys.stdout = winstdout(sys.stdout)
862 863
863 864 def system_rcpath():
864 865 try:
865 866 return system_rcpath_win32()
866 867 except:
867 868 return [r'c:\mercurial\mercurial.ini']
868 869
869 870 def user_rcpath():
870 871 '''return os-specific hgrc search path to the user dir'''
871 872 try:
872 873 userrc = user_rcpath_win32()
873 874 except:
874 875 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
875 876 path = [userrc]
876 877 userprofile = os.environ.get('USERPROFILE')
877 878 if userprofile:
878 879 path.append(os.path.join(userprofile, 'mercurial.ini'))
879 880 return path
880 881
881 882 def parse_patch_output(output_line):
882 883 """parses the output produced by patch and returns the file name"""
883 884 pf = output_line[14:]
884 885 if pf[0] == '`':
885 886 pf = pf[1:-1] # Remove the quotes
886 887 return pf
887 888
888 889 def testpid(pid):
889 890 '''return False if pid dead, True if running or not known'''
890 891 return True
891 892
892 893 def set_exec(f, mode):
893 894 pass
894 895
895 896 def set_link(f, mode):
896 897 pass
897 898
898 899 def set_binary(fd):
899 900 msvcrt.setmode(fd.fileno(), os.O_BINARY)
900 901
901 902 def pconvert(path):
902 903 return path.replace("\\", "/")
903 904
904 905 def localpath(path):
905 906 return path.replace('/', '\\')
906 907
907 908 def normpath(path):
908 909 return pconvert(os.path.normpath(path))
909 910
910 911 makelock = _makelock_file
911 912 readlock = _readlock_file
912 913
913 914 def samestat(s1, s2):
914 915 return False
915 916
916 917 # A sequence of backslashes is special iff it precedes a double quote:
917 918 # - if there's an even number of backslashes, the double quote is not
918 919 # quoted (i.e. it ends the quoted region)
919 920 # - if there's an odd number of backslashes, the double quote is quoted
920 921 # - in both cases, every pair of backslashes is unquoted into a single
921 922 # backslash
922 923 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
923 924 # So, to quote a string, we must surround it in double quotes, double
924 925 # the number of backslashes that preceed double quotes and add another
925 926 # backslash before every double quote (being careful with the double
926 927 # quote we've appended to the end)
927 928 _quotere = None
928 929 def shellquote(s):
929 930 global _quotere
930 931 if _quotere is None:
931 932 _quotere = re.compile(r'(\\*)("|\\$)')
932 933 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
933 934
934 935 def explain_exit(code):
935 936 return _("exited with status %d") % code, code
936 937
937 938 # if you change this stub into a real check, please try to implement the
938 939 # username and groupname functions above, too.
939 940 def isowner(fp, st=None):
940 941 return True
941 942
942 943 def find_in_path(name, path, default=None):
943 944 '''find name in search path. path can be string (will be split
944 945 with os.pathsep), or iterable thing that returns strings. if name
945 946 found, return path to name. else return default. name is looked up
946 947 using cmd.exe rules, using PATHEXT.'''
947 948 if isinstance(path, str):
948 949 path = path.split(os.pathsep)
949 950
950 951 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
951 952 pathext = pathext.lower().split(os.pathsep)
952 953 isexec = os.path.splitext(name)[1].lower() in pathext
953 954
954 955 for p in path:
955 956 p_name = os.path.join(p, name)
956 957
957 958 if isexec and os.path.exists(p_name):
958 959 return p_name
959 960
960 961 for ext in pathext:
961 962 p_name_ext = p_name + ext
962 963 if os.path.exists(p_name_ext):
963 964 return p_name_ext
964 965 return default
965 966
966 967 try:
967 968 # override functions with win32 versions if possible
968 969 from util_win32 import *
969 970 if not is_win_9x():
970 971 posixfile = posixfile_nt
971 972 except ImportError:
972 973 pass
973 974
974 975 else:
975 976 nulldev = '/dev/null'
976 977
977 978 def rcfiles(path):
978 979 rcs = [os.path.join(path, 'hgrc')]
979 980 rcdir = os.path.join(path, 'hgrc.d')
980 981 try:
981 982 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
982 983 if f.endswith(".rc")])
983 984 except OSError:
984 985 pass
985 986 return rcs
986 987
987 988 def system_rcpath():
988 989 path = []
989 990 # old mod_python does not set sys.argv
990 991 if len(getattr(sys, 'argv', [])) > 0:
991 992 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
992 993 '/../etc/mercurial'))
993 994 path.extend(rcfiles('/etc/mercurial'))
994 995 return path
995 996
996 997 def user_rcpath():
997 998 return [os.path.expanduser('~/.hgrc')]
998 999
999 1000 def parse_patch_output(output_line):
1000 1001 """parses the output produced by patch and returns the file name"""
1001 1002 pf = output_line[14:]
1002 1003 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1003 1004 pf = pf[1:-1] # Remove the quotes
1004 1005 return pf
1005 1006
1006 1007 def is_exec(f):
1007 1008 """check whether a file is executable"""
1008 1009 return (os.lstat(f).st_mode & 0100 != 0)
1009 1010
1010 1011 def set_exec(f, mode):
1011 1012 s = os.lstat(f).st_mode
1012 1013 if (s & 0100 != 0) == mode:
1013 1014 return
1014 1015 if mode:
1015 1016 # Turn on +x for every +r bit when making a file executable
1016 1017 # and obey umask.
1017 1018 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1018 1019 else:
1019 1020 os.chmod(f, s & 0666)
1020 1021
1021 1022 def set_link(f, mode):
1022 1023 """make a file a symbolic link/regular file
1023 1024
1024 1025 if a file is changed to a link, its contents become the link data
1025 1026 if a link is changed to a file, its link data become its contents
1026 1027 """
1027 1028
1028 1029 m = os.path.islink(f)
1029 1030 if m == bool(mode):
1030 1031 return
1031 1032
1032 1033 if mode: # switch file to link
1033 1034 data = file(f).read()
1034 1035 os.unlink(f)
1035 1036 os.symlink(data, f)
1036 1037 else:
1037 1038 data = os.readlink(f)
1038 1039 os.unlink(f)
1039 1040 file(f, "w").write(data)
1040 1041
1041 1042 def set_binary(fd):
1042 1043 pass
1043 1044
1044 1045 def pconvert(path):
1045 1046 return path
1046 1047
1047 1048 def localpath(path):
1048 1049 return path
1049 1050
1050 1051 normpath = os.path.normpath
1051 1052 samestat = os.path.samestat
1052 1053
1053 1054 def makelock(info, pathname):
1054 1055 try:
1055 1056 os.symlink(info, pathname)
1056 1057 except OSError, why:
1057 1058 if why.errno == errno.EEXIST:
1058 1059 raise
1059 1060 else:
1060 1061 _makelock_file(info, pathname)
1061 1062
1062 1063 def readlock(pathname):
1063 1064 try:
1064 1065 return os.readlink(pathname)
1065 1066 except OSError, why:
1066 1067 if why.errno == errno.EINVAL:
1067 1068 return _readlock_file(pathname)
1068 1069 else:
1069 1070 raise
1070 1071
1071 1072 def shellquote(s):
1072 1073 return "'%s'" % s.replace("'", "'\\''")
1073 1074
1074 1075 def testpid(pid):
1075 1076 '''return False if pid dead, True if running or not sure'''
1076 1077 try:
1077 1078 os.kill(pid, 0)
1078 1079 return True
1079 1080 except OSError, inst:
1080 1081 return inst.errno != errno.ESRCH
1081 1082
1082 1083 def explain_exit(code):
1083 1084 """return a 2-tuple (desc, code) describing a process's status"""
1084 1085 if os.WIFEXITED(code):
1085 1086 val = os.WEXITSTATUS(code)
1086 1087 return _("exited with status %d") % val, val
1087 1088 elif os.WIFSIGNALED(code):
1088 1089 val = os.WTERMSIG(code)
1089 1090 return _("killed by signal %d") % val, val
1090 1091 elif os.WIFSTOPPED(code):
1091 1092 val = os.WSTOPSIG(code)
1092 1093 return _("stopped by signal %d") % val, val
1093 1094 raise ValueError(_("invalid exit code"))
1094 1095
1095 1096 def isowner(fp, st=None):
1096 1097 """Return True if the file object f belongs to the current user.
1097 1098
1098 1099 The return value of a util.fstat(f) may be passed as the st argument.
1099 1100 """
1100 1101 if st is None:
1101 1102 st = fstat(fp)
1102 1103 return st.st_uid == os.getuid()
1103 1104
1104 1105 def find_in_path(name, path, default=None):
1105 1106 '''find name in search path. path can be string (will be split
1106 1107 with os.pathsep), or iterable thing that returns strings. if name
1107 1108 found, return path to name. else return default.'''
1108 1109 if isinstance(path, str):
1109 1110 path = path.split(os.pathsep)
1110 1111 for p in path:
1111 1112 p_name = os.path.join(p, name)
1112 1113 if os.path.exists(p_name):
1113 1114 return p_name
1114 1115 return default
1115 1116
1116 1117 def set_signal_handler():
1117 1118 pass
1118 1119
1119 1120 def find_exe(name, default=None):
1120 1121 '''find path of an executable.
1121 1122 if name contains a path component, return it as is. otherwise,
1122 1123 use normal executable search path.'''
1123 1124
1124 1125 if os.sep in name:
1125 1126 # don't check the executable bit. if the file isn't
1126 1127 # executable, whoever tries to actually run it will give a
1127 1128 # much more useful error message.
1128 1129 return name
1129 1130 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1130 1131
1131 1132 def _buildencodefun():
1132 1133 e = '_'
1133 1134 win_reserved = [ord(x) for x in '\\:*?"<>|']
1134 1135 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1135 1136 for x in (range(32) + range(126, 256) + win_reserved):
1136 1137 cmap[chr(x)] = "~%02x" % x
1137 1138 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1138 1139 cmap[chr(x)] = e + chr(x).lower()
1139 1140 dmap = {}
1140 1141 for k, v in cmap.iteritems():
1141 1142 dmap[v] = k
1142 1143 def decode(s):
1143 1144 i = 0
1144 1145 while i < len(s):
1145 1146 for l in xrange(1, 4):
1146 1147 try:
1147 1148 yield dmap[s[i:i+l]]
1148 1149 i += l
1149 1150 break
1150 1151 except KeyError:
1151 1152 pass
1152 1153 else:
1153 1154 raise KeyError
1154 1155 return (lambda s: "".join([cmap[c] for c in s]),
1155 1156 lambda s: "".join(list(decode(s))))
1156 1157
1157 1158 encodefilename, decodefilename = _buildencodefun()
1158 1159
1159 1160 def encodedopener(openerfn, fn):
1160 1161 def o(path, *args, **kw):
1161 1162 return openerfn(fn(path), *args, **kw)
1162 1163 return o
1163 1164
1164 1165 def opener(base, audit=True):
1165 1166 """
1166 1167 return a function that opens files relative to base
1167 1168
1168 1169 this function is used to hide the details of COW semantics and
1169 1170 remote file access from higher level code.
1170 1171 """
1171 1172 def mktempcopy(name, emptyok=False):
1172 1173 d, fn = os.path.split(name)
1173 1174 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1174 1175 os.close(fd)
1175 1176 # Temporary files are created with mode 0600, which is usually not
1176 1177 # what we want. If the original file already exists, just copy
1177 1178 # its mode. Otherwise, manually obey umask.
1178 1179 try:
1179 1180 st_mode = os.lstat(name).st_mode
1180 1181 except OSError, inst:
1181 1182 if inst.errno != errno.ENOENT:
1182 1183 raise
1183 1184 st_mode = 0666 & ~_umask
1184 1185 os.chmod(temp, st_mode)
1185 1186 if emptyok:
1186 1187 return temp
1187 1188 try:
1188 1189 try:
1189 1190 ifp = posixfile(name, "rb")
1190 1191 except IOError, inst:
1191 1192 if inst.errno == errno.ENOENT:
1192 1193 return temp
1193 1194 if not getattr(inst, 'filename', None):
1194 1195 inst.filename = name
1195 1196 raise
1196 1197 ofp = posixfile(temp, "wb")
1197 1198 for chunk in filechunkiter(ifp):
1198 1199 ofp.write(chunk)
1199 1200 ifp.close()
1200 1201 ofp.close()
1201 1202 except:
1202 1203 try: os.unlink(temp)
1203 1204 except: pass
1204 1205 raise
1205 1206 return temp
1206 1207
1207 1208 class atomictempfile(posixfile):
1208 1209 """the file will only be copied when rename is called"""
1209 1210 def __init__(self, name, mode):
1210 1211 self.__name = name
1211 1212 self.temp = mktempcopy(name, emptyok=('w' in mode))
1212 1213 posixfile.__init__(self, self.temp, mode)
1213 1214 def rename(self):
1214 1215 if not self.closed:
1215 1216 posixfile.close(self)
1216 1217 rename(self.temp, localpath(self.__name))
1217 1218 def __del__(self):
1218 1219 if not self.closed:
1219 1220 try:
1220 1221 os.unlink(self.temp)
1221 1222 except: pass
1222 1223 posixfile.close(self)
1223 1224
1224 1225 def o(path, mode="r", text=False, atomictemp=False):
1225 1226 if audit:
1226 1227 audit_path(path)
1227 1228 f = os.path.join(base, path)
1228 1229
1229 1230 if not text:
1230 1231 mode += "b" # for that other OS
1231 1232
1232 1233 if mode[0] != "r":
1233 1234 try:
1234 1235 nlink = nlinks(f)
1235 1236 except OSError:
1236 1237 nlink = 0
1237 1238 d = os.path.dirname(f)
1238 1239 if not os.path.isdir(d):
1239 1240 os.makedirs(d)
1240 1241 if atomictemp:
1241 1242 return atomictempfile(f, mode)
1242 1243 if nlink > 1:
1243 1244 rename(mktempcopy(f), f)
1244 1245 return posixfile(f, mode)
1245 1246
1246 1247 return o
1247 1248
1248 1249 class chunkbuffer(object):
1249 1250 """Allow arbitrary sized chunks of data to be efficiently read from an
1250 1251 iterator over chunks of arbitrary size."""
1251 1252
1252 1253 def __init__(self, in_iter, targetsize = 2**16):
1253 1254 """in_iter is the iterator that's iterating over the input chunks.
1254 1255 targetsize is how big a buffer to try to maintain."""
1255 1256 self.in_iter = iter(in_iter)
1256 1257 self.buf = ''
1257 1258 self.targetsize = int(targetsize)
1258 1259 if self.targetsize <= 0:
1259 1260 raise ValueError(_("targetsize must be greater than 0, was %d") %
1260 1261 targetsize)
1261 1262 self.iterempty = False
1262 1263
1263 1264 def fillbuf(self):
1264 1265 """Ignore target size; read every chunk from iterator until empty."""
1265 1266 if not self.iterempty:
1266 1267 collector = cStringIO.StringIO()
1267 1268 collector.write(self.buf)
1268 1269 for ch in self.in_iter:
1269 1270 collector.write(ch)
1270 1271 self.buf = collector.getvalue()
1271 1272 self.iterempty = True
1272 1273
1273 1274 def read(self, l):
1274 1275 """Read L bytes of data from the iterator of chunks of data.
1275 1276 Returns less than L bytes if the iterator runs dry."""
1276 1277 if l > len(self.buf) and not self.iterempty:
1277 1278 # Clamp to a multiple of self.targetsize
1278 1279 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1279 1280 collector = cStringIO.StringIO()
1280 1281 collector.write(self.buf)
1281 1282 collected = len(self.buf)
1282 1283 for chunk in self.in_iter:
1283 1284 collector.write(chunk)
1284 1285 collected += len(chunk)
1285 1286 if collected >= targetsize:
1286 1287 break
1287 1288 if collected < targetsize:
1288 1289 self.iterempty = True
1289 1290 self.buf = collector.getvalue()
1290 1291 s, self.buf = self.buf[:l], buffer(self.buf, l)
1291 1292 return s
1292 1293
1293 1294 def filechunkiter(f, size=65536, limit=None):
1294 1295 """Create a generator that produces the data in the file size
1295 1296 (default 65536) bytes at a time, up to optional limit (default is
1296 1297 to read all data). Chunks may be less than size bytes if the
1297 1298 chunk is the last chunk in the file, or the file is a socket or
1298 1299 some other type of file that sometimes reads less data than is
1299 1300 requested."""
1300 1301 assert size >= 0
1301 1302 assert limit is None or limit >= 0
1302 1303 while True:
1303 1304 if limit is None: nbytes = size
1304 1305 else: nbytes = min(limit, size)
1305 1306 s = nbytes and f.read(nbytes)
1306 1307 if not s: break
1307 1308 if limit: limit -= len(s)
1308 1309 yield s
1309 1310
1310 1311 def makedate():
1311 1312 lt = time.localtime()
1312 1313 if lt[8] == 1 and time.daylight:
1313 1314 tz = time.altzone
1314 1315 else:
1315 1316 tz = time.timezone
1316 1317 return time.mktime(lt), tz
1317 1318
1318 1319 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1319 1320 """represent a (unixtime, offset) tuple as a localized time.
1320 1321 unixtime is seconds since the epoch, and offset is the time zone's
1321 1322 number of seconds away from UTC. if timezone is false, do not
1322 1323 append time zone to string."""
1323 1324 t, tz = date or makedate()
1324 1325 s = time.strftime(format, time.gmtime(float(t) - tz))
1325 1326 if timezone:
1326 1327 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1327 1328 return s
1328 1329
1329 1330 def strdate(string, format, defaults):
1330 1331 """parse a localized time string and return a (unixtime, offset) tuple.
1331 1332 if the string cannot be parsed, ValueError is raised."""
1332 1333 def timezone(string):
1333 1334 tz = string.split()[-1]
1334 1335 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1335 1336 tz = int(tz)
1336 1337 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1337 1338 return offset
1338 1339 if tz == "GMT" or tz == "UTC":
1339 1340 return 0
1340 1341 return None
1341 1342
1342 1343 # NOTE: unixtime = localunixtime + offset
1343 1344 offset, date = timezone(string), string
1344 1345 if offset != None:
1345 1346 date = " ".join(string.split()[:-1])
1346 1347
1347 1348 # add missing elements from defaults
1348 1349 for part in defaults:
1349 1350 found = [True for p in part if ("%"+p) in format]
1350 1351 if not found:
1351 1352 date += "@" + defaults[part]
1352 1353 format += "@%" + part[0]
1353 1354
1354 1355 timetuple = time.strptime(date, format)
1355 1356 localunixtime = int(calendar.timegm(timetuple))
1356 1357 if offset is None:
1357 1358 # local timezone
1358 1359 unixtime = int(time.mktime(timetuple))
1359 1360 offset = unixtime - localunixtime
1360 1361 else:
1361 1362 unixtime = localunixtime + offset
1362 1363 return unixtime, offset
1363 1364
1364 1365 def parsedate(string, formats=None, defaults=None):
1365 1366 """parse a localized time string and return a (unixtime, offset) tuple.
1366 1367 The date may be a "unixtime offset" string or in one of the specified
1367 1368 formats."""
1368 1369 if not string:
1369 1370 return 0, 0
1370 1371 if not formats:
1371 1372 formats = defaultdateformats
1372 1373 string = string.strip()
1373 1374 try:
1374 1375 when, offset = map(int, string.split(' '))
1375 1376 except ValueError:
1376 1377 # fill out defaults
1377 1378 if not defaults:
1378 1379 defaults = {}
1379 1380 now = makedate()
1380 1381 for part in "d mb yY HI M S".split():
1381 1382 if part not in defaults:
1382 1383 if part[0] in "HMS":
1383 1384 defaults[part] = "00"
1384 1385 elif part[0] in "dm":
1385 1386 defaults[part] = "1"
1386 1387 else:
1387 1388 defaults[part] = datestr(now, "%" + part[0], False)
1388 1389
1389 1390 for format in formats:
1390 1391 try:
1391 1392 when, offset = strdate(string, format, defaults)
1392 1393 except ValueError:
1393 1394 pass
1394 1395 else:
1395 1396 break
1396 1397 else:
1397 1398 raise Abort(_('invalid date: %r ') % string)
1398 1399 # validate explicit (probably user-specified) date and
1399 1400 # time zone offset. values must fit in signed 32 bits for
1400 1401 # current 32-bit linux runtimes. timezones go from UTC-12
1401 1402 # to UTC+14
1402 1403 if abs(when) > 0x7fffffff:
1403 1404 raise Abort(_('date exceeds 32 bits: %d') % when)
1404 1405 if offset < -50400 or offset > 43200:
1405 1406 raise Abort(_('impossible time zone offset: %d') % offset)
1406 1407 return when, offset
1407 1408
1408 1409 def matchdate(date):
1409 1410 """Return a function that matches a given date match specifier
1410 1411
1411 1412 Formats include:
1412 1413
1413 1414 '{date}' match a given date to the accuracy provided
1414 1415
1415 1416 '<{date}' on or before a given date
1416 1417
1417 1418 '>{date}' on or after a given date
1418 1419
1419 1420 """
1420 1421
1421 1422 def lower(date):
1422 1423 return parsedate(date, extendeddateformats)[0]
1423 1424
1424 1425 def upper(date):
1425 1426 d = dict(mb="12", HI="23", M="59", S="59")
1426 1427 for days in "31 30 29".split():
1427 1428 try:
1428 1429 d["d"] = days
1429 1430 return parsedate(date, extendeddateformats, d)[0]
1430 1431 except:
1431 1432 pass
1432 1433 d["d"] = "28"
1433 1434 return parsedate(date, extendeddateformats, d)[0]
1434 1435
1435 1436 if date[0] == "<":
1436 1437 when = upper(date[1:])
1437 1438 return lambda x: x <= when
1438 1439 elif date[0] == ">":
1439 1440 when = lower(date[1:])
1440 1441 return lambda x: x >= when
1441 1442 elif date[0] == "-":
1442 1443 try:
1443 1444 days = int(date[1:])
1444 1445 except ValueError:
1445 1446 raise Abort(_("invalid day spec: %s") % date[1:])
1446 1447 when = makedate()[0] - days * 3600 * 24
1447 1448 return lambda x: x >= when
1448 1449 elif " to " in date:
1449 1450 a, b = date.split(" to ")
1450 1451 start, stop = lower(a), upper(b)
1451 1452 return lambda x: x >= start and x <= stop
1452 1453 else:
1453 1454 start, stop = lower(date), upper(date)
1454 1455 return lambda x: x >= start and x <= stop
1455 1456
1456 1457 def shortuser(user):
1457 1458 """Return a short representation of a user name or email address."""
1458 1459 f = user.find('@')
1459 1460 if f >= 0:
1460 1461 user = user[:f]
1461 1462 f = user.find('<')
1462 1463 if f >= 0:
1463 1464 user = user[f+1:]
1464 1465 f = user.find(' ')
1465 1466 if f >= 0:
1466 1467 user = user[:f]
1467 1468 f = user.find('.')
1468 1469 if f >= 0:
1469 1470 user = user[:f]
1470 1471 return user
1471 1472
1472 1473 def ellipsis(text, maxlength=400):
1473 1474 """Trim string to at most maxlength (default: 400) characters."""
1474 1475 if len(text) <= maxlength:
1475 1476 return text
1476 1477 else:
1477 1478 return "%s..." % (text[:maxlength-3])
1478 1479
1479 1480 def walkrepos(path):
1480 1481 '''yield every hg repository under path, recursively.'''
1481 1482 def errhandler(err):
1482 1483 if err.filename == path:
1483 1484 raise err
1484 1485
1485 1486 for root, dirs, files in os.walk(path, onerror=errhandler):
1486 1487 for d in dirs:
1487 1488 if d == '.hg':
1488 1489 yield root
1489 1490 dirs[:] = []
1490 1491 break
1491 1492
1492 1493 _rcpath = None
1493 1494
1494 1495 def os_rcpath():
1495 1496 '''return default os-specific hgrc search path'''
1496 1497 path = system_rcpath()
1497 1498 path.extend(user_rcpath())
1498 1499 path = [os.path.normpath(f) for f in path]
1499 1500 return path
1500 1501
1501 1502 def rcpath():
1502 1503 '''return hgrc search path. if env var HGRCPATH is set, use it.
1503 1504 for each item in path, if directory, use files ending in .rc,
1504 1505 else use item.
1505 1506 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1506 1507 if no HGRCPATH, use default os-specific path.'''
1507 1508 global _rcpath
1508 1509 if _rcpath is None:
1509 1510 if 'HGRCPATH' in os.environ:
1510 1511 _rcpath = []
1511 1512 for p in os.environ['HGRCPATH'].split(os.pathsep):
1512 1513 if not p: continue
1513 1514 if os.path.isdir(p):
1514 1515 for f in os.listdir(p):
1515 1516 if f.endswith('.rc'):
1516 1517 _rcpath.append(os.path.join(p, f))
1517 1518 else:
1518 1519 _rcpath.append(p)
1519 1520 else:
1520 1521 _rcpath = os_rcpath()
1521 1522 return _rcpath
1522 1523
1523 1524 def bytecount(nbytes):
1524 1525 '''return byte count formatted as readable string, with units'''
1525 1526
1526 1527 units = (
1527 1528 (100, 1<<30, _('%.0f GB')),
1528 1529 (10, 1<<30, _('%.1f GB')),
1529 1530 (1, 1<<30, _('%.2f GB')),
1530 1531 (100, 1<<20, _('%.0f MB')),
1531 1532 (10, 1<<20, _('%.1f MB')),
1532 1533 (1, 1<<20, _('%.2f MB')),
1533 1534 (100, 1<<10, _('%.0f KB')),
1534 1535 (10, 1<<10, _('%.1f KB')),
1535 1536 (1, 1<<10, _('%.2f KB')),
1536 1537 (1, 1, _('%.0f bytes')),
1537 1538 )
1538 1539
1539 1540 for multiplier, divisor, format in units:
1540 1541 if nbytes >= divisor * multiplier:
1541 1542 return format % (nbytes / float(divisor))
1542 1543 return units[-1][2] % nbytes
1543 1544
1544 1545 def drop_scheme(scheme, path):
1545 1546 sc = scheme + ':'
1546 1547 if path.startswith(sc):
1547 1548 path = path[len(sc):]
1548 1549 if path.startswith('//'):
1549 1550 path = path[2:]
1550 1551 return path
@@ -1,57 +1,57 b''
1 1 default = 'summary'
2 2 header = header.tmpl
3 3 footer = footer.tmpl
4 4 search = search.tmpl
5 5 changelog = changelog.tmpl
6 6 summary = summary.tmpl
7 7 error = error.tmpl
8 8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 12 filenodelink = '<tr class="parity#parity#"><td><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">#file|escape#</a></td><td></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> | <a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a></td></tr>'
13 13 fileellipses = '...'
14 14 changelogentry = changelogentry.tmpl
15 15 searchentry = changelogentry.tmpl
16 16 changeset = changeset.tmpl
17 17 manifest = manifest.tmpl
18 18 manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a></td></tr>'
19 19 manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td style="font-family:monospace" align=right>#size#</td><td class="list"><a class="list" href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a></td></tr>'
20 20 filerevision = filerevision.tmpl
21 21 fileannotate = fileannotate.tmpl
22 22 filediff = filediff.tmpl
23 23 filelog = filelog.tmpl
24 24 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><span class="linenr"> #linenumber#</span> #line|escape#</pre></div>'
25 25 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
26 26 difflineplus = '<div style="color:#008800;">#line|escape#</div>'
27 27 difflineminus = '<div style="color:#cc0000;">#line|escape#</div>'
28 28 difflineat = '<div style="color:#990099;">#line|escape#</div>'
29 29 diffline = '<div>#line|escape#</div>'
30 30 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
31 31 changesetparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
32 32 filerevparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
33 33 filerename = '{file|escape}@'
34 34 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
35 35 fileannotateparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
36 36 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
37 37 changesetchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
38 38 filerevchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
39 39 fileannotatechild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
40 40 tags = tags.tmpl
41 41 tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>#tag|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/#node|short#{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
42 42 branchentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
43 43 diffblock = '<pre>#lines#</pre>'
44 44 filediffparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
45 45 filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="{url}file/{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
46 46 filediffchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
47 47 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="{url}file{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 48 shortlog = shortlog.tmpl
49 49 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
50 50 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
51 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b> <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
51 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author|person#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b> <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
52 52 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url}diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url}annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> #rename%filelogrename#</td></tr>'
53 53 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
54 54 indexentry = '<tr class="parity#parity#"><td><a class="list" href="#url#{sessionvars%urlparameter}"><b>#name|escape#</b></a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a class="rss_logo" href="#url#rss-log">RSS</a> #archives%archiveentry#</td></tr>'
55 55 index = index.tmpl
56 56 urlparameter = '#separator##name#=#value|urlescape#'
57 57 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,54 +1,54 b''
1 1 #header#
2 2 <title>#repo|escape#: Summary</title>
3 3 <link rel="alternate" type="application/rss+xml"
4 4 href="{url}rss-log" title="RSS feed for #repo|escape#">
5 5 </head>
6 6 <body>
7 7
8 8 <div class="page_header">
9 9 <a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="{url}summary{sessionvars%urlparameter}">#repo|escape#</a> / summary
10 10
11 11 <form action="{url}log">
12 12 {sessionvars%hiddenformentry}
13 13 <div class="search">
14 14 <input type="text" name="rev" />
15 15 </div>
16 16 </form>
17 17 </div>
18 18
19 19 <div class="page_nav">
20 20 summary |
21 21 <a href="{url}shortlog{sessionvars%urlparameter}">shortlog</a> |
22 22 <a href="{url}log{sessionvars%urlparameter}">changelog</a> |
23 23 <a href="{url}tags{sessionvars%urlparameter}">tags</a> |
24 24 <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a>#archives%archiveentry#
25 25 <br/>
26 26 </div>
27 27
28 28 <div class="title">&nbsp;</div>
29 29 <table cellspacing="0">
30 30 <tr><td>description</td><td>#desc#</td></tr>
31 31 <tr><td>owner</td><td>#owner|escape#</td></tr>
32 32 <tr><td>last change</td><td>#lastchange|rfc822date#</td></tr>
33 33 </table>
34 34
35 <div><a class="title" href="{url}log{sessionvars%urlparameter}">changes</a></div>
35 <div><a class="title" href="{url}shortlog{sessionvars%urlparameter}">changes</a></div>
36 36 <table cellspacing="0">
37 37 #shortlog#
38 <tr class="light"><td colspan="4"><a class="list" href="{url}log{sessionvars%urlparameter}">...</a></td></tr>
38 <tr class="light"><td colspan="4"><a class="list" href="{url}shortlog{sessionvars%urlparameter}">...</a></td></tr>
39 39 </table>
40 40
41 41 <div><a class="title" href="{url}tags{sessionvars%urlparameter}">tags</a></div>
42 42 <table cellspacing="0">
43 43 #tags#
44 44 <tr class="light"><td colspan="3"><a class="list" href="{url}tags{sessionvars%urlparameter}">...</a></td></tr>
45 45 </table>
46 46
47 47 <div><a class="title" href="#">branches</a></div>
48 48 <table cellspacing="0">
49 49 {branches%branchentry}
50 50 <tr class="light">
51 51 <td colspan="4"><a class="list" href="#">...</a></td>
52 52 </tr>
53 53 </table>
54 54 #footer#
@@ -1,56 +1,56 b''
1 default = 'changelog'
1 default = 'shortlog'
2 2 header = header.tmpl
3 3 footer = footer.tmpl
4 4 search = search.tmpl
5 5 changelog = changelog.tmpl
6 6 shortlog = shortlog.tmpl
7 7 shortlogentry = shortlogentry.tmpl
8 8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 12 filenodelink = '<a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 13 fileellipses = '...'
14 14 changelogentry = changelogentry.tmpl
15 15 searchentry = changelogentry.tmpl
16 16 changeset = changeset.tmpl
17 17 manifest = manifest.tmpl
18 18 manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td>&nbsp;<td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a>'
19 19 manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td align=right><tt>#size#</tt>&nbsp;<td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a>'
20 20 filerevision = filerevision.tmpl
21 21 fileannotate = fileannotate.tmpl
22 22 filediff = filediff.tmpl
23 23 filelog = filelog.tmpl
24 24 fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
25 25 filelogentry = filelogentry.tmpl
26 26 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
27 27 difflineplus = '<span class="plusline">#line|escape#</span>'
28 28 difflineminus = '<span class="minusline">#line|escape#</span>'
29 29 difflineat = '<span class="atline">#line|escape#</span>'
30 30 diffline = '#line|escape#'
31 31 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
32 32 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
33 33 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
34 34 filerename = '{file|escape}@'
35 35 filelogrename = '<tr><th>base:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#@#node|short#</a></td></tr>'
36 36 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
37 37 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
38 38 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
39 39 filerevchild = '<tr><td class="metatag">child:</td><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
40 40 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
41 41 tags = tags.tmpl
42 42 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="#url#rev/#node|short#{sessionvars%urlparameter}">#tag|escape#</a></li>'
43 43 diffblock = '<pre class="parity#parity#">#lines#</pre>'
44 44 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 45 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
46 46 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 47 filelogparent = '<tr><th>parent #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 48 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 49 filelogchild = '<tr><th>child #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
50 50 indexentry = '<tr class="parity#parity#"><td><a href="#url#{sessionvars%urlparameter}">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#rss-log">RSS</a> #archives%archiveentry#</td></tr>'
51 51 index = index.tmpl
52 52 archiveentry = '<a href="#url#archive/#node|short##extension|urlescape#">#type|escape#</a> '
53 53 notfound = notfound.tmpl
54 54 error = error.tmpl
55 55 urlparameter = '#separator##name#=#value|urlescape#'
56 56 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,7 +1,7 b''
1 1 <table class="slogEntry parity#parity#">
2 2 <tr>
3 3 <td class="age">#date|age#</td>
4 <td class="author">#author|obfuscate#</td>
4 <td class="author">#author|person#</td>
5 5 <td class="node"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#desc|strip|firstline|escape#</a></td>
6 6 </tr>
7 7 </table>
@@ -1,78 +1,78 b''
1 1 a { text-decoration:none; }
2 2 .age { white-space:nowrap; }
3 3 .indexlinks { white-space:nowrap; }
4 4 .parity0 { background-color: #dddddd; }
5 5 .parity1 { background-color: #eeeeee; }
6 6 .lineno { width: 60px; color: #aaaaaa; font-size: smaller;
7 7 text-align: right; padding-right:1em; }
8 8 .plusline { color: green; }
9 9 .minusline { color: red; }
10 10 .atline { color: purple; }
11 11 .annotate { font-size: smaller; text-align: right; padding-right: 1em; }
12 12 .buttons a {
13 13 background-color: #666666;
14 14 padding: 2pt;
15 15 color: white;
16 16 font-family: sans;
17 17 font-weight: bold;
18 18 }
19 19 .navigate a {
20 20 background-color: #ccc;
21 21 padding: 2pt;
22 22 font-family: sans;
23 23 color: black;
24 24 }
25 25
26 26 .metatag {
27 27 background-color: #888888;
28 28 color: white;
29 29 text-align: right;
30 30 }
31 31
32 32 /* Common */
33 33 pre { margin: 0; }
34 34
35 35 .logo {
36 36 background-color: #333;
37 37 padding: 4pt;
38 38 margin: 8pt 0 8pt 8pt;
39 39 font-family: sans;
40 40 font-size: 60%;
41 41 color: white;
42 42 float: right;
43 43 clear: right;
44 44 text-align: left;
45 45 }
46 46
47 47 .logo a {
48 48 font-weight: bold;
49 49 font-size: 150%;
50 50 color: #999;
51 51 }
52 52
53 53 /* Changelog/Filelog entries */
54 54 .logEntry { width: 100%; }
55 55 .logEntry .age { width: 15%; }
56 56 .logEntry th { font-weight: normal; text-align: right; vertical-align: top; }
57 57 .logEntry th.age, .logEntry th.firstline { font-weight: bold; }
58 58 .logEntry th.firstline { text-align: left; width: inherit; }
59 59
60 60 /* Shortlog entries */
61 .slogEntry { width: 100%; font-size: smaller; }
62 .slogEntry .age { width: 7%; }
61 .slogEntry { width: 100%; }
62 .slogEntry .age { width: 8em; }
63 63 .slogEntry td { font-weight: normal; text-align: left; vertical-align: top; }
64 .slogEntry td.author { width: 35%; }
64 .slogEntry td.author { width: 15em; }
65 65
66 66 /* Tag entries */
67 67 #tagEntries { list-style: none; margin: 0; padding: 0; }
68 68 #tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
69 69
70 70 /* Changeset entry */
71 71 #changesetEntry { }
72 72 #changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
73 73 #changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
74 74
75 75 /* File diff view */
76 76 #filediffEntry { }
77 77 #filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
78 78
General Comments 0
You need to be logged in to leave comments. Login now