##// END OF EJS Templates
Get add and locate to use new repo and dirstate walk code....
Bryan O'Sullivan -
r724:1c0c413c default
parent child Browse files
Show More
@@ -1,527 +1,573 b''
1 1 HG(1)
2 2 =====
3 3 Matt Mackall <mpm@selenic.com>
4 4
5 5 NAME
6 6 ----
7 7 hg - Mercurial source code management system
8 8
9 9 SYNOPSIS
10 10 --------
11 11 'hg' [-v -d -q -y] <command> [command options] [files]
12 12
13 13 DESCRIPTION
14 14 -----------
15 15 The hg(1) command provides a command line interface to the Mercurial system.
16 16
17 17 OPTIONS
18 18 -------
19 19
20 20 --debug, -d::
21 21 enable debugging output
22 22
23 23 --quiet, -q::
24 24 suppress output
25 25
26 26 --verbose, -v::
27 27 enable additional output
28 28
29 29 --noninteractive, -y::
30 30 do not prompt, assume 'yes' for any required answers
31 31
32 32 COMMAND ELEMENTS
33 33 ----------------
34 34
35 35 files ...::
36 indicates one or more filename or relative path filenames
36 indicates one or more filename or relative path filenames; see
37 "FILE NAME PATTERNS" for information on pattern matching
37 38
38 39 path::
39 40 indicates a path on the local machine
40 41
41 42 revision::
42 43 indicates a changeset which can be specified as a changeset revision
43 44 number, a tag, or a unique substring of the changeset hash value
44 45
45 46 repository path::
46 47 either the pathname of a local repository or the URI of a remote
47 48 repository. There are two available URI protocols, http:// which is
48 49 fast and the old-http:// protocol which is much slower but does not
49 50 require a special server on the web host.
50 51
51 52 COMMANDS
52 53 --------
53 54
54 add [files ...]::
55 add [options] [files ...]::
55 56 Schedule files to be version controlled and added to the repository.
56 57
57 58 The files will be added to the repository at the next commit.
58 59
60 If no names are given, add all files in the current directory and
61 its subdirectory.
62
59 63 addremove::
60 64 Add all new files and remove all missing files from the repository.
61 65
62 66 New files are ignored if they match any of the patterns in .hgignore. As
63 67 with add, these changes take effect at the next commit.
64 68
65 69 annotate [-r <rev> -u -n -c] [files ...]::
66 70 List changes in files, showing the revision id responsible for each line
67 71
68 72 This command is useful to discover who did a change or when a change took
69 73 place.
70 74
71 75 options:
72 76 -r, --revision <rev> annotate the specified revision
73 77 -u, --user list the author
74 78 -c, --changeset list the changeset
75 79 -n, --number list the revision number (default)
76 80
77 81 cat <file> [revision]::
78 82 Output to stdout the given revision for the specified file.
79 83
80 84 If no revision is given then the tip is used.
81 85
82 86 clone [-U] <source> [dest]::
83 87 Create a copy of an existing repository in a new directory.
84 88
85 89 If no destination directory name is specified, it defaults to the
86 90 basename of the source.
87 91
88 92 The source is added to the new repository's .hg/hgrc file to be used in
89 93 future pulls.
90 94
91 95 For efficiency, hardlinks are used for cloning whenever the
92 96 source and destination are on the same filesystem.
93 97
94 98 options:
95 99 -U, --noupdate do not update the new working directory
96 100
97 101 commit [-A -t -l <file> -t <text> -u <user> -d <datecode>] [files...]::
98 102 Commit changes to the given files into the repository.
99 103
100 104 If a list of files is omitted, all changes reported by "hg status"
101 105 will be commited.
102 106
103 107 The HGEDITOR or EDITOR environment variables are used to start an
104 108 editor to add a commit comment.
105 109
106 110 Options:
107 111
108 112 -A, --addremove run addremove during commit
109 113 -t, --text <text> use <text> as commit message
110 114 -l, --logfile <file> show the commit message for the given file
111 115 -d, --date <datecode> record datecode as commit date
112 116 -u, --user <user> record user as commiter
113 117
114 118 aliases: ci
115 119
116 120 copy <source> <dest>::
117 121 Mark <dest> file as a copy or rename of a <source> one
118 122
119 123 This command takes effect for the next commit.
120 124
121 125 diff [-r revision] [-r revision] [files ...]::
122 126 Show differences between revisions for the specified files.
123 127
124 128 Differences between files are shown using the unified diff format.
125 129
126 130 When two revision arguments are given, then changes are shown
127 131 between those revisions. If only one revision is specified then
128 132 that revision is compared to the working directory, and, when no
129 133 revisions are specified, the working directory files are compared
130 134 to its parent.
131 135
132 136 export [-o filespec] [revision] ...::
133 137 Print the changeset header and diffs for one or more revisions.
134 138
135 139 The information shown in the changeset header is: author,
136 140 changeset hash, parent and commit comment.
137 141
138 142 Output may be to a file, in which case the name of the file is
139 143 given using a format string. The formatting rules are as follows:
140 144
141 145 %% literal "%" character
142 146 %H changeset hash (40 bytes of hexadecimal)
143 147 %N number of patches being generated
144 148 %R changeset revision number
145 149 %b basename of the exporting repository
146 150 %h short-form changeset hash (12 bytes of hexadecimal)
147 151 %n zero-padded sequence number, starting at 1
148 152 %r zero-padded changeset revision number
149 153
150 154 Options:
151 155
152 156 -o, --output <filespec> print output to file with formatted named
153 157
154 158 forget [files]::
155 159 Undo an 'hg add' scheduled for the next commit.
156 160
157 161 heads::
158 162 Show all repository head changesets.
159 163
160 164 Repository "heads" are changesets that don't have children
161 165 changesets. They are where development generally takes place and
162 166 are the usual targets for update and merge operations.
163 167
164 168 identify::
165 169 Print a short summary of the current state of the repo.
166 170
167 171 This summary identifies the repository state using one or two parent
168 172 hash identifiers, followed by a "+" if there are uncommitted changes
169 173 in the working directory, followed by a list of tags for this revision.
170 174
171 175 aliases: id
172 176
173 177 import [-p <n> -b <base> -q] <patches>::
174 178 Import a list of patches and commit them individually.
175 179
176 180 options:
177 181 -p, --strip <n> directory strip option for patch. This has the same
178 182 meaning as the correnponding patch option
179 183 -b <path> base directory to read patches from
180 184
181 185 aliases: patch
182 186
183 187 init::
184 188 Initialize a new repository in the current directory.
185 189
186 locate [options] [patterns]::
187 Print all files under Mercurial control whose basenames match the
190 locate [options] [files]::
191 Print all files under Mercurial control whose names match the
188 192 given patterns.
189 193
190 Patterns are shell-style globs. To restrict searches to specific
191 directories, use the "-i <pat>" option. To eliminate particular
192 directories from searching, use the "-x <pat>" option.
193
194 194 This command searches the current directory and its
195 195 subdirectories. To search an entire repository, move to the root
196 196 of the repository.
197 197
198 198 If no patterns are given to match, this command prints all file
199 199 names.
200 200
201 201 If you want to feed the output of this command into the "xargs"
202 202 command, use the "-0" option to both this command and "xargs".
203 203 This will avoid the problem of "xargs" treating single filenames
204 204 that contain white space as multiple file names.
205 205
206 206 options:
207 207
208 208 -0, --print0 end filenames with NUL, for use with xargs
209 209 -f, --fullpath print complete paths from the filesystem root
210 -i, --include <pat> include directories matching the given globs
210 -I, --include <pat> include directories matching the given patterns
211 211 -r, --rev <rev> search the repository as it stood at rev
212 -x, --exclude <pat> exclude directories matching the given globs
212 -X, --exclude <pat> exclude directories matching the given patterns
213 213
214 214 log [-r revision ...] [-p] [file]::
215 215 Print the revision history of the specified file or the entire project.
216 216
217 217 By default this command outputs: changeset id and hash, tags,
218 218 parents, user, date and time, and a summary for each commit. The
219 219 -v switch adds some more detail, such as changed files, manifest
220 220 hashes or message signatures.
221 221
222 222 options:
223 223 -r, --rev <A>, ... When a revision argument is given, only this file or
224 224 changelog revision is displayed. With two revision
225 225 arguments all revisions in this range are listed.
226 226 Additional revision arguments may be given repeating
227 227 the above cycle.
228 228 -p, --patch show patch
229 229
230 230 aliases: history
231 231
232 232 manifest [revision]::
233 233 Print a list of version controlled files for the given revision.
234 234
235 235 The manifest is the list of files being version controlled. If no revision
236 236 is given then the tip is used.
237 237
238 238 parents::
239 239 Print the working directory's parent revisions.
240 240
241 241 pull <repository path>::
242 242 Pull changes from a remote repository to a local one.
243 243
244 244 This finds all changes from the repository at the specified path
245 245 or URL and adds them to the local repository. By default, this
246 246 does not update the copy of the project in the working directory.
247 247
248 248 options:
249 249 -u, --update update the working directory to tip after pull
250 250
251 251 push <destination>::
252 252 Push changes from the local repository to the given destination.
253 253
254 254 This is the symmetrical operation for pull. It helps to move
255 255 changes from the current repository to a different one. If the
256 256 destination is local this is identical to a pull in that directory
257 257 from the current one.
258 258
259 259 The other currently available push method is SSH. This requires an
260 260 accessible shell account on the destination machine and a copy of
261 261 hg in the remote path. Destinations are specified in the following
262 262 form:
263 263
264 264 ssh://[user@]host[:port]/path
265 265
266 266 rawcommit [-p -d -u -F -t -l]::
267 267 Lowlevel commit, for use in helper scripts.
268 268
269 269 This command is not intended to be used by normal users, as it is
270 270 primarily useful for importing from other SCMs.
271 271
272 272 recover::
273 273 Recover from an interrupted commit or pull.
274 274
275 275 This command tries to fix the repository status after an interrupted
276 276 operation. It should only be necessary when Mercurial suggests it.
277 277
278 278 remove [files ...]::
279 279 Schedule the indicated files for removal from the repository.
280 280
281 281 This command shedules the files to be removed at the next commit.
282 282 This only removes files from the current branch, not from the
283 283 entire project history.
284 284
285 285 aliases: rm
286 286
287 287 revert [names ...]::
288 288 Revert any uncommitted modifications made to the named files or
289 289 directories. This restores the contents of the affected files to
290 290 an unmodified state.
291 291
292 292 If a file has been deleted, it is recreated. If the executable
293 293 mode of a file was changed, it is reset.
294 294
295 295 If a directory is given, all files in that directory and its
296 296 subdirectories are reverted.
297 297
298 298 If no arguments are given, all files in the current directory and
299 299 its subdirectories are reverted.
300 300
301 301 options:
302 302 -r, --rev <rev> revision to revert to
303 303 -n, --nonrecursive do not recurse into subdirectories
304 304
305 305 root::
306 306 Print the root directory of the current repository.
307 307
308 308 serve [options]::
309 309 Start a local HTTP repository browser and pull server.
310 310
311 311 By default, the server logs accesses to stdout and errors to
312 312 stderr. Use the "-A" and "-E" options to log to files.
313 313
314 314 options:
315 315 -A, --accesslog <file> name of access log file to write to
316 316 -E, --errorlog <file> name of error log file to write to
317 317 -a, --address <addr> address to use
318 318 -p, --port <n> port to use (default: 8000)
319 319 -n, --name <name> name to show in web pages (default: working dir)
320 320 -t, --templatedir <path> web templates to use
321 321
322 322 status::
323 323 Show changed files in the working directory.
324 324
325 325 The codes used to show the status of files are:
326 326
327 327 C = changed
328 328 A = added
329 329 R = removed
330 330 ? = not tracked
331 331
332 332 tag [-l -t <text> -d <datecode> -u <user>] <name> [revision]::
333 333 Name a particular revision using <name>.
334 334
335 335 Tags are used to name particular revisions of the repository and are
336 336 very useful to compare different revision, to go back to significant
337 337 earlier versions or to mark branch points as releases, etc.
338 338
339 339 If no revision is given, the tip is used.
340 340
341 341 To facilitate version control, distribution, and merging of tags,
342 342 they are stored as a file named ".hgtags" which is managed
343 343 similarly to other project files and can be hand-edited if
344 344 necessary.
345 345
346 346 options:
347 347 -l, --local make the tag local
348 348 -t, --text <text> message for tag commit log entry
349 349 -d, --date <datecode> datecode for commit
350 350 -u, --user <user> user for commit
351 351
352 352 Note: Local tags are not version-controlled or distributed and are
353 353 stored in the .hg/localtags file. If there exists a local tag and
354 354 a public tag with the same name, local tag is used.
355 355
356 356 tags::
357 357 List the repository tags.
358 358
359 359 This lists both regular and local tags.
360 360
361 361 tip::
362 362 Show the tip revision.
363 363
364 364 undo::
365 365 Undo the last commit or pull transaction.
366 366
367 367 Roll back the last pull or commit transaction on the
368 368 repository, restoring the project to its earlier state.
369 369
370 370 This command should be used with care. There is only one level of
371 371 undo and there is no redo.
372 372
373 373 This command is not intended for use on public repositories. Once
374 374 a change is visible for pull by other users, undoing it locally is
375 375 ineffective.
376 376
377 377 update [-m -C] [revision]::
378 378 Update the working directory to the specified revision.
379 379
380 380 By default, update will refuse to run if doing so would require
381 381 merging or discarding local changes.
382 382
383 383 With the -m option, a merge will be performed.
384 384
385 385 With the -C option, local changes will be lost.
386 386
387 387 options:
388 388 -m, --merge allow merging of branches
389 389 -C, --clean overwrite locally modified files
390 390
391 391 aliases: up checkout co
392 392
393 393 verify::
394 394 Verify the integrity of the current repository.
395 395
396 396 This will perform an extensive check of the repository's
397 397 integrity, validating the hashes and checksums of each entry in
398 398 the changelog, manifest, and tracked files, as well as the
399 399 integrity of their crosslinks and indices.
400 400
401 FILE NAME PATTERNS
402 ------------------
403
404 Mercurial accepts several notations for identifying one or more
405 file at a time.
406
407 By default, Mercurial treats file names as shell-style extended
408 glob patterns.
409
410 Alternate pattern notations must be specified explicitly.
411
412 To use a plain path name without any pattern matching, start a
413 name with "path:". These path names must match completely, from
414 the root of the current repository.
415
416 To use an extended glob, start a name with "glob:". Globs are
417 rooted at the current directory; a glob such as "*.c" will match
418 files ending in ".c" in the current directory only.
419
420 The supported glob syntax extensions are "**" to match any string
421 across path separators, and "{a,b}" to mean "a or b".
422
423 To use a Perl/Python regular expression, start a name with "re:".
424 Regexp pattern matching is anchored at the root of the repository.
425
426 Plain examples:
427
428 path:foo/bar a name bar in a directory named foo in the root of
429 the repository
430 path:path:name a file or directory named "path:name"
431
432 Glob examples:
433
434 glob:*.c any name ending in ".c" in the current directory
435 *.c any name ending in ".c" in the current directory
436 **.c any name ending in ".c" in the current directory, or
437 any subdirectory
438 foo/*.c any name ending in ".c" in the directory foo
439 foo/**.c any name ending in ".c" in the directory foo, or any
440 subdirectory
441
442 Regexp examples:
443
444 re:.*\.c$ any name ending in ".c", anywhere in the repsitory
445
446
401 447 SPECIFYING SINGLE REVISIONS
402 448 ---------------------------
403 449
404 450 Mercurial accepts several notations for identifying individual
405 451 revisions.
406 452
407 453 A plain integer is treated as a revision number. Negative
408 454 integers are treated as offsets from the tip, with -1 denoting the
409 455 tip.
410 456
411 457 A 40-digit hexadecimal string is treated as a unique revision
412 458 identifier.
413 459
414 460 A hexadecimal string less than 40 characters long is treated as a
415 461 unique revision identifier, and referred to as a short-form
416 462 identifier. A short-form identifier is only valid if it is the
417 463 prefix of one full-length identifier.
418 464
419 465 Any other string is treated as a tag name, which is a symbolic
420 466 name associated with a revision identifier. Tag names may not
421 467 contain the ":" character.
422 468
423 469 The reserved name "tip" is a special tag that always identifies
424 470 the most recent revision.
425 471
426 472 SPECIFYING MULTIPLE REVISIONS
427 473 -----------------------------
428 474
429 475 When Mercurial accepts more than one revision, they may be
430 476 specified individually, or provided as a continuous range,
431 477 separated by the ":" character.
432 478
433 479 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
434 480 are revision identifiers. Both BEGIN and END are optional. If
435 481 BEGIN is not specified, it defaults to revision number 0. If END
436 482 is not specified, it defaults to the tip. The range ":" thus
437 483 means "all revisions".
438 484
439 485 If BEGIN is greater than END, revisions are treated in reverse
440 486 order.
441 487
442 488 A range acts as an open interval. This means that a range of 3:5
443 489 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
444 490
445 491 ENVIRONMENT VARIABLES
446 492 ---------------------
447 493
448 494 HGEDITOR::
449 495 This is the name of the editor to use when committing. Defaults to the
450 496 value of EDITOR.
451 497
452 498 (deprecated, use .hgrc)
453 499
454 500 HGMERGE::
455 501 An executable to use for resolving merge conflicts. The program
456 502 will be executed with three arguments: local file, remote file,
457 503 ancestor file.
458 504
459 505 The default program is "hgmerge", which is a shell script provided
460 506 by Mercurial with some sensible defaults.
461 507
462 508 (deprecated, use .hgrc)
463 509
464 510 HGUSER::
465 511 This is the string used for the author of a commit.
466 512
467 513 (deprecated, use .hgrc)
468 514
469 515 EMAIL::
470 516 If HGUSER is not set, this will be used as the author for a commit.
471 517
472 518 LOGNAME::
473 519 If neither HGUSER nor EMAIL is set, LOGNAME will be used (with
474 520 '@hostname' appended) as the author value for a commit.
475 521
476 522 EDITOR::
477 523 This is the name of the editor used in the hgmerge script. It will be
478 524 used for commit messages if HGEDITOR isn't set. Defaults to 'vi'.
479 525
480 526 PYTHONPATH::
481 527 This is used by Python to find imported modules and may need to be set
482 528 appropriately if Mercurial is not installed system-wide.
483 529
484 530 FILES
485 531 -----
486 532 .hgignore::
487 533 This file contains regular expressions (one per line) that describe file
488 534 names that should be ignored by hg.
489 535
490 536 .hgtags::
491 537 This file contains changeset hash values and text tag names (one of each
492 538 seperated by spaces) that correspond to tagged versions of the repository
493 539 contents.
494 540
495 541 $HOME/.hgrc, .hg/hgrc::
496 542 This file contains defaults and configuration. Values in .hg/hgrc
497 543 override those in .hgrc. See hgrc(5) for details of the contents
498 544 and format of these files.
499 545
500 546 BUGS
501 547 ----
502 548 Probably lots, please post them to the mailing list (See Resources below)
503 549 when you find them.
504 550
505 551 SEE ALSO
506 552 --------
507 553 hgrc(5)
508 554
509 555 AUTHOR
510 556 ------
511 557 Written by Matt Mackall <mpm@selenic.com>
512 558
513 559 RESOURCES
514 560 ---------
515 561 http://selenic.com/mercurial[Main Web Site]
516 562
517 563 http://www.serpentine.com/mercurial[Wiki site]
518 564
519 565 http://selenic.com/hg[Source code repository]
520 566
521 567 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
522 568
523 569 COPYING
524 570 -------
525 571 Copyright (C) 2005 Matt Mackall.
526 572 Free use of this software is granted under the terms of the GNU General
527 573 Public License (GPL).
@@ -1,1355 +1,1368 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 demandload(globals(), "os re sys signal shutil")
10 10 demandload(globals(), "fancyopts ui hg util")
11 11 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
12 12 demandload(globals(), "errno socket version struct")
13 13
14 14 class UnknownCommand(Exception):
15 15 """Exception raised if command is not in the command table."""
16 16
17 17 def filterfiles(filters, files):
18 18 l = [x for x in files if x in filters]
19 19
20 20 for t in filters:
21 21 if t and t[-1] != "/":
22 22 t += "/"
23 23 l += [x for x in files if x.startswith(t)]
24 24 return l
25 25
26 26 def relfilter(repo, files):
27 27 cwd = repo.getcwd()
28 28 if cwd:
29 29 return filterfiles([util.pconvert(cwd)], files)
30 30 return files
31 31
32 32 def relpath(repo, args):
33 33 cwd = repo.getcwd()
34 34 if cwd:
35 35 return [util.pconvert(os.path.normpath(os.path.join(cwd, x)))
36 36 for x in args]
37 37 return args
38 38
39 def matchpats(ui, cwd, pats = [], opts = {}):
40 head = ''
41 if opts.get('rootless'): head = '(?:.*/|)'
42 def reify(name, tail):
43 if name.startswith('re:'):
44 return name[3:]
45 elif name.startswith('glob:'):
46 return head + util.globre(name[5:], '', tail)
47 elif name.startswith('path:'):
48 return '^' + re.escape(name[5:]) + '$'
49 return head + util.globre(name, '', tail)
50 cwdsep = cwd + os.sep
51 def under(fn):
52 if not cwd or fn.startswith(cwdsep): return True
53 def matchfn(pats, tail, ifempty = util.always):
54 if not pats: return ifempty
55 pat = '(?:%s)' % '|'.join([reify(p, tail) for p in pats])
56 if cwd: pat = re.escape(cwd + os.sep) + pat
57 ui.debug('regexp: %s\n' % pat)
58 return re.compile(pat).match
59 patmatch = matchfn(pats, '$')
60 incmatch = matchfn(opts.get('include'), '(?:/|$)', under)
61 excmatch = matchfn(opts.get('exclude'), '(?:/|$)', util.never)
62 return lambda fn: (incmatch(fn) and not excmatch(fn) and
63 (fn.endswith('/') or patmatch(fn)))
64
65 def walk(repo, pats, opts):
66 cwd = repo.getcwd()
67 if cwd: c = len(cwd) + 1
68 for fn in repo.walk(match = matchpats(repo.ui, cwd, pats, opts)):
69 if cwd: yield fn, fn[c:]
70 else: yield fn, fn
71
39 72 revrangesep = ':'
40 73
41 74 def revrange(ui, repo, revs, revlog=None):
42 75 if revlog is None:
43 76 revlog = repo.changelog
44 77 revcount = revlog.count()
45 78 def fix(val, defval):
46 79 if not val:
47 80 return defval
48 81 try:
49 82 num = int(val)
50 83 if str(num) != val:
51 84 raise ValueError
52 85 if num < 0:
53 86 num += revcount
54 87 if not (0 <= num < revcount):
55 88 raise ValueError
56 89 except ValueError:
57 90 try:
58 91 num = repo.changelog.rev(repo.lookup(val))
59 92 except KeyError:
60 93 try:
61 94 num = revlog.rev(revlog.lookup(val))
62 95 except KeyError:
63 96 ui.warn('abort: invalid revision identifier %s\n' % val)
64 97 sys.exit(1)
65 98 return num
66 99 for spec in revs:
67 100 if spec.find(revrangesep) >= 0:
68 101 start, end = spec.split(revrangesep, 1)
69 102 start = fix(start, 0)
70 103 end = fix(end, revcount - 1)
71 104 if end > start:
72 105 end += 1
73 106 step = 1
74 107 else:
75 108 end -= 1
76 109 step = -1
77 110 for rev in xrange(start, end, step):
78 111 yield str(rev)
79 112 else:
80 113 yield spec
81 114
82 115 def make_filename(repo, r, pat, node=None,
83 116 total=None, seqno=None, revwidth=None):
84 117 node_expander = {
85 118 'H': lambda: hg.hex(node),
86 119 'R': lambda: str(r.rev(node)),
87 120 'h': lambda: hg.short(node),
88 121 }
89 122 expander = {
90 123 '%': lambda: '%',
91 124 'b': lambda: os.path.basename(repo.root),
92 125 }
93 126
94 127 if node:
95 128 expander.update(node_expander)
96 129 if node and revwidth is not None:
97 130 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
98 131 if total is not None:
99 132 expander['N'] = lambda: str(total)
100 133 if seqno is not None:
101 134 expander['n'] = lambda: str(seqno)
102 135 if total is not None and seqno is not None:
103 136 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
104 137
105 138 newname = []
106 139 patlen = len(pat)
107 140 i = 0
108 141 while i < patlen:
109 142 c = pat[i]
110 143 if c == '%':
111 144 i += 1
112 145 c = pat[i]
113 146 c = expander[c]()
114 147 newname.append(c)
115 148 i += 1
116 149 return ''.join(newname)
117 150
118 151 def dodiff(fp, ui, repo, files=None, node1=None, node2=None):
119 152 def date(c):
120 153 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
121 154
122 155 (c, a, d, u) = repo.changes(node1, node2, files)
123 156 if files:
124 157 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
125 158
126 159 if not c and not a and not d:
127 160 return
128 161
129 162 if node2:
130 163 change = repo.changelog.read(node2)
131 164 mmap2 = repo.manifest.read(change[0])
132 165 date2 = date(change)
133 166 def read(f):
134 167 return repo.file(f).read(mmap2[f])
135 168 else:
136 169 date2 = time.asctime()
137 170 if not node1:
138 171 node1 = repo.dirstate.parents()[0]
139 172 def read(f):
140 173 return repo.wfile(f).read()
141 174
142 175 if ui.quiet:
143 176 r = None
144 177 else:
145 178 hexfunc = ui.verbose and hg.hex or hg.short
146 179 r = [hexfunc(node) for node in [node1, node2] if node]
147 180
148 181 change = repo.changelog.read(node1)
149 182 mmap = repo.manifest.read(change[0])
150 183 date1 = date(change)
151 184
152 185 for f in c:
153 186 to = None
154 187 if f in mmap:
155 188 to = repo.file(f).read(mmap[f])
156 189 tn = read(f)
157 190 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
158 191 for f in a:
159 192 to = None
160 193 tn = read(f)
161 194 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
162 195 for f in d:
163 196 to = repo.file(f).read(mmap[f])
164 197 tn = None
165 198 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
166 199
167 200 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
168 201 """show a single changeset or file revision"""
169 202 changelog = repo.changelog
170 203 if filelog:
171 204 log = filelog
172 205 filerev = rev
173 206 node = filenode = filelog.node(filerev)
174 207 changerev = filelog.linkrev(filenode)
175 208 changenode = changenode or changelog.node(changerev)
176 209 else:
177 210 log = changelog
178 211 changerev = rev
179 212 if changenode is None:
180 213 changenode = changelog.node(changerev)
181 214 elif not changerev:
182 215 rev = changerev = changelog.rev(changenode)
183 216 node = changenode
184 217
185 218 if ui.quiet:
186 219 ui.write("%d:%s\n" % (rev, hg.hex(node)))
187 220 return
188 221
189 222 changes = changelog.read(changenode)
190 223
191 224 parents = [(log.rev(parent), hg.hex(parent))
192 225 for parent in log.parents(node)
193 226 if ui.debugflag or parent != hg.nullid]
194 227 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
195 228 parents = []
196 229
197 230 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
198 231 for tag in repo.nodetags(changenode):
199 232 ui.status("tag: %s\n" % tag)
200 233 for parent in parents:
201 234 ui.write("parent: %d:%s\n" % parent)
202 235 if filelog:
203 236 ui.debug("file rev: %d:%s\n" % (filerev, hg.hex(filenode)))
204 237 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
205 238 hg.hex(changes[0])))
206 239 ui.status("user: %s\n" % changes[1])
207 240 ui.status("date: %s\n" % time.asctime(
208 241 time.localtime(float(changes[2].split(' ')[0]))))
209 242 if ui.debugflag:
210 243 files = repo.changes(changelog.parents(changenode)[0], changenode)
211 244 for key, value in zip(["files:", "files+:", "files-:"], files):
212 245 if value:
213 246 ui.note("%-12s %s\n" % (key, " ".join(value)))
214 247 else:
215 248 ui.note("files: %s\n" % " ".join(changes[3]))
216 249 description = changes[4].strip()
217 250 if description:
218 251 if ui.verbose:
219 252 ui.status("description:\n")
220 253 ui.status(description)
221 254 ui.status("\n\n")
222 255 else:
223 256 ui.status("summary: %s\n" % description.splitlines()[0])
224 257 ui.status("\n")
225 258
226 259 def show_version(ui):
227 260 """output version and copyright information"""
228 261 ui.write("Mercurial version %s\n" % version.get_version())
229 262 ui.status(
230 263 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
231 264 "This is free software; see the source for copying conditions. "
232 265 "There is NO\nwarranty; "
233 266 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
234 267 )
235 268
236 269 def help_(ui, cmd=None):
237 270 """show help for a given command or all commands"""
238 271 if cmd:
239 272 try:
240 273 i = find(cmd)
241 274 ui.write("%s\n\n" % i[2])
242 275
243 276 if i[1]:
244 277 for s, l, d, c in i[1]:
245 278 opt = ' '
246 279 if s:
247 280 opt = opt + '-' + s + ' '
248 281 if l:
249 282 opt = opt + '--' + l + ' '
250 283 if d:
251 284 opt = opt + '(' + str(d) + ')'
252 285 ui.write(opt, "\n")
253 286 if c:
254 287 ui.write(' %s\n' % c)
255 288 ui.write("\n")
256 289
257 290 ui.write(i[0].__doc__, "\n")
258 291 except UnknownCommand:
259 292 ui.warn("hg: unknown command %s\n" % cmd)
260 293 sys.exit(0)
261 294 else:
262 295 if ui.verbose:
263 296 show_version(ui)
264 297 ui.write('\n')
265 298 if ui.verbose:
266 299 ui.write('hg commands:\n\n')
267 300 else:
268 301 ui.write('basic hg commands (use "hg help -v" for more):\n\n')
269 302
270 303 h = {}
271 304 for c, e in table.items():
272 305 f = c.split("|")[0]
273 306 if not ui.verbose and not f.startswith("^"):
274 307 continue
275 308 if not ui.debugflag and f.startswith("debug"):
276 309 continue
277 310 f = f.lstrip("^")
278 311 d = ""
279 312 if e[0].__doc__:
280 313 d = e[0].__doc__.splitlines(0)[0].rstrip()
281 314 h[f] = d
282 315
283 316 fns = h.keys()
284 317 fns.sort()
285 318 m = max(map(len, fns))
286 319 for f in fns:
287 320 ui.write(' %-*s %s\n' % (m, f, h[f]))
288 321
289 322 # Commands start here, listed alphabetically
290 323
291 def add(ui, repo, file1, *files):
324 def add(ui, repo, *pats, **opts):
292 325 '''add the specified files on the next commit'''
293 repo.add(relpath(repo, (file1,) + files))
326 names = []
327 q = dict(zip(pats, pats))
328 for abs, rel in walk(repo, pats, opts):
329 if rel in q or abs in q:
330 names.append(abs)
331 elif repo.dirstate.state(abs) == '?':
332 ui.status('adding %s\n' % rel)
333 names.append(abs)
334 repo.add(names)
294 335
295 336 def addremove(ui, repo, *files):
296 337 """add all new files, delete all missing files"""
297 338 if files:
298 339 files = relpath(repo, files)
299 340 d = []
300 341 u = []
301 342 for f in files:
302 343 p = repo.wjoin(f)
303 344 s = repo.dirstate.state(f)
304 345 isfile = os.path.isfile(p)
305 346 if s != 'r' and not isfile:
306 347 d.append(f)
307 348 elif s not in 'nmai' and isfile:
308 349 u.append(f)
309 350 else:
310 351 (c, a, d, u) = repo.changes()
311 352 repo.add(u)
312 353 repo.remove(d)
313 354
314 355 def annotate(u, repo, file1, *files, **ops):
315 356 """show changeset information per file line"""
316 357 def getnode(rev):
317 358 return hg.short(repo.changelog.node(rev))
318 359
319 360 def getname(rev):
320 361 try:
321 362 return bcache[rev]
322 363 except KeyError:
323 364 cl = repo.changelog.read(repo.changelog.node(rev))
324 365 name = cl[1]
325 366 f = name.find('@')
326 367 if f >= 0:
327 368 name = name[:f]
328 369 f = name.find('<')
329 370 if f >= 0:
330 371 name = name[f+1:]
331 372 bcache[rev] = name
332 373 return name
333 374
334 375 bcache = {}
335 376 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
336 377 if not ops['user'] and not ops['changeset']:
337 378 ops['number'] = 1
338 379
339 380 node = repo.dirstate.parents()[0]
340 381 if ops['revision']:
341 382 node = repo.changelog.lookup(ops['revision'])
342 383 change = repo.changelog.read(node)
343 384 mmap = repo.manifest.read(change[0])
344 385 for f in relpath(repo, (file1,) + files):
345 386 lines = repo.file(f).annotate(mmap[f])
346 387 pieces = []
347 388
348 389 for o, f in opmap:
349 390 if ops[o]:
350 391 l = [f(n) for n, dummy in lines]
351 392 m = max(map(len, l))
352 393 pieces.append(["%*s" % (m, x) for x in l])
353 394
354 395 for p, l in zip(zip(*pieces), lines):
355 396 u.write(" ".join(p) + ": " + l[1])
356 397
357 398 def cat(ui, repo, file1, rev=None, **opts):
358 399 """output the latest or given revision of a file"""
359 400 r = repo.file(relpath(repo, [file1])[0])
360 401 if rev:
361 402 n = r.lookup(rev)
362 403 else:
363 404 n = r.tip()
364 405 if opts['output'] and opts['output'] != '-':
365 406 try:
366 407 outname = make_filename(repo, r, opts['output'], node=n)
367 408 fp = open(outname, 'wb')
368 409 except KeyError, inst:
369 410 ui.warn("error: invlaid format spec '%%%s' in output file name\n" %
370 411 inst.args[0])
371 412 sys.exit(1);
372 413 else:
373 414 fp = sys.stdout
374 415 fp.write(r.read(n))
375 416
376 417 def clone(ui, source, dest=None, **opts):
377 418 """make a copy of an existing repository"""
378 419 if dest is None:
379 420 dest = os.path.basename(os.path.normpath(source))
380 421
381 422 if os.path.exists(dest):
382 423 ui.warn("abort: destination '%s' already exists\n" % dest)
383 424 return 1
384 425
385 426 class Dircleanup:
386 427 def __init__(self, dir_):
387 428 self.rmtree = shutil.rmtree
388 429 self.dir_ = dir_
389 430 os.mkdir(dir_)
390 431 def close(self):
391 432 self.dir_ = None
392 433 def __del__(self):
393 434 if self.dir_:
394 435 self.rmtree(self.dir_, True)
395 436
396 437 d = Dircleanup(dest)
397 438 abspath = source
398 439 source = ui.expandpath(source)
399 440 other = hg.repository(ui, source)
400 441
401 442 if other.dev() != -1:
402 443 abspath = os.path.abspath(source)
403 444 copyfile = (os.stat(dest).st_dev == other.dev()
404 445 and getattr(os, 'link', None) or shutil.copy2)
405 446 if copyfile is not shutil.copy2:
406 447 ui.note("cloning by hardlink\n")
407 448 util.copytree(os.path.join(source, ".hg"), os.path.join(dest, ".hg"),
408 449 copyfile)
409 450 try:
410 451 os.unlink(os.path.join(dest, ".hg", "dirstate"))
411 452 except IOError:
412 453 pass
413 454
414 455 repo = hg.repository(ui, dest)
415 456
416 457 else:
417 458 repo = hg.repository(ui, dest, create=1)
418 459 repo.pull(other)
419 460
420 461 f = repo.opener("hgrc", "w")
421 462 f.write("[paths]\n")
422 463 f.write("default = %s\n" % abspath)
423 464
424 465 if not opts['noupdate']:
425 466 update(ui, repo)
426 467
427 468 d.close()
428 469
429 470 def commit(ui, repo, *files, **opts):
430 471 """commit the specified files or all outstanding changes"""
431 472 text = opts['text']
432 473 logfile = opts['logfile']
433 474 if not text and logfile:
434 475 try:
435 476 text = open(logfile).read()
436 477 except IOError, why:
437 478 ui.warn("Can't read commit text %s: %s\n" % (logfile, why))
438 479
439 480 if opts['addremove']:
440 481 addremove(ui, repo, *files)
441 482 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
442 483
443 484 def copy(ui, repo, source, dest):
444 485 """mark a file as copied or renamed for the next commit"""
445 486 return repo.copy(*relpath(repo, (source, dest)))
446 487
447 488 def debugcheckstate(ui, repo):
448 489 """validate the correctness of the current dirstate"""
449 490 parent1, parent2 = repo.dirstate.parents()
450 491 repo.dirstate.read()
451 492 dc = repo.dirstate.map
452 493 keys = dc.keys()
453 494 keys.sort()
454 495 m1n = repo.changelog.read(parent1)[0]
455 496 m2n = repo.changelog.read(parent2)[0]
456 497 m1 = repo.manifest.read(m1n)
457 498 m2 = repo.manifest.read(m2n)
458 499 errors = 0
459 500 for f in dc:
460 501 state = repo.dirstate.state(f)
461 502 if state in "nr" and f not in m1:
462 503 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
463 504 errors += 1
464 505 if state in "a" and f in m1:
465 506 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
466 507 errors += 1
467 508 if state in "m" and f not in m1 and f not in m2:
468 509 ui.warn("%s in state %s, but not in either manifest\n" %
469 510 (f, state))
470 511 errors += 1
471 512 for f in m1:
472 513 state = repo.dirstate.state(f)
473 514 if state not in "nrm":
474 515 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
475 516 errors += 1
476 517 if errors:
477 518 ui.warn(".hg/dirstate inconsistent with current parent's manifest\n")
478 519 sys.exit(1)
479 520
480 521 def debugstate(ui, repo):
481 522 """show the contents of the current dirstate"""
482 523 repo.dirstate.read()
483 524 dc = repo.dirstate.map
484 525 keys = dc.keys()
485 526 keys.sort()
486 527 for file_ in keys:
487 528 ui.write("%c %s\n" % (dc[file_][0], file_))
488 529
489 530 def debugindex(ui, file_):
490 531 """dump the contents of an index file"""
491 532 r = hg.revlog(hg.opener(""), file_, "")
492 533 ui.write(" rev offset length base linkrev" +
493 534 " p1 p2 nodeid\n")
494 535 for i in range(r.count()):
495 536 e = r.index[i]
496 537 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
497 538 i, e[0], e[1], e[2], e[3],
498 539 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
499 540
500 541 def debugindexdot(ui, file_):
501 542 """dump an index DAG as a .dot file"""
502 543 r = hg.revlog(hg.opener(""), file_, "")
503 544 ui.write("digraph G {\n")
504 545 for i in range(r.count()):
505 546 e = r.index[i]
506 547 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
507 548 if e[5] != hg.nullid:
508 549 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
509 550 ui.write("}\n")
510 551
511 552 def diff(ui, repo, *files, **opts):
512 553 """diff working directory (or selected files)"""
513 554 revs = []
514 555 if opts['rev']:
515 556 revs = map(lambda x: repo.lookup(x), opts['rev'])
516 557
517 558 if len(revs) > 2:
518 559 ui.warn("too many revisions to diff\n")
519 560 sys.exit(1)
520 561
521 562 if files:
522 563 files = relpath(repo, files)
523 564 else:
524 565 files = relpath(repo, [""])
525 566
526 567 dodiff(sys.stdout, ui, repo, files, *revs)
527 568
528 569 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
529 570 node = repo.lookup(changeset)
530 571 prev, other = repo.changelog.parents(node)
531 572 change = repo.changelog.read(node)
532 573
533 574 if opts['output'] and opts['output'] != '-':
534 575 try:
535 576 outname = make_filename(repo, repo.changelog, opts['output'],
536 577 node=node, total=total, seqno=seqno,
537 578 revwidth=revwidth)
538 579 fp = open(outname, 'wb')
539 580 except KeyError, inst:
540 581 ui.warn("error: invalid format spec '%%%s' in output file name\n" %
541 582 inst.args[0])
542 583 sys.exit(1)
543 584 else:
544 585 fp = sys.stdout
545 586
546 587 fp.write("# HG changeset patch\n")
547 588 fp.write("# User %s\n" % change[1])
548 589 fp.write("# Node ID %s\n" % hg.hex(node))
549 590 fp.write("# Parent %s\n" % hg.hex(prev))
550 591 if other != hg.nullid:
551 592 fp.write("# Parent %s\n" % hg.hex(other))
552 593 fp.write(change[4].rstrip())
553 594 fp.write("\n\n")
554 595
555 596 dodiff(fp, ui, repo, None, prev, node)
556 597
557 598 def export(ui, repo, *changesets, **opts):
558 599 """dump the header and diffs for one or more changesets"""
559 600 if not changesets:
560 601 ui.warn("error: export requires at least one changeset\n")
561 602 sys.exit(1)
562 603 seqno = 0
563 604 revs = list(revrange(ui, repo, changesets))
564 605 total = len(revs)
565 606 revwidth = max(len(revs[0]), len(revs[-1]))
566 607 for cset in revs:
567 608 seqno += 1
568 609 doexport(ui, repo, cset, seqno, total, revwidth, opts)
569 610
570 611 def forget(ui, repo, file1, *files):
571 612 """don't add the specified files on the next commit"""
572 613 repo.forget(relpath(repo, (file1,) + files))
573 614
574 615 def heads(ui, repo):
575 616 """show current repository heads"""
576 617 for n in repo.changelog.heads():
577 618 show_changeset(ui, repo, changenode=n)
578 619
579 620 def identify(ui, repo):
580 621 """print information about the working copy"""
581 622 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
582 623 if not parents:
583 624 ui.write("unknown\n")
584 625 return
585 626
586 627 hexfunc = ui.verbose and hg.hex or hg.short
587 628 (c, a, d, u) = repo.changes()
588 629 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
589 630 (c or a or d) and "+" or "")]
590 631
591 632 if not ui.quiet:
592 633 # multiple tags for a single parent separated by '/'
593 634 parenttags = ['/'.join(tags)
594 635 for tags in map(repo.nodetags, parents) if tags]
595 636 # tags for multiple parents separated by ' + '
596 637 output.append(' + '.join(parenttags))
597 638
598 639 ui.write("%s\n" % ' '.join(output))
599 640
600 641 def import_(ui, repo, patch1, *patches, **opts):
601 642 """import an ordered set of patches"""
602 643 try:
603 644 import psyco
604 645 psyco.full()
605 646 except ImportError:
606 647 pass
607 648
608 649 patches = (patch1,) + patches
609 650
610 651 d = opts["base"]
611 652 strip = opts["strip"]
612 653
613 654 for patch in patches:
614 655 ui.status("applying %s\n" % patch)
615 656 pf = os.path.join(d, patch)
616 657
617 658 text = []
618 659 user = None
619 660 hgpatch = False
620 661 for line in file(pf):
621 662 line = line.rstrip()
622 663 if line.startswith("--- ") or line.startswith("diff -r"):
623 664 break
624 665 elif hgpatch:
625 666 # parse values when importing the result of an hg export
626 667 if line.startswith("# User "):
627 668 user = line[7:]
628 669 ui.debug('User: %s\n' % user)
629 670 elif not line.startswith("# ") and line:
630 671 text.append(line)
631 672 hgpatch = False
632 673 elif line == '# HG changeset patch':
633 674 hgpatch = True
634 675 else:
635 676 text.append(line)
636 677
637 678 # make sure text isn't empty
638 679 if not text:
639 680 text = "imported patch %s\n" % patch
640 681 else:
641 682 text = "%s\n" % '\n'.join(text)
642 683 ui.debug('text:\n%s\n' % text)
643 684
644 685 f = os.popen("patch -p%d < %s" % (strip, pf))
645 686 files = []
646 687 for l in f.read().splitlines():
647 688 l.rstrip('\r\n');
648 689 ui.status("%s\n" % l)
649 690 if l.startswith('patching file '):
650 691 pf = l[14:]
651 692 if pf not in files:
652 693 files.append(pf)
653 694 patcherr = f.close()
654 695 if patcherr:
655 696 sys.stderr.write("patch failed")
656 697 sys.exit(1)
657 698
658 699 if len(files) > 0:
659 700 addremove(ui, repo, *files)
660 701 repo.commit(files, text, user)
661 702
662 703 def init(ui, source=None):
663 704 """create a new repository in the current directory"""
664 705
665 706 if source:
666 707 ui.warn("no longer supported: use \"hg clone\" instead\n")
667 708 sys.exit(1)
668 709 hg.repository(ui, ".", create=1)
669 710
670 711 def locate(ui, repo, *pats, **opts):
671 712 """locate files matching specific patterns"""
672 if [p for p in pats if os.sep in p]:
673 ui.warn("error: patterns may not contain '%s'\n" % os.sep)
674 ui.warn("use '-i <dir>' instead\n")
675 sys.exit(1)
676 def compile(pats, head='^', tail=os.sep, on_empty=True):
677 if not pats:
678 class c:
679 def match(self, x):
680 return on_empty
681 return c()
682 fnpats = [fnmatch.translate(os.path.normpath(os.path.normcase(p)))[:-1]
683 for p in pats]
684 regexp = r'%s(?:%s)%s' % (head, '|'.join(fnpats), tail)
685 return re.compile(regexp)
686 exclude = compile(opts['exclude'], on_empty=False)
687 include = compile(opts['include'])
688 pat = compile(pats, head='', tail='$')
689 end = opts['print0'] and '\0' or '\n'
690 if opts['rev']:
691 node = repo.manifest.lookup(opts['rev'])
692 else:
693 node = repo.manifest.tip()
694 manifest = repo.manifest.read(node)
695 cwd = repo.getcwd()
696 cwd_plus = cwd and (cwd + os.sep)
697 found = []
698 for f in manifest:
699 f = os.path.normcase(f)
700 if exclude.match(f) or not(include.match(f) and
701 f.startswith(cwd_plus) and
702 pat.match(os.path.basename(f))):
703 continue
713 if opts['print0']: end = '\0'
714 else: end = '\n'
715 opts['rootless'] = True
716 for abs, rel in walk(repo, pats, opts):
717 if repo.dirstate.state(abs) == '?': continue
704 718 if opts['fullpath']:
705 f = os.path.join(repo.root, f)
706 elif cwd:
707 f = f[len(cwd_plus):]
708 found.append(f)
709 found.sort()
710 for f in found:
711 ui.write(f, end)
719 ui.write(os.path.join(repo.root, abs), end)
720 else:
721 ui.write(rel, end)
712 722
713 723 def log(ui, repo, f=None, **opts):
714 724 """show the revision history of the repository or a single file"""
715 725 if f:
716 726 files = relpath(repo, [f])
717 727 filelog = repo.file(files[0])
718 728 log = filelog
719 729 lookup = filelog.lookup
720 730 else:
721 731 files = None
722 732 filelog = None
723 733 log = repo.changelog
724 734 lookup = repo.lookup
725 735 revlist = []
726 736 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
727 737 while revs:
728 738 if len(revs) == 1:
729 739 revlist.append(revs.pop(0))
730 740 else:
731 741 a = revs.pop(0)
732 742 b = revs.pop(0)
733 743 off = a > b and -1 or 1
734 744 revlist.extend(range(a, b + off, off))
735 745
736 746 for i in revlist or range(log.count() - 1, -1, -1):
737 747 show_changeset(ui, repo, filelog=filelog, rev=i)
738 748 if opts['patch']:
739 749 if filelog:
740 750 filenode = filelog.node(i)
741 751 i = filelog.linkrev(filenode)
742 752 changenode = repo.changelog.node(i)
743 753 prev, other = repo.changelog.parents(changenode)
744 754 dodiff(sys.stdout, ui, repo, files, prev, changenode)
745 755 ui.write("\n\n")
746 756
747 757 def manifest(ui, repo, rev=None):
748 758 """output the latest or given revision of the project manifest"""
749 759 if rev:
750 760 try:
751 761 # assume all revision numbers are for changesets
752 762 n = repo.lookup(rev)
753 763 change = repo.changelog.read(n)
754 764 n = change[0]
755 765 except hg.RepoError:
756 766 n = repo.manifest.lookup(rev)
757 767 else:
758 768 n = repo.manifest.tip()
759 769 m = repo.manifest.read(n)
760 770 mf = repo.manifest.readflags(n)
761 771 files = m.keys()
762 772 files.sort()
763 773
764 774 for f in files:
765 775 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
766 776
767 777 def parents(ui, repo, node=None):
768 778 '''show the parents of the current working dir'''
769 779 if node:
770 780 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
771 781 else:
772 782 p = repo.dirstate.parents()
773 783
774 784 for n in p:
775 785 if n != hg.nullid:
776 786 show_changeset(ui, repo, changenode=n)
777 787
778 788 def pull(ui, repo, source="default", **opts):
779 789 """pull changes from the specified source"""
780 790 source = ui.expandpath(source)
781 791 ui.status('pulling from %s\n' % (source))
782 792
783 793 other = hg.repository(ui, source)
784 794 r = repo.pull(other)
785 795 if not r:
786 796 if opts['update']:
787 797 return update(ui, repo)
788 798 else:
789 799 ui.status("(run 'hg update' to get a working copy)\n")
790 800
791 801 return r
792 802
793 803 def push(ui, repo, dest="default-push"):
794 804 """push changes to the specified destination"""
795 805 dest = ui.expandpath(dest)
796 806 ui.status('pushing to %s\n' % (dest))
797 807
798 808 other = hg.repository(ui, dest)
799 809 r = repo.push(other)
800 810 return r
801 811
802 812 def rawcommit(ui, repo, *flist, **rc):
803 813 "raw commit interface"
804 814
805 815 text = rc['text']
806 816 if not text and rc['logfile']:
807 817 try:
808 818 text = open(rc['logfile']).read()
809 819 except IOError:
810 820 pass
811 821 if not text and not rc['logfile']:
812 822 ui.warn("abort: missing commit text\n")
813 823 return 1
814 824
815 825 files = relpath(repo, list(flist))
816 826 if rc['files']:
817 827 files += open(rc['files']).read().splitlines()
818 828
819 829 rc['parent'] = map(repo.lookup, rc['parent'])
820 830
821 831 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
822 832
823 833 def recover(ui, repo):
824 834 """roll back an interrupted transaction"""
825 835 repo.recover()
826 836
827 837 def remove(ui, repo, file1, *files):
828 838 """remove the specified files on the next commit"""
829 839 repo.remove(relpath(repo, (file1,) + files))
830 840
831 841 def revert(ui, repo, *names, **opts):
832 842 """revert modified files or dirs back to their unmodified states"""
833 843 node = opts['rev'] and repo.lookup(opts['rev']) or \
834 844 repo.dirstate.parents()[0]
835 845 root = os.path.realpath(repo.root)
836 846
837 847 def trimpath(p):
838 848 p = os.path.realpath(p)
839 849 if p.startswith(root):
840 850 rest = p[len(root):]
841 851 if not rest:
842 852 return rest
843 853 if p.startswith(os.sep):
844 854 return rest[1:]
845 855 return p
846 856
847 857 relnames = map(trimpath, names or [os.getcwd()])
848 858 chosen = {}
849 859
850 860 def choose(name):
851 861 def body(name):
852 862 for r in relnames:
853 863 if not name.startswith(r):
854 864 continue
855 865 rest = name[len(r):]
856 866 if not rest:
857 867 return r, True
858 868 depth = rest.count(os.sep)
859 869 if not r:
860 870 if depth == 0 or not opts['nonrecursive']:
861 871 return r, True
862 872 elif rest[0] == os.sep:
863 873 if depth == 1 or not opts['nonrecursive']:
864 874 return r, True
865 875 return None, False
866 876 relname, ret = body(name)
867 877 if ret:
868 878 chosen[relname] = 1
869 879 return ret
870 880
871 881 r = repo.update(node, False, True, choose, False)
872 882 for n in relnames:
873 883 if n not in chosen:
874 884 ui.warn('error: no matches for %s\n' % n)
875 885 r = 1
876 886 sys.stdout.flush()
877 887 return r
878 888
879 889 def root(ui, repo):
880 890 """print the root (top) of the current working dir"""
881 891 ui.write(repo.root + "\n")
882 892
883 893 def serve(ui, repo, **opts):
884 894 """export the repository via HTTP"""
885 895
886 896 if opts["stdio"]:
887 897 fin, fout = sys.stdin, sys.stdout
888 898 sys.stdout = sys.stderr
889 899
890 900 def getarg():
891 901 argline = fin.readline()[:-1]
892 902 arg, l = argline.split()
893 903 val = fin.read(int(l))
894 904 return arg, val
895 905 def respond(v):
896 906 fout.write("%d\n" % len(v))
897 907 fout.write(v)
898 908 fout.flush()
899 909
900 910 lock = None
901 911
902 912 while 1:
903 913 cmd = fin.readline()[:-1]
904 914 if cmd == '':
905 915 return
906 916 if cmd == "heads":
907 917 h = repo.heads()
908 918 respond(" ".join(map(hg.hex, h)) + "\n")
909 919 if cmd == "lock":
910 920 lock = repo.lock()
911 921 respond("")
912 922 if cmd == "unlock":
913 923 if lock:
914 924 lock.release()
915 925 lock = None
916 926 respond("")
917 927 elif cmd == "branches":
918 928 arg, nodes = getarg()
919 929 nodes = map(hg.bin, nodes.split(" "))
920 930 r = []
921 931 for b in repo.branches(nodes):
922 932 r.append(" ".join(map(hg.hex, b)) + "\n")
923 933 respond("".join(r))
924 934 elif cmd == "between":
925 935 arg, pairs = getarg()
926 936 pairs = [map(hg.bin, p.split("-")) for p in pairs.split(" ")]
927 937 r = []
928 938 for b in repo.between(pairs):
929 939 r.append(" ".join(map(hg.hex, b)) + "\n")
930 940 respond("".join(r))
931 941 elif cmd == "changegroup":
932 942 nodes = []
933 943 arg, roots = getarg()
934 944 nodes = map(hg.bin, roots.split(" "))
935 945
936 946 cg = repo.changegroup(nodes)
937 947 while 1:
938 948 d = cg.read(4096)
939 949 if not d:
940 950 break
941 951 fout.write(d)
942 952
943 953 fout.flush()
944 954
945 955 elif cmd == "addchangegroup":
946 956 if not lock:
947 957 respond("not locked")
948 958 continue
949 959 respond("")
950 960
951 961 r = repo.addchangegroup(fin)
952 962 respond("")
953 963
954 964 def openlog(opt, default):
955 965 if opts[opt] and opts[opt] != '-':
956 966 return open(opts[opt], 'w')
957 967 else:
958 968 return default
959 969
960 970 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
961 971 opts["address"], opts["port"],
962 972 openlog('accesslog', sys.stdout),
963 973 openlog('errorlog', sys.stderr))
964 974 if ui.verbose:
965 975 addr, port = httpd.socket.getsockname()
966 976 if addr == '0.0.0.0':
967 977 addr = socket.gethostname()
968 978 else:
969 979 try:
970 980 addr = socket.gethostbyaddr(addr)[0]
971 981 except socket.error:
972 982 pass
973 983 if port != 80:
974 984 ui.status('listening at http://%s:%d/\n' % (addr, port))
975 985 else:
976 986 ui.status('listening at http://%s/\n' % addr)
977 987 httpd.serve_forever()
978 988
979 989 def status(ui, repo):
980 990 '''show changed files in the working directory
981 991
982 992 C = changed
983 993 A = added
984 994 R = removed
985 995 ? = not tracked'''
986 996
987 997 (c, a, d, u) = repo.changes()
988 998 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
989 999
990 1000 for f in c:
991 1001 ui.write("C ", f, "\n")
992 1002 for f in a:
993 1003 ui.write("A ", f, "\n")
994 1004 for f in d:
995 1005 ui.write("R ", f, "\n")
996 1006 for f in u:
997 1007 ui.write("? ", f, "\n")
998 1008
999 1009 def tag(ui, repo, name, rev=None, **opts):
1000 1010 """add a tag for the current tip or a given revision"""
1001 1011
1002 1012 if name == "tip":
1003 1013 ui.warn("abort: 'tip' is a reserved name!\n")
1004 1014 return -1
1005 1015 if rev:
1006 1016 r = hg.hex(repo.lookup(rev))
1007 1017 else:
1008 1018 r = hg.hex(repo.changelog.tip())
1009 1019
1010 1020 if name.find(revrangesep) >= 0:
1011 1021 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
1012 1022 return -1
1013 1023
1014 1024 if opts['local']:
1015 1025 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1016 1026 return
1017 1027
1018 1028 (c, a, d, u) = repo.changes()
1019 1029 for x in (c, a, d, u):
1020 1030 if ".hgtags" in x:
1021 1031 ui.warn("abort: working copy of .hgtags is changed!\n")
1022 1032 ui.status("(please commit .hgtags manually)\n")
1023 1033 return -1
1024 1034
1025 1035 add = not os.path.exists(repo.wjoin(".hgtags"))
1026 1036 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1027 1037 if add:
1028 1038 repo.add([".hgtags"])
1029 1039
1030 1040 if not opts['text']:
1031 1041 opts['text'] = "Added tag %s for changeset %s" % (name, r)
1032 1042
1033 1043 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
1034 1044
1035 1045 def tags(ui, repo):
1036 1046 """list repository tags"""
1037 1047
1038 1048 l = repo.tagslist()
1039 1049 l.reverse()
1040 1050 for t, n in l:
1041 1051 try:
1042 1052 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
1043 1053 except KeyError:
1044 1054 r = " ?:?"
1045 1055 ui.write("%-30s %s\n" % (t, r))
1046 1056
1047 1057 def tip(ui, repo):
1048 1058 """show the tip revision"""
1049 1059 n = repo.changelog.tip()
1050 1060 show_changeset(ui, repo, changenode=n)
1051 1061
1052 1062 def undo(ui, repo):
1053 1063 """undo the last commit or pull
1054 1064
1055 1065 Roll back the last pull or commit transaction on the
1056 1066 repository, restoring the project to its earlier state.
1057 1067
1058 1068 This command should be used with care. There is only one level of
1059 1069 undo and there is no redo.
1060 1070
1061 1071 This command is not intended for use on public repositories. Once
1062 1072 a change is visible for pull by other users, undoing it locally is
1063 1073 ineffective.
1064 1074 """
1065 1075 repo.undo()
1066 1076
1067 1077 def update(ui, repo, node=None, merge=False, clean=False):
1068 1078 '''update or merge working directory
1069 1079
1070 1080 If there are no outstanding changes in the working directory and
1071 1081 there is a linear relationship between the current version and the
1072 1082 requested version, the result is the requested version.
1073 1083
1074 1084 Otherwise the result is a merge between the contents of the
1075 1085 current working directory and the requested version. Files that
1076 1086 changed between either parent are marked as changed for the next
1077 1087 commit and a commit must be performed before any further updates
1078 1088 are allowed.
1079 1089 '''
1080 1090 node = node and repo.lookup(node) or repo.changelog.tip()
1081 1091 return repo.update(node, allow=merge, force=clean)
1082 1092
1083 1093 def verify(ui, repo):
1084 1094 """verify the integrity of the repository"""
1085 1095 return repo.verify()
1086 1096
1087 1097 # Command options and aliases are listed here, alphabetically
1088 1098
1089 1099 table = {
1090 "^add": (add, [], "hg add [files]"),
1100 "^add": (add,
1101 [('I', 'include', [], 'include path in search'),
1102 ('X', 'exclude', [], 'exclude path from search')],
1103 "hg add [options] [files]"),
1091 1104 "addremove": (addremove, [], "hg addremove [files]"),
1092 1105 "^annotate":
1093 1106 (annotate,
1094 1107 [('r', 'revision', '', 'revision'),
1095 1108 ('u', 'user', None, 'show user'),
1096 1109 ('n', 'number', None, 'show revision number'),
1097 1110 ('c', 'changeset', None, 'show changeset')],
1098 1111 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
1099 1112 "cat":
1100 1113 (cat,
1101 1114 [('o', 'output', "", 'output to file')],
1102 1115 'hg cat [-o outfile] <file> [rev]'),
1103 1116 "^clone":
1104 1117 (clone,
1105 1118 [('U', 'noupdate', None, 'skip update after cloning')],
1106 1119 'hg clone [options] <source> [dest]'),
1107 1120 "^commit|ci":
1108 1121 (commit,
1109 1122 [('t', 'text', "", 'commit text'),
1110 1123 ('A', 'addremove', None, 'run add/remove during commit'),
1111 1124 ('l', 'logfile', "", 'commit text file'),
1112 1125 ('d', 'date', "", 'date code'),
1113 1126 ('u', 'user', "", 'user')],
1114 1127 'hg commit [files]'),
1115 1128 "copy": (copy, [], 'hg copy <source> <dest>'),
1116 1129 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1117 1130 "debugstate": (debugstate, [], 'debugstate'),
1118 1131 "debugindex": (debugindex, [], 'debugindex <file>'),
1119 1132 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
1120 1133 "^diff":
1121 1134 (diff,
1122 1135 [('r', 'rev', [], 'revision')],
1123 1136 'hg diff [-r A] [-r B] [files]'),
1124 1137 "^export":
1125 1138 (export,
1126 1139 [('o', 'output', "", 'output to file')],
1127 1140 "hg export [-o file] <changeset> ..."),
1128 1141 "forget": (forget, [], "hg forget [files]"),
1129 1142 "heads": (heads, [], 'hg heads'),
1130 1143 "help": (help_, [], 'hg help [command]'),
1131 1144 "identify|id": (identify, [], 'hg identify'),
1132 1145 "import|patch":
1133 1146 (import_,
1134 1147 [('p', 'strip', 1, 'path strip'),
1135 1148 ('b', 'base', "", 'base path')],
1136 1149 "hg import [options] <patches>"),
1137 1150 "^init": (init, [], 'hg init'),
1138 1151 "locate":
1139 1152 (locate,
1140 1153 [('0', 'print0', None, 'end records with NUL'),
1141 1154 ('f', 'fullpath', None, 'print complete paths'),
1142 ('i', 'include', [], 'include path in search'),
1155 ('I', 'include', [], 'include path in search'),
1143 1156 ('r', 'rev', '', 'revision'),
1144 ('x', 'exclude', [], 'exclude path from search')],
1157 ('X', 'exclude', [], 'exclude path from search')],
1145 1158 'hg locate [options] [files]'),
1146 1159 "^log|history":
1147 1160 (log,
1148 1161 [('r', 'rev', [], 'revision'),
1149 1162 ('p', 'patch', None, 'show patch')],
1150 1163 'hg log [-r A] [-r B] [-p] [file]'),
1151 1164 "manifest": (manifest, [], 'hg manifest [rev]'),
1152 1165 "parents": (parents, [], 'hg parents [node]'),
1153 1166 "^pull":
1154 1167 (pull,
1155 1168 [('u', 'update', None, 'update working directory')],
1156 1169 'hg pull [options] [source]'),
1157 1170 "^push": (push, [], 'hg push <destination>'),
1158 1171 "rawcommit":
1159 1172 (rawcommit,
1160 1173 [('p', 'parent', [], 'parent'),
1161 1174 ('d', 'date', "", 'date code'),
1162 1175 ('u', 'user', "", 'user'),
1163 1176 ('F', 'files', "", 'file list'),
1164 1177 ('t', 'text', "", 'commit text'),
1165 1178 ('l', 'logfile', "", 'commit text file')],
1166 1179 'hg rawcommit [options] [files]'),
1167 1180 "recover": (recover, [], "hg recover"),
1168 1181 "^remove|rm": (remove, [], "hg remove [files]"),
1169 1182 "^revert":
1170 1183 (revert,
1171 1184 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1172 1185 ("r", "rev", "", "revision")],
1173 1186 "hg revert [files|dirs]"),
1174 1187 "root": (root, [], "hg root"),
1175 1188 "^serve":
1176 1189 (serve,
1177 1190 [('A', 'accesslog', '', 'access log file'),
1178 1191 ('E', 'errorlog', '', 'error log file'),
1179 1192 ('p', 'port', 8000, 'listen port'),
1180 1193 ('a', 'address', '', 'interface address'),
1181 1194 ('n', 'name', os.getcwd(), 'repository name'),
1182 1195 ('', 'stdio', None, 'for remote clients'),
1183 1196 ('t', 'templates', "", 'template map')],
1184 1197 "hg serve [options]"),
1185 1198 "^status": (status, [], 'hg status'),
1186 1199 "tag":
1187 1200 (tag,
1188 1201 [('l', 'local', None, 'make the tag local'),
1189 1202 ('t', 'text', "", 'commit text'),
1190 1203 ('d', 'date', "", 'date code'),
1191 1204 ('u', 'user', "", 'user')],
1192 1205 'hg tag [options] <name> [rev]'),
1193 1206 "tags": (tags, [], 'hg tags'),
1194 1207 "tip": (tip, [], 'hg tip'),
1195 1208 "undo": (undo, [], 'hg undo'),
1196 1209 "^update|up|checkout|co":
1197 1210 (update,
1198 1211 [('m', 'merge', None, 'allow merging of conflicts'),
1199 1212 ('C', 'clean', None, 'overwrite locally modified files')],
1200 1213 'hg update [options] [node]'),
1201 1214 "verify": (verify, [], 'hg verify'),
1202 1215 "version": (show_version, [], 'hg version'),
1203 1216 }
1204 1217
1205 1218 globalopts = [('v', 'verbose', None, 'verbose'),
1206 1219 ('', 'debug', None, 'debug'),
1207 1220 ('q', 'quiet', None, 'quiet'),
1208 1221 ('', 'profile', None, 'profile'),
1209 1222 ('R', 'repository', "", 'repository root directory'),
1210 1223 ('', 'traceback', None, 'print traceback on exception'),
1211 1224 ('y', 'noninteractive', None, 'run non-interactively'),
1212 1225 ('', 'version', None, 'output version information and exit'),
1213 1226 ]
1214 1227
1215 1228 norepo = "clone init version help debugindex debugindexdot"
1216 1229
1217 1230 def find(cmd):
1218 1231 for e in table.keys():
1219 1232 if re.match("(%s)$" % e, cmd):
1220 1233 return table[e]
1221 1234
1222 1235 raise UnknownCommand(cmd)
1223 1236
1224 1237 class SignalInterrupt(Exception):
1225 1238 """Exception raised on SIGTERM and SIGHUP."""
1226 1239
1227 1240 def catchterm(*args):
1228 1241 raise SignalInterrupt
1229 1242
1230 1243 def run():
1231 1244 sys.exit(dispatch(sys.argv[1:]))
1232 1245
1233 1246 class ParseError(Exception):
1234 1247 """Exception raised on errors in parsing the command line."""
1235 1248
1236 1249 def parse(args):
1237 1250 options = {}
1238 1251 cmdoptions = {}
1239 1252
1240 1253 try:
1241 1254 args = fancyopts.fancyopts(args, globalopts, options)
1242 1255 except fancyopts.getopt.GetoptError, inst:
1243 1256 raise ParseError(None, inst)
1244 1257
1245 1258 if options["version"]:
1246 1259 return ("version", show_version, [], options, cmdoptions)
1247 1260 elif not args:
1248 1261 return ("help", help_, [], options, cmdoptions)
1249 1262 else:
1250 1263 cmd, args = args[0], args[1:]
1251 1264
1252 1265 i = find(cmd)
1253 1266
1254 1267 # combine global options into local
1255 1268 c = list(i[1])
1256 1269 for o in globalopts:
1257 1270 c.append((o[0], o[1], options[o[1]], o[3]))
1258 1271
1259 1272 try:
1260 1273 args = fancyopts.fancyopts(args, c, cmdoptions)
1261 1274 except fancyopts.getopt.GetoptError, inst:
1262 1275 raise ParseError(cmd, inst)
1263 1276
1264 1277 # separate global options back out
1265 1278 for o in globalopts:
1266 1279 n = o[1]
1267 1280 options[n] = cmdoptions[n]
1268 1281 del cmdoptions[n]
1269 1282
1270 1283 return (cmd, i[0], args, options, cmdoptions)
1271 1284
1272 1285 def dispatch(args):
1273 1286 signal.signal(signal.SIGTERM, catchterm)
1274 1287 try:
1275 1288 signal.signal(signal.SIGHUP, catchterm)
1276 1289 except AttributeError:
1277 1290 pass
1278 1291
1279 1292 try:
1280 1293 cmd, func, args, options, cmdoptions = parse(args)
1281 1294 except ParseError, inst:
1282 1295 u = ui.ui()
1283 1296 if inst.args[0]:
1284 1297 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1285 1298 help_(u, inst.args[0])
1286 1299 else:
1287 1300 u.warn("hg: %s\n" % inst.args[1])
1288 1301 help_(u)
1289 1302 sys.exit(-1)
1290 1303 except UnknownCommand, inst:
1291 1304 u = ui.ui()
1292 1305 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1293 1306 help_(u)
1294 1307 sys.exit(1)
1295 1308
1296 1309 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1297 1310 not options["noninteractive"])
1298 1311
1299 1312 try:
1300 1313 try:
1301 1314 if cmd not in norepo.split():
1302 1315 path = options["repository"] or ""
1303 1316 repo = hg.repository(ui=u, path=path)
1304 1317 d = lambda: func(u, repo, *args, **cmdoptions)
1305 1318 else:
1306 1319 d = lambda: func(u, *args, **cmdoptions)
1307 1320
1308 1321 if options['profile']:
1309 1322 import hotshot, hotshot.stats
1310 1323 prof = hotshot.Profile("hg.prof")
1311 1324 r = prof.runcall(d)
1312 1325 prof.close()
1313 1326 stats = hotshot.stats.load("hg.prof")
1314 1327 stats.strip_dirs()
1315 1328 stats.sort_stats('time', 'calls')
1316 1329 stats.print_stats(40)
1317 1330 return r
1318 1331 else:
1319 1332 return d()
1320 1333 except:
1321 1334 if options['traceback']:
1322 1335 traceback.print_exc()
1323 1336 raise
1324 1337 except util.CommandError, inst:
1325 1338 u.warn("abort: %s\n" % inst.args)
1326 1339 except hg.RepoError, inst:
1327 1340 u.warn("abort: ", inst, "!\n")
1328 1341 except SignalInterrupt:
1329 1342 u.warn("killed!\n")
1330 1343 except KeyboardInterrupt:
1331 1344 u.warn("interrupted!\n")
1332 1345 except IOError, inst:
1333 1346 if hasattr(inst, "code"):
1334 1347 u.warn("abort: %s\n" % inst)
1335 1348 elif hasattr(inst, "reason"):
1336 1349 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
1337 1350 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1338 1351 if u.debugflag: u.warn("broken pipe\n")
1339 1352 else:
1340 1353 raise
1341 1354 except OSError, inst:
1342 1355 if hasattr(inst, "filename"):
1343 1356 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1344 1357 else:
1345 1358 u.warn("abort: %s\n" % inst.strerror)
1346 1359 except TypeError, inst:
1347 1360 # was this an argument error?
1348 1361 tb = traceback.extract_tb(sys.exc_info()[2])
1349 1362 if len(tb) > 2: # no
1350 1363 raise
1351 1364 u.debug(inst, "\n")
1352 1365 u.warn("%s: invalid arguments\n" % cmd)
1353 1366 help_(u, cmd)
1354 1367
1355 1368 sys.exit(-1)
@@ -1,1925 +1,1926 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import sys, struct, os
9 9 import util
10 10 from revlog import *
11 11 from demandload import *
12 12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
13 13 demandload(globals(), "tempfile httprangereader bdiff")
14 14 demandload(globals(), "bisect select")
15 15
16 def always(fn):
17 return True
18
19 16 class filelog(revlog):
20 17 def __init__(self, opener, path):
21 18 revlog.__init__(self, opener,
22 19 os.path.join("data", path + ".i"),
23 20 os.path.join("data", path + ".d"))
24 21
25 22 def read(self, node):
26 23 t = self.revision(node)
27 24 if not t.startswith('\1\n'):
28 25 return t
29 26 s = t.find('\1\n', 2)
30 27 return t[s+2:]
31 28
32 29 def readmeta(self, node):
33 30 t = self.revision(node)
34 31 if not t.startswith('\1\n'):
35 32 return t
36 33 s = t.find('\1\n', 2)
37 34 mt = t[2:s]
38 35 for l in mt.splitlines():
39 36 k, v = l.split(": ", 1)
40 37 m[k] = v
41 38 return m
42 39
43 40 def add(self, text, meta, transaction, link, p1=None, p2=None):
44 41 if meta or text.startswith('\1\n'):
45 42 mt = ""
46 43 if meta:
47 44 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
48 45 text = "\1\n" + "".join(mt) + "\1\n" + text
49 46 return self.addrevision(text, transaction, link, p1, p2)
50 47
51 48 def annotate(self, node):
52 49
53 50 def decorate(text, rev):
54 51 return ([rev] * len(text.splitlines()), text)
55 52
56 53 def pair(parent, child):
57 54 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
58 55 child[0][b1:b2] = parent[0][a1:a2]
59 56 return child
60 57
61 58 # find all ancestors
62 59 needed = {node:1}
63 60 visit = [node]
64 61 while visit:
65 62 n = visit.pop(0)
66 63 for p in self.parents(n):
67 64 if p not in needed:
68 65 needed[p] = 1
69 66 visit.append(p)
70 67 else:
71 68 # count how many times we'll use this
72 69 needed[p] += 1
73 70
74 71 # sort by revision which is a topological order
75 72 visit = [ (self.rev(n), n) for n in needed.keys() ]
76 73 visit.sort()
77 74 hist = {}
78 75
79 76 for r,n in visit:
80 77 curr = decorate(self.read(n), self.linkrev(n))
81 78 for p in self.parents(n):
82 79 if p != nullid:
83 80 curr = pair(hist[p], curr)
84 81 # trim the history of unneeded revs
85 82 needed[p] -= 1
86 83 if not needed[p]:
87 84 del hist[p]
88 85 hist[n] = curr
89 86
90 87 return zip(hist[n][0], hist[n][1].splitlines(1))
91 88
92 89 class manifest(revlog):
93 90 def __init__(self, opener):
94 91 self.mapcache = None
95 92 self.listcache = None
96 93 self.addlist = None
97 94 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
98 95
99 96 def read(self, node):
100 97 if node == nullid: return {} # don't upset local cache
101 98 if self.mapcache and self.mapcache[0] == node:
102 99 return self.mapcache[1]
103 100 text = self.revision(node)
104 101 map = {}
105 102 flag = {}
106 103 self.listcache = (text, text.splitlines(1))
107 104 for l in self.listcache[1]:
108 105 (f, n) = l.split('\0')
109 106 map[f] = bin(n[:40])
110 107 flag[f] = (n[40:-1] == "x")
111 108 self.mapcache = (node, map, flag)
112 109 return map
113 110
114 111 def readflags(self, node):
115 112 if node == nullid: return {} # don't upset local cache
116 113 if not self.mapcache or self.mapcache[0] != node:
117 114 self.read(node)
118 115 return self.mapcache[2]
119 116
120 117 def diff(self, a, b):
121 118 # this is sneaky, as we're not actually using a and b
122 119 if self.listcache and self.addlist and self.listcache[0] == a:
123 120 d = mdiff.diff(self.listcache[1], self.addlist, 1)
124 121 if mdiff.patch(a, d) != b:
125 122 sys.stderr.write("*** sortdiff failed, falling back ***\n")
126 123 return mdiff.textdiff(a, b)
127 124 return d
128 125 else:
129 126 return mdiff.textdiff(a, b)
130 127
131 128 def add(self, map, flags, transaction, link, p1=None, p2=None,changed=None):
132 129 # directly generate the mdiff delta from the data collected during
133 130 # the bisect loop below
134 131 def gendelta(delta):
135 132 i = 0
136 133 result = []
137 134 while i < len(delta):
138 135 start = delta[i][2]
139 136 end = delta[i][3]
140 137 l = delta[i][4]
141 138 if l == None:
142 139 l = ""
143 140 while i < len(delta) - 1 and start <= delta[i+1][2] and end >= delta[i+1][2]:
144 141 if delta[i+1][3] > end:
145 142 end = delta[i+1][3]
146 143 if delta[i+1][4]:
147 144 l += delta[i+1][4]
148 145 i += 1
149 146 result.append(struct.pack(">lll", start, end, len(l)) + l)
150 147 i += 1
151 148 return result
152 149
153 150 # apply the changes collected during the bisect loop to our addlist
154 151 def addlistdelta(addlist, delta):
155 152 # apply the deltas to the addlist. start from the bottom up
156 153 # so changes to the offsets don't mess things up.
157 154 i = len(delta)
158 155 while i > 0:
159 156 i -= 1
160 157 start = delta[i][0]
161 158 end = delta[i][1]
162 159 if delta[i][4]:
163 160 addlist[start:end] = [delta[i][4]]
164 161 else:
165 162 del addlist[start:end]
166 163 return addlist
167 164
168 165 # calculate the byte offset of the start of each line in the
169 166 # manifest
170 167 def calcoffsets(addlist):
171 168 offsets = [0] * (len(addlist) + 1)
172 169 offset = 0
173 170 i = 0
174 171 while i < len(addlist):
175 172 offsets[i] = offset
176 173 offset += len(addlist[i])
177 174 i += 1
178 175 offsets[i] = offset
179 176 return offsets
180 177
181 178 # if we're using the listcache, make sure it is valid and
182 179 # parented by the same node we're diffing against
183 180 if not changed or not self.listcache or not p1 or self.mapcache[0] != p1:
184 181 files = map.keys()
185 182 files.sort()
186 183
187 184 self.addlist = ["%s\000%s%s\n" %
188 185 (f, hex(map[f]), flags[f] and "x" or '')
189 186 for f in files]
190 187 cachedelta = None
191 188 else:
192 189 addlist = self.listcache[1]
193 190
194 191 # find the starting offset for each line in the add list
195 192 offsets = calcoffsets(addlist)
196 193
197 194 # combine the changed lists into one list for sorting
198 195 work = [[x, 0] for x in changed[0]]
199 196 work[len(work):] = [[x, 1] for x in changed[1]]
200 197 work.sort()
201 198
202 199 delta = []
203 200 bs = 0
204 201
205 202 for w in work:
206 203 f = w[0]
207 204 # bs will either be the index of the item or the insertion point
208 205 bs = bisect.bisect(addlist, f, bs)
209 206 if bs < len(addlist):
210 207 fn = addlist[bs][:addlist[bs].index('\0')]
211 208 else:
212 209 fn = None
213 210 if w[1] == 0:
214 211 l = "%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '')
215 212 else:
216 213 l = None
217 214 start = bs
218 215 if fn != f:
219 216 # item not found, insert a new one
220 217 end = bs
221 218 if w[1] == 1:
222 219 sys.stderr.write("failed to remove %s from manifest" % f)
223 220 sys.exit(1)
224 221 else:
225 222 # item is found, replace/delete the existing line
226 223 end = bs + 1
227 224 delta.append([start, end, offsets[start], offsets[end], l])
228 225
229 226 self.addlist = addlistdelta(addlist, delta)
230 227 if self.mapcache[0] == self.tip():
231 228 cachedelta = "".join(gendelta(delta))
232 229 else:
233 230 cachedelta = None
234 231
235 232 text = "".join(self.addlist)
236 233 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
237 234 sys.stderr.write("manifest delta failure")
238 235 sys.exit(1)
239 236 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
240 237 self.mapcache = (n, map, flags)
241 238 self.listcache = (text, self.addlist)
242 239 self.addlist = None
243 240
244 241 return n
245 242
246 243 class changelog(revlog):
247 244 def __init__(self, opener):
248 245 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
249 246
250 247 def extract(self, text):
251 248 if not text:
252 249 return (nullid, "", "0", [], "")
253 250 last = text.index("\n\n")
254 251 desc = text[last + 2:]
255 252 l = text[:last].splitlines()
256 253 manifest = bin(l[0])
257 254 user = l[1]
258 255 date = l[2]
259 256 files = l[3:]
260 257 return (manifest, user, date, files, desc)
261 258
262 259 def read(self, node):
263 260 return self.extract(self.revision(node))
264 261
265 262 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
266 263 user=None, date=None):
267 264 date = date or "%d %d" % (time.time(), time.timezone)
268 265 list.sort()
269 266 l = [hex(manifest), user, date] + list + ["", desc]
270 267 text = "\n".join(l)
271 268 return self.addrevision(text, transaction, self.count(), p1, p2)
272 269
273 270 class dirstate:
274 271 def __init__(self, opener, ui, root):
275 272 self.opener = opener
276 273 self.root = root
277 274 self.dirty = 0
278 275 self.ui = ui
279 276 self.map = None
280 277 self.pl = None
281 278 self.copies = {}
282 279 self.ignorefunc = None
283 280
284 281 def wjoin(self, f):
285 282 return os.path.join(self.root, f)
286 283
287 284 def ignore(self, f):
288 285 if not self.ignorefunc:
289 286 bigpat = []
290 287 try:
291 288 l = file(self.wjoin(".hgignore"))
292 289 for pat in l:
293 290 if pat != "\n":
294 291 p = util.pconvert(pat[:-1])
295 292 try:
296 293 r = re.compile(p)
297 294 except:
298 295 self.ui.warn("ignoring invalid ignore"
299 296 + " regular expression '%s'\n" % p)
300 297 else:
301 298 bigpat.append(util.pconvert(pat[:-1]))
302 299 except IOError: pass
303 300
304 301 s = "(?:%s)" % (")|(?:".join(bigpat))
305 302 r = re.compile(s)
306 303 self.ignorefunc = r.search
307 304
308 305 return self.ignorefunc(f)
309 306
310 307 def __del__(self):
311 308 if self.dirty:
312 309 self.write()
313 310
314 311 def __getitem__(self, key):
315 312 try:
316 313 return self.map[key]
317 314 except TypeError:
318 315 self.read()
319 316 return self[key]
320 317
321 318 def __contains__(self, key):
322 319 if not self.map: self.read()
323 320 return key in self.map
324 321
325 322 def parents(self):
326 323 if not self.pl:
327 324 self.read()
328 325 return self.pl
329 326
330 327 def markdirty(self):
331 328 if not self.dirty:
332 329 self.dirty = 1
333 330
334 331 def setparents(self, p1, p2 = nullid):
335 332 self.markdirty()
336 333 self.pl = p1, p2
337 334
338 335 def state(self, key):
339 336 try:
340 337 return self[key][0]
341 338 except KeyError:
342 339 return "?"
343 340
344 341 def read(self):
345 342 if self.map is not None: return self.map
346 343
347 344 self.map = {}
348 345 self.pl = [nullid, nullid]
349 346 try:
350 347 st = self.opener("dirstate").read()
351 348 if not st: return
352 349 except: return
353 350
354 351 self.pl = [st[:20], st[20: 40]]
355 352
356 353 pos = 40
357 354 while pos < len(st):
358 355 e = struct.unpack(">cllll", st[pos:pos+17])
359 356 l = e[4]
360 357 pos += 17
361 358 f = st[pos:pos + l]
362 359 if '\0' in f:
363 360 f, c = f.split('\0')
364 361 self.copies[f] = c
365 362 self.map[f] = e[:4]
366 363 pos += l
367 364
368 365 def copy(self, source, dest):
369 366 self.read()
370 367 self.markdirty()
371 368 self.copies[dest] = source
372 369
373 370 def copied(self, file):
374 371 return self.copies.get(file, None)
375 372
376 373 def update(self, files, state):
377 374 ''' current states:
378 375 n normal
379 376 m needs merging
380 377 r marked for removal
381 378 a marked for addition'''
382 379
383 380 if not files: return
384 381 self.read()
385 382 self.markdirty()
386 383 for f in files:
387 384 if state == "r":
388 385 self.map[f] = ('r', 0, 0, 0)
389 386 else:
390 387 s = os.stat(os.path.join(self.root, f))
391 388 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
392 389
393 390 def forget(self, files):
394 391 if not files: return
395 392 self.read()
396 393 self.markdirty()
397 394 for f in files:
398 395 try:
399 396 del self.map[f]
400 397 except KeyError:
401 398 self.ui.warn("not in dirstate: %s!\n" % f)
402 399 pass
403 400
404 401 def clear(self):
405 402 self.map = {}
406 403 self.markdirty()
407 404
408 405 def write(self):
409 406 st = self.opener("dirstate", "w")
410 407 st.write("".join(self.pl))
411 408 for f, e in self.map.items():
412 409 c = self.copied(f)
413 410 if c:
414 411 f = f + "\0" + c
415 412 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
416 413 st.write(e + f)
417 414 self.dirty = 0
418 415
419 def walk(self, files = None, match = always):
416 def walk(self, files = None, match = util.always):
420 417 self.read()
421 418 dc = self.map.copy()
422 419 # walk all files by default
423 420 if not files: files = [self.root]
424 421 def traverse():
425 422 for f in util.unique(files):
426 423 f = os.path.join(self.root, f)
427 424 if os.path.isdir(f):
428 425 for dir, subdirs, fl in os.walk(f):
429 426 d = dir[len(self.root) + 1:]
430 427 if d == '.hg':
431 428 subdirs[:] = []
432 429 continue
433 430 for sd in subdirs:
434 431 ds = os.path.join(d, sd +'/')
435 432 if self.ignore(ds) or not match(ds):
436 433 subdirs.remove(sd)
437 434 for fn in fl:
438 435 fn = util.pconvert(os.path.join(d, fn))
439 436 yield fn
440 437 else:
441 438 yield f[len(self.root) + 1:]
442 439
443 440 for k in dc.keys():
444 441 yield k
445 442
446 443 # yield only files that match: all in dirstate, others only if
447 444 # not in .hgignore
448 445
449 446 for fn in util.unique(traverse()):
450 447 if fn in dc:
451 448 del dc[fn]
452 449 elif self.ignore(fn):
453 450 continue
454 451 if match(fn):
455 452 yield fn
456 453
457 def changes(self, files = None, match = always):
454 def changes(self, files = None, match = util.always):
458 455 self.read()
459 456 dc = self.map.copy()
460 457 lookup, changed, added, unknown = [], [], [], []
461 458
462 459 for fn in self.walk(files, match):
463 460 try: s = os.stat(os.path.join(self.root, fn))
464 461 except: continue
465 462
466 463 if fn in dc:
467 464 c = dc[fn]
468 465 del dc[fn]
469 466
470 467 if c[0] == 'm':
471 468 changed.append(fn)
472 469 elif c[0] == 'a':
473 470 added.append(fn)
474 471 elif c[0] == 'r':
475 472 unknown.append(fn)
476 473 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
477 474 changed.append(fn)
478 475 elif c[1] != s.st_mode or c[3] != s.st_mtime:
479 476 lookup.append(fn)
480 477 else:
481 478 if match(fn): unknown.append(fn)
482 479
483 480 return (lookup, changed, added, dc.keys(), unknown)
484 481
485 482 # used to avoid circular references so destructors work
486 483 def opener(base):
487 484 p = base
488 485 def o(path, mode="r"):
489 486 if p.startswith("http://"):
490 487 f = os.path.join(p, urllib.quote(path))
491 488 return httprangereader.httprangereader(f)
492 489
493 490 f = os.path.join(p, path)
494 491
495 492 mode += "b" # for that other OS
496 493
497 494 if mode[0] != "r":
498 495 try:
499 496 s = os.stat(f)
500 497 except OSError:
501 498 d = os.path.dirname(f)
502 499 if not os.path.isdir(d):
503 500 os.makedirs(d)
504 501 else:
505 502 if s.st_nlink > 1:
506 503 file(f + ".tmp", "wb").write(file(f, "rb").read())
507 504 util.rename(f+".tmp", f)
508 505
509 506 return file(f, mode)
510 507
511 508 return o
512 509
513 510 class RepoError(Exception): pass
514 511
515 512 class localrepository:
516 513 def __init__(self, ui, path=None, create=0):
517 514 self.remote = 0
518 515 if path and path.startswith("http://"):
519 516 self.remote = 1
520 517 self.path = path
521 518 else:
522 519 if not path:
523 520 p = os.getcwd()
524 521 while not os.path.isdir(os.path.join(p, ".hg")):
525 522 oldp = p
526 523 p = os.path.dirname(p)
527 524 if p == oldp: raise RepoError("no repo found")
528 525 path = p
529 526 self.path = os.path.join(path, ".hg")
530 527
531 528 if not create and not os.path.isdir(self.path):
532 529 raise RepoError("repository %s not found" % self.path)
533 530
534 531 self.root = path
535 532 self.ui = ui
536 533
537 534 if create:
538 535 os.mkdir(self.path)
539 536 os.mkdir(self.join("data"))
540 537
541 538 self.opener = opener(self.path)
542 539 self.wopener = opener(self.root)
543 540 self.manifest = manifest(self.opener)
544 541 self.changelog = changelog(self.opener)
545 542 self.tagscache = None
546 543 self.nodetagscache = None
547 544
548 545 if not self.remote:
549 546 self.dirstate = dirstate(self.opener, ui, self.root)
550 547 try:
551 548 self.ui.readconfig(self.opener("hgrc"))
552 549 except IOError: pass
553 550
554 551 def hook(self, name, **args):
555 552 s = self.ui.config("hooks", name)
556 553 if s:
557 554 self.ui.note("running hook %s: %s\n" % (name, s))
558 555 old = {}
559 556 for k, v in args.items():
560 557 k = k.upper()
561 558 old[k] = os.environ.get(k, None)
562 559 os.environ[k] = v
563 560
564 561 r = os.system(s)
565 562
566 563 for k, v in old.items():
567 564 if v != None:
568 565 os.environ[k] = v
569 566 else:
570 567 del os.environ[k]
571 568
572 569 if r:
573 570 self.ui.warn("abort: %s hook failed with status %d!\n" %
574 571 (name, r))
575 572 return False
576 573 return True
577 574
578 575 def tags(self):
579 576 '''return a mapping of tag to node'''
580 577 if not self.tagscache:
581 578 self.tagscache = {}
582 579 def addtag(self, k, n):
583 580 try:
584 581 bin_n = bin(n)
585 582 except TypeError:
586 583 bin_n = ''
587 584 self.tagscache[k.strip()] = bin_n
588 585
589 586 try:
590 587 # read each head of the tags file, ending with the tip
591 588 # and add each tag found to the map, with "newer" ones
592 589 # taking precedence
593 590 fl = self.file(".hgtags")
594 591 h = fl.heads()
595 592 h.reverse()
596 593 for r in h:
597 594 for l in fl.revision(r).splitlines():
598 595 if l:
599 596 n, k = l.split(" ", 1)
600 597 addtag(self, k, n)
601 598 except KeyError:
602 599 pass
603 600
604 601 try:
605 602 f = self.opener("localtags")
606 603 for l in f:
607 604 n, k = l.split(" ", 1)
608 605 addtag(self, k, n)
609 606 except IOError:
610 607 pass
611 608
612 609 self.tagscache['tip'] = self.changelog.tip()
613 610
614 611 return self.tagscache
615 612
616 613 def tagslist(self):
617 614 '''return a list of tags ordered by revision'''
618 615 l = []
619 616 for t, n in self.tags().items():
620 617 try:
621 618 r = self.changelog.rev(n)
622 619 except:
623 620 r = -2 # sort to the beginning of the list if unknown
624 621 l.append((r,t,n))
625 622 l.sort()
626 623 return [(t,n) for r,t,n in l]
627 624
628 625 def nodetags(self, node):
629 626 '''return the tags associated with a node'''
630 627 if not self.nodetagscache:
631 628 self.nodetagscache = {}
632 629 for t,n in self.tags().items():
633 630 self.nodetagscache.setdefault(n,[]).append(t)
634 631 return self.nodetagscache.get(node, [])
635 632
636 633 def lookup(self, key):
637 634 try:
638 635 return self.tags()[key]
639 636 except KeyError:
640 637 try:
641 638 return self.changelog.lookup(key)
642 639 except:
643 640 raise RepoError("unknown revision '%s'" % key)
644 641
645 642 def dev(self):
646 643 if self.remote: return -1
647 644 return os.stat(self.path).st_dev
648 645
649 646 def join(self, f):
650 647 return os.path.join(self.path, f)
651 648
652 649 def wjoin(self, f):
653 650 return os.path.join(self.root, f)
654 651
655 652 def file(self, f):
656 653 if f[0] == '/': f = f[1:]
657 654 return filelog(self.opener, f)
658 655
659 656 def getcwd(self):
660 657 cwd = os.getcwd()
661 658 if cwd == self.root: return ''
662 659 return cwd[len(self.root) + 1:]
663 660
664 661 def wfile(self, f, mode='r'):
665 662 return self.wopener(f, mode)
666 663
667 664 def transaction(self):
668 665 # save dirstate for undo
669 666 try:
670 667 ds = self.opener("dirstate").read()
671 668 except IOError:
672 669 ds = ""
673 670 self.opener("undo.dirstate", "w").write(ds)
674 671
675 672 return transaction.transaction(self.ui.warn,
676 673 self.opener, self.join("journal"),
677 674 self.join("undo"))
678 675
679 676 def recover(self):
680 677 lock = self.lock()
681 678 if os.path.exists(self.join("journal")):
682 679 self.ui.status("rolling back interrupted transaction\n")
683 680 return transaction.rollback(self.opener, self.join("journal"))
684 681 else:
685 682 self.ui.warn("no interrupted transaction available\n")
686 683
687 684 def undo(self):
688 685 lock = self.lock()
689 686 if os.path.exists(self.join("undo")):
690 687 self.ui.status("rolling back last transaction\n")
691 688 transaction.rollback(self.opener, self.join("undo"))
692 689 self.dirstate = None
693 690 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
694 691 self.dirstate = dirstate(self.opener, self.ui, self.root)
695 692 else:
696 693 self.ui.warn("no undo information available\n")
697 694
698 695 def lock(self, wait = 1):
699 696 try:
700 697 return lock.lock(self.join("lock"), 0)
701 698 except lock.LockHeld, inst:
702 699 if wait:
703 700 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
704 701 return lock.lock(self.join("lock"), wait)
705 702 raise inst
706 703
707 704 def rawcommit(self, files, text, user, date, p1=None, p2=None):
708 705 orig_parent = self.dirstate.parents()[0] or nullid
709 706 p1 = p1 or self.dirstate.parents()[0] or nullid
710 707 p2 = p2 or self.dirstate.parents()[1] or nullid
711 708 c1 = self.changelog.read(p1)
712 709 c2 = self.changelog.read(p2)
713 710 m1 = self.manifest.read(c1[0])
714 711 mf1 = self.manifest.readflags(c1[0])
715 712 m2 = self.manifest.read(c2[0])
716 713
717 714 if orig_parent == p1:
718 715 update_dirstate = 1
719 716 else:
720 717 update_dirstate = 0
721 718
722 719 tr = self.transaction()
723 720 mm = m1.copy()
724 721 mfm = mf1.copy()
725 722 linkrev = self.changelog.count()
726 723 for f in files:
727 724 try:
728 725 t = self.wfile(f).read()
729 726 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
730 727 r = self.file(f)
731 728 mfm[f] = tm
732 729 mm[f] = r.add(t, {}, tr, linkrev,
733 730 m1.get(f, nullid), m2.get(f, nullid))
734 731 if update_dirstate:
735 732 self.dirstate.update([f], "n")
736 733 except IOError:
737 734 try:
738 735 del mm[f]
739 736 del mfm[f]
740 737 if update_dirstate:
741 738 self.dirstate.forget([f])
742 739 except:
743 740 # deleted from p2?
744 741 pass
745 742
746 743 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
747 744 user = user or self.ui.username()
748 745 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
749 746 tr.close()
750 747 if update_dirstate:
751 748 self.dirstate.setparents(n, nullid)
752 749
753 750 def commit(self, files = None, text = "", user = None, date = None):
754 751 commit = []
755 752 remove = []
756 753 if files:
757 754 for f in files:
758 755 s = self.dirstate.state(f)
759 756 if s in 'nmai':
760 757 commit.append(f)
761 758 elif s == 'r':
762 759 remove.append(f)
763 760 else:
764 761 self.ui.warn("%s not tracked!\n" % f)
765 762 else:
766 763 (c, a, d, u) = self.changes()
767 764 commit = c + a
768 765 remove = d
769 766
770 767 if not commit and not remove:
771 768 self.ui.status("nothing changed\n")
772 769 return
773 770
774 771 if not self.hook("precommit"):
775 772 return 1
776 773
777 774 p1, p2 = self.dirstate.parents()
778 775 c1 = self.changelog.read(p1)
779 776 c2 = self.changelog.read(p2)
780 777 m1 = self.manifest.read(c1[0])
781 778 mf1 = self.manifest.readflags(c1[0])
782 779 m2 = self.manifest.read(c2[0])
783 780 lock = self.lock()
784 781 tr = self.transaction()
785 782
786 783 # check in files
787 784 new = {}
788 785 linkrev = self.changelog.count()
789 786 commit.sort()
790 787 for f in commit:
791 788 self.ui.note(f + "\n")
792 789 try:
793 790 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
794 791 t = self.wfile(f).read()
795 792 except IOError:
796 793 self.ui.warn("trouble committing %s!\n" % f)
797 794 raise
798 795
799 796 meta = {}
800 797 cp = self.dirstate.copied(f)
801 798 if cp:
802 799 meta["copy"] = cp
803 800 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
804 801 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
805 802
806 803 r = self.file(f)
807 804 fp1 = m1.get(f, nullid)
808 805 fp2 = m2.get(f, nullid)
809 806 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
810 807
811 808 # update manifest
812 809 m1.update(new)
813 810 for f in remove:
814 811 if f in m1:
815 812 del m1[f]
816 813 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0], (new,remove))
817 814
818 815 # add changeset
819 816 new = new.keys()
820 817 new.sort()
821 818
822 819 if not text:
823 820 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
824 821 edittext += "".join(["HG: changed %s\n" % f for f in new])
825 822 edittext += "".join(["HG: removed %s\n" % f for f in remove])
826 823 edittext = self.ui.edit(edittext)
827 824 if not edittext.rstrip():
828 825 return 1
829 826 text = edittext
830 827
831 828 user = user or self.ui.username()
832 829 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
833 830
834 831 tr.close()
835 832
836 833 self.dirstate.setparents(n)
837 834 self.dirstate.update(new, "n")
838 835 self.dirstate.forget(remove)
839 836
840 837 if not self.hook("commit", node=hex(n)):
841 838 return 1
842 839
843 def walk(self, rev = None, files = [], match = always):
844 if rev is None: fns = self.dirstate.walk(files, match)
845 else: fns = filter(match, self.manifest.read(rev))
840 def walk(self, node = None, files = [], match = util.always):
841 if node:
842 change = self.changelog.read(node)
843 fns = filter(match, self.manifest.read(change[0]))
844 else:
845 fns = self.dirstate.walk(files, match)
846 846 for fn in fns: yield fn
847 847
848 def changes(self, node1 = None, node2 = None, files = [], match = always):
848 def changes(self, node1 = None, node2 = None, files = [],
849 match = util.always):
849 850 mf2, u = None, []
850 851
851 852 def fcmp(fn, mf):
852 853 t1 = self.wfile(fn).read()
853 854 t2 = self.file(fn).revision(mf[fn])
854 855 return cmp(t1, t2)
855 856
856 857 def mfmatches(node):
857 858 mf = dict(self.manifest.read(node))
858 859 for fn in mf.keys():
859 860 if not match(fn):
860 861 del mf[fn]
861 862 return mf
862 863
863 864 # are we comparing the working directory?
864 865 if not node2:
865 866 l, c, a, d, u = self.dirstate.changes(files, match)
866 867
867 868 # are we comparing working dir against its parent?
868 869 if not node1:
869 870 if l:
870 871 # do a full compare of any files that might have changed
871 872 change = self.changelog.read(self.dirstate.parents()[0])
872 873 mf2 = mfmatches(change[0])
873 874 for f in l:
874 875 if fcmp(f, mf2):
875 876 c.append(f)
876 877
877 878 for l in c, a, d, u:
878 879 l.sort()
879 880
880 881 return (c, a, d, u)
881 882
882 883 # are we comparing working dir against non-tip?
883 884 # generate a pseudo-manifest for the working dir
884 885 if not node2:
885 886 if not mf2:
886 887 change = self.changelog.read(self.dirstate.parents()[0])
887 888 mf2 = mfmatches(change[0])
888 889 for f in a + c + l:
889 890 mf2[f] = ""
890 891 for f in d:
891 892 if f in mf2: del mf2[f]
892 893 else:
893 894 change = self.changelog.read(node2)
894 895 mf2 = mfmatches(change[0])
895 896
896 897 # flush lists from dirstate before comparing manifests
897 898 c, a = [], []
898 899
899 900 change = self.changelog.read(node1)
900 901 mf1 = mfmatches(change[0])
901 902
902 903 for fn in mf2:
903 904 if mf1.has_key(fn):
904 905 if mf1[fn] != mf2[fn]:
905 906 if mf2[fn] != "" or fcmp(fn, mf1):
906 907 c.append(fn)
907 908 del mf1[fn]
908 909 else:
909 910 a.append(fn)
910 911
911 912 d = mf1.keys()
912 913
913 914 for l in c, a, d, u:
914 915 l.sort()
915 916
916 917 return (c, a, d, u)
917 918
918 919 def add(self, list):
919 920 for f in list:
920 921 p = self.wjoin(f)
921 922 if not os.path.exists(p):
922 923 self.ui.warn("%s does not exist!\n" % f)
923 924 elif not os.path.isfile(p):
924 925 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
925 elif self.dirstate.state(f) == 'n':
926 elif self.dirstate.state(f) in 'an':
926 927 self.ui.warn("%s already tracked!\n" % f)
927 928 else:
928 929 self.dirstate.update([f], "a")
929 930
930 931 def forget(self, list):
931 932 for f in list:
932 933 if self.dirstate.state(f) not in 'ai':
933 934 self.ui.warn("%s not added!\n" % f)
934 935 else:
935 936 self.dirstate.forget([f])
936 937
937 938 def remove(self, list):
938 939 for f in list:
939 940 p = self.wjoin(f)
940 941 if os.path.exists(p):
941 942 self.ui.warn("%s still exists!\n" % f)
942 943 elif self.dirstate.state(f) == 'a':
943 944 self.ui.warn("%s never committed!\n" % f)
944 945 self.dirstate.forget([f])
945 946 elif f not in self.dirstate:
946 947 self.ui.warn("%s not tracked!\n" % f)
947 948 else:
948 949 self.dirstate.update([f], "r")
949 950
950 951 def copy(self, source, dest):
951 952 p = self.wjoin(dest)
952 953 if not os.path.exists(dest):
953 954 self.ui.warn("%s does not exist!\n" % dest)
954 955 elif not os.path.isfile(dest):
955 956 self.ui.warn("copy failed: %s is not a file\n" % dest)
956 957 else:
957 958 if self.dirstate.state(dest) == '?':
958 959 self.dirstate.update([dest], "a")
959 960 self.dirstate.copy(source, dest)
960 961
961 962 def heads(self):
962 963 return self.changelog.heads()
963 964
964 965 def branches(self, nodes):
965 966 if not nodes: nodes = [self.changelog.tip()]
966 967 b = []
967 968 for n in nodes:
968 969 t = n
969 970 while n:
970 971 p = self.changelog.parents(n)
971 972 if p[1] != nullid or p[0] == nullid:
972 973 b.append((t, n, p[0], p[1]))
973 974 break
974 975 n = p[0]
975 976 return b
976 977
977 978 def between(self, pairs):
978 979 r = []
979 980
980 981 for top, bottom in pairs:
981 982 n, l, i = top, [], 0
982 983 f = 1
983 984
984 985 while n != bottom:
985 986 p = self.changelog.parents(n)[0]
986 987 if i == f:
987 988 l.append(n)
988 989 f = f * 2
989 990 n = p
990 991 i += 1
991 992
992 993 r.append(l)
993 994
994 995 return r
995 996
996 997 def newer(self, nodes):
997 998 m = {}
998 999 nl = []
999 1000 pm = {}
1000 1001 cl = self.changelog
1001 1002 t = l = cl.count()
1002 1003
1003 1004 # find the lowest numbered node
1004 1005 for n in nodes:
1005 1006 l = min(l, cl.rev(n))
1006 1007 m[n] = 1
1007 1008
1008 1009 for i in xrange(l, t):
1009 1010 n = cl.node(i)
1010 1011 if n in m: # explicitly listed
1011 1012 pm[n] = 1
1012 1013 nl.append(n)
1013 1014 continue
1014 1015 for p in cl.parents(n):
1015 1016 if p in pm: # parent listed
1016 1017 pm[n] = 1
1017 1018 nl.append(n)
1018 1019 break
1019 1020
1020 1021 return nl
1021 1022
1022 1023 def findincoming(self, remote, base={}):
1023 1024 m = self.changelog.nodemap
1024 1025 search = []
1025 1026 fetch = []
1026 1027 seen = {}
1027 1028 seenbranch = {}
1028 1029
1029 1030 # assume we're closer to the tip than the root
1030 1031 # and start by examining the heads
1031 1032 self.ui.status("searching for changes\n")
1032 1033 heads = remote.heads()
1033 1034 unknown = []
1034 1035 for h in heads:
1035 1036 if h not in m:
1036 1037 unknown.append(h)
1037 1038 else:
1038 1039 base[h] = 1
1039 1040
1040 1041 if not unknown:
1041 1042 return None
1042 1043
1043 1044 rep = {}
1044 1045 reqcnt = 0
1045 1046
1046 1047 # search through remote branches
1047 1048 # a 'branch' here is a linear segment of history, with four parts:
1048 1049 # head, root, first parent, second parent
1049 1050 # (a branch always has two parents (or none) by definition)
1050 1051 unknown = remote.branches(unknown)
1051 1052 while unknown:
1052 1053 r = []
1053 1054 while unknown:
1054 1055 n = unknown.pop(0)
1055 1056 if n[0] in seen:
1056 1057 continue
1057 1058
1058 1059 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1059 1060 if n[0] == nullid:
1060 1061 break
1061 1062 if n in seenbranch:
1062 1063 self.ui.debug("branch already found\n")
1063 1064 continue
1064 1065 if n[1] and n[1] in m: # do we know the base?
1065 1066 self.ui.debug("found incomplete branch %s:%s\n"
1066 1067 % (short(n[0]), short(n[1])))
1067 1068 search.append(n) # schedule branch range for scanning
1068 1069 seenbranch[n] = 1
1069 1070 else:
1070 1071 if n[1] not in seen and n[1] not in fetch:
1071 1072 if n[2] in m and n[3] in m:
1072 1073 self.ui.debug("found new changeset %s\n" %
1073 1074 short(n[1]))
1074 1075 fetch.append(n[1]) # earliest unknown
1075 1076 base[n[2]] = 1 # latest known
1076 1077 continue
1077 1078
1078 1079 for a in n[2:4]:
1079 1080 if a not in rep:
1080 1081 r.append(a)
1081 1082 rep[a] = 1
1082 1083
1083 1084 seen[n[0]] = 1
1084 1085
1085 1086 if r:
1086 1087 reqcnt += 1
1087 1088 self.ui.debug("request %d: %s\n" %
1088 1089 (reqcnt, " ".join(map(short, r))))
1089 1090 for p in range(0, len(r), 10):
1090 1091 for b in remote.branches(r[p:p+10]):
1091 1092 self.ui.debug("received %s:%s\n" %
1092 1093 (short(b[0]), short(b[1])))
1093 1094 if b[0] not in m and b[0] not in seen:
1094 1095 unknown.append(b)
1095 1096
1096 1097 # do binary search on the branches we found
1097 1098 while search:
1098 1099 n = search.pop(0)
1099 1100 reqcnt += 1
1100 1101 l = remote.between([(n[0], n[1])])[0]
1101 1102 l.append(n[1])
1102 1103 p = n[0]
1103 1104 f = 1
1104 1105 for i in l:
1105 1106 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1106 1107 if i in m:
1107 1108 if f <= 2:
1108 1109 self.ui.debug("found new branch changeset %s\n" %
1109 1110 short(p))
1110 1111 fetch.append(p)
1111 1112 base[i] = 1
1112 1113 else:
1113 1114 self.ui.debug("narrowed branch search to %s:%s\n"
1114 1115 % (short(p), short(i)))
1115 1116 search.append((p, i))
1116 1117 break
1117 1118 p, f = i, f * 2
1118 1119
1119 1120 # sanity check our fetch list
1120 1121 for f in fetch:
1121 1122 if f in m:
1122 1123 raise RepoError("already have changeset " + short(f[:4]))
1123 1124
1124 1125 if base.keys() == [nullid]:
1125 1126 self.ui.warn("warning: pulling from an unrelated repository!\n")
1126 1127
1127 1128 self.ui.note("adding new changesets starting at " +
1128 1129 " ".join([short(f) for f in fetch]) + "\n")
1129 1130
1130 1131 self.ui.debug("%d total queries\n" % reqcnt)
1131 1132
1132 1133 return fetch
1133 1134
1134 1135 def findoutgoing(self, remote):
1135 1136 base = {}
1136 1137 self.findincoming(remote, base)
1137 1138 remain = dict.fromkeys(self.changelog.nodemap)
1138 1139
1139 1140 # prune everything remote has from the tree
1140 1141 del remain[nullid]
1141 1142 remove = base.keys()
1142 1143 while remove:
1143 1144 n = remove.pop(0)
1144 1145 if n in remain:
1145 1146 del remain[n]
1146 1147 for p in self.changelog.parents(n):
1147 1148 remove.append(p)
1148 1149
1149 1150 # find every node whose parents have been pruned
1150 1151 subset = []
1151 1152 for n in remain:
1152 1153 p1, p2 = self.changelog.parents(n)
1153 1154 if p1 not in remain and p2 not in remain:
1154 1155 subset.append(n)
1155 1156
1156 1157 # this is the set of all roots we have to push
1157 1158 return subset
1158 1159
1159 1160 def pull(self, remote):
1160 1161 lock = self.lock()
1161 1162
1162 1163 # if we have an empty repo, fetch everything
1163 1164 if self.changelog.tip() == nullid:
1164 1165 self.ui.status("requesting all changes\n")
1165 1166 fetch = [nullid]
1166 1167 else:
1167 1168 fetch = self.findincoming(remote)
1168 1169
1169 1170 if not fetch:
1170 1171 self.ui.status("no changes found\n")
1171 1172 return 1
1172 1173
1173 1174 cg = remote.changegroup(fetch)
1174 1175 return self.addchangegroup(cg)
1175 1176
1176 1177 def push(self, remote):
1177 1178 lock = remote.lock()
1178 1179 update = self.findoutgoing(remote)
1179 1180 if not update:
1180 1181 self.ui.status("no changes found\n")
1181 1182 return 1
1182 1183
1183 1184 cg = self.changegroup(update)
1184 1185 return remote.addchangegroup(cg)
1185 1186
1186 1187 def changegroup(self, basenodes):
1187 1188 class genread:
1188 1189 def __init__(self, generator):
1189 1190 self.g = generator
1190 1191 self.buf = ""
1191 1192 def read(self, l):
1192 1193 while l > len(self.buf):
1193 1194 try:
1194 1195 self.buf += self.g.next()
1195 1196 except StopIteration:
1196 1197 break
1197 1198 d, self.buf = self.buf[:l], self.buf[l:]
1198 1199 return d
1199 1200
1200 1201 def gengroup():
1201 1202 nodes = self.newer(basenodes)
1202 1203
1203 1204 # construct the link map
1204 1205 linkmap = {}
1205 1206 for n in nodes:
1206 1207 linkmap[self.changelog.rev(n)] = n
1207 1208
1208 1209 # construct a list of all changed files
1209 1210 changed = {}
1210 1211 for n in nodes:
1211 1212 c = self.changelog.read(n)
1212 1213 for f in c[3]:
1213 1214 changed[f] = 1
1214 1215 changed = changed.keys()
1215 1216 changed.sort()
1216 1217
1217 1218 # the changegroup is changesets + manifests + all file revs
1218 1219 revs = [ self.changelog.rev(n) for n in nodes ]
1219 1220
1220 1221 for y in self.changelog.group(linkmap): yield y
1221 1222 for y in self.manifest.group(linkmap): yield y
1222 1223 for f in changed:
1223 1224 yield struct.pack(">l", len(f) + 4) + f
1224 1225 g = self.file(f).group(linkmap)
1225 1226 for y in g:
1226 1227 yield y
1227 1228
1228 1229 yield struct.pack(">l", 0)
1229 1230
1230 1231 return genread(gengroup())
1231 1232
1232 1233 def addchangegroup(self, source):
1233 1234
1234 1235 def getchunk():
1235 1236 d = source.read(4)
1236 1237 if not d: return ""
1237 1238 l = struct.unpack(">l", d)[0]
1238 1239 if l <= 4: return ""
1239 1240 return source.read(l - 4)
1240 1241
1241 1242 def getgroup():
1242 1243 while 1:
1243 1244 c = getchunk()
1244 1245 if not c: break
1245 1246 yield c
1246 1247
1247 1248 def csmap(x):
1248 1249 self.ui.debug("add changeset %s\n" % short(x))
1249 1250 return self.changelog.count()
1250 1251
1251 1252 def revmap(x):
1252 1253 return self.changelog.rev(x)
1253 1254
1254 1255 if not source: return
1255 1256 changesets = files = revisions = 0
1256 1257
1257 1258 tr = self.transaction()
1258 1259
1259 1260 # pull off the changeset group
1260 1261 self.ui.status("adding changesets\n")
1261 1262 co = self.changelog.tip()
1262 1263 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1263 1264 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1264 1265
1265 1266 # pull off the manifest group
1266 1267 self.ui.status("adding manifests\n")
1267 1268 mm = self.manifest.tip()
1268 1269 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1269 1270
1270 1271 # process the files
1271 1272 self.ui.status("adding file revisions\n")
1272 1273 while 1:
1273 1274 f = getchunk()
1274 1275 if not f: break
1275 1276 self.ui.debug("adding %s revisions\n" % f)
1276 1277 fl = self.file(f)
1277 1278 o = fl.count()
1278 1279 n = fl.addgroup(getgroup(), revmap, tr)
1279 1280 revisions += fl.count() - o
1280 1281 files += 1
1281 1282
1282 1283 self.ui.status(("modified %d files, added %d changesets" +
1283 1284 " and %d new revisions\n")
1284 1285 % (files, changesets, revisions))
1285 1286
1286 1287 tr.close()
1287 1288 return
1288 1289
1289 1290 def update(self, node, allow=False, force=False, choose=None,
1290 1291 moddirstate=True):
1291 1292 pl = self.dirstate.parents()
1292 1293 if not force and pl[1] != nullid:
1293 1294 self.ui.warn("aborting: outstanding uncommitted merges\n")
1294 1295 return 1
1295 1296
1296 1297 p1, p2 = pl[0], node
1297 1298 pa = self.changelog.ancestor(p1, p2)
1298 1299 m1n = self.changelog.read(p1)[0]
1299 1300 m2n = self.changelog.read(p2)[0]
1300 1301 man = self.manifest.ancestor(m1n, m2n)
1301 1302 m1 = self.manifest.read(m1n)
1302 1303 mf1 = self.manifest.readflags(m1n)
1303 1304 m2 = self.manifest.read(m2n)
1304 1305 mf2 = self.manifest.readflags(m2n)
1305 1306 ma = self.manifest.read(man)
1306 1307 mfa = self.manifest.readflags(man)
1307 1308
1308 1309 (c, a, d, u) = self.changes()
1309 1310
1310 1311 # is this a jump, or a merge? i.e. is there a linear path
1311 1312 # from p1 to p2?
1312 1313 linear_path = (pa == p1 or pa == p2)
1313 1314
1314 1315 # resolve the manifest to determine which files
1315 1316 # we care about merging
1316 1317 self.ui.note("resolving manifests\n")
1317 1318 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1318 1319 (force, allow, moddirstate, linear_path))
1319 1320 self.ui.debug(" ancestor %s local %s remote %s\n" %
1320 1321 (short(man), short(m1n), short(m2n)))
1321 1322
1322 1323 merge = {}
1323 1324 get = {}
1324 1325 remove = []
1325 1326 mark = {}
1326 1327
1327 1328 # construct a working dir manifest
1328 1329 mw = m1.copy()
1329 1330 mfw = mf1.copy()
1330 1331 umap = dict.fromkeys(u)
1331 1332
1332 1333 for f in a + c + u:
1333 1334 mw[f] = ""
1334 1335 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1335 1336
1336 1337 for f in d:
1337 1338 if f in mw: del mw[f]
1338 1339
1339 1340 # If we're jumping between revisions (as opposed to merging),
1340 1341 # and if neither the working directory nor the target rev has
1341 1342 # the file, then we need to remove it from the dirstate, to
1342 1343 # prevent the dirstate from listing the file when it is no
1343 1344 # longer in the manifest.
1344 1345 if moddirstate and linear_path and f not in m2:
1345 1346 self.dirstate.forget((f,))
1346 1347
1347 1348 # Compare manifests
1348 1349 for f, n in mw.iteritems():
1349 1350 if choose and not choose(f): continue
1350 1351 if f in m2:
1351 1352 s = 0
1352 1353
1353 1354 # is the wfile new since m1, and match m2?
1354 1355 if f not in m1:
1355 1356 t1 = self.wfile(f).read()
1356 1357 t2 = self.file(f).revision(m2[f])
1357 1358 if cmp(t1, t2) == 0:
1358 1359 mark[f] = 1
1359 1360 n = m2[f]
1360 1361 del t1, t2
1361 1362
1362 1363 # are files different?
1363 1364 if n != m2[f]:
1364 1365 a = ma.get(f, nullid)
1365 1366 # are both different from the ancestor?
1366 1367 if n != a and m2[f] != a:
1367 1368 self.ui.debug(" %s versions differ, resolve\n" % f)
1368 1369 # merge executable bits
1369 1370 # "if we changed or they changed, change in merge"
1370 1371 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1371 1372 mode = ((a^b) | (a^c)) ^ a
1372 1373 merge[f] = (m1.get(f, nullid), m2[f], mode)
1373 1374 s = 1
1374 1375 # are we clobbering?
1375 1376 # is remote's version newer?
1376 1377 # or are we going back in time?
1377 1378 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1378 1379 self.ui.debug(" remote %s is newer, get\n" % f)
1379 1380 get[f] = m2[f]
1380 1381 s = 1
1381 1382 else:
1382 1383 mark[f] = 1
1383 1384 elif f in umap:
1384 1385 # this unknown file is the same as the checkout
1385 1386 get[f] = m2[f]
1386 1387
1387 1388 if not s and mfw[f] != mf2[f]:
1388 1389 if force:
1389 1390 self.ui.debug(" updating permissions for %s\n" % f)
1390 1391 util.set_exec(self.wjoin(f), mf2[f])
1391 1392 else:
1392 1393 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1393 1394 mode = ((a^b) | (a^c)) ^ a
1394 1395 if mode != b:
1395 1396 self.ui.debug(" updating permissions for %s\n" % f)
1396 1397 util.set_exec(self.wjoin(f), mode)
1397 1398 mark[f] = 1
1398 1399 del m2[f]
1399 1400 elif f in ma:
1400 1401 if n != ma[f]:
1401 1402 r = "d"
1402 1403 if not force and (linear_path or allow):
1403 1404 r = self.ui.prompt(
1404 1405 (" local changed %s which remote deleted\n" % f) +
1405 1406 "(k)eep or (d)elete?", "[kd]", "k")
1406 1407 if r == "d":
1407 1408 remove.append(f)
1408 1409 else:
1409 1410 self.ui.debug("other deleted %s\n" % f)
1410 1411 remove.append(f) # other deleted it
1411 1412 else:
1412 1413 if n == m1.get(f, nullid): # same as parent
1413 1414 if p2 == pa: # going backwards?
1414 1415 self.ui.debug("remote deleted %s\n" % f)
1415 1416 remove.append(f)
1416 1417 else:
1417 1418 self.ui.debug("local created %s, keeping\n" % f)
1418 1419 else:
1419 1420 self.ui.debug("working dir created %s, keeping\n" % f)
1420 1421
1421 1422 for f, n in m2.iteritems():
1422 1423 if choose and not choose(f): continue
1423 1424 if f[0] == "/": continue
1424 1425 if f in ma and n != ma[f]:
1425 1426 r = "k"
1426 1427 if not force and (linear_path or allow):
1427 1428 r = self.ui.prompt(
1428 1429 ("remote changed %s which local deleted\n" % f) +
1429 1430 "(k)eep or (d)elete?", "[kd]", "k")
1430 1431 if r == "k": get[f] = n
1431 1432 elif f not in ma:
1432 1433 self.ui.debug("remote created %s\n" % f)
1433 1434 get[f] = n
1434 1435 else:
1435 1436 if force or p2 == pa: # going backwards?
1436 1437 self.ui.debug("local deleted %s, recreating\n" % f)
1437 1438 get[f] = n
1438 1439 else:
1439 1440 self.ui.debug("local deleted %s\n" % f)
1440 1441
1441 1442 del mw, m1, m2, ma
1442 1443
1443 1444 if force:
1444 1445 for f in merge:
1445 1446 get[f] = merge[f][1]
1446 1447 merge = {}
1447 1448
1448 1449 if linear_path or force:
1449 1450 # we don't need to do any magic, just jump to the new rev
1450 1451 mode = 'n'
1451 1452 p1, p2 = p2, nullid
1452 1453 else:
1453 1454 if not allow:
1454 1455 self.ui.status("this update spans a branch" +
1455 1456 " affecting the following files:\n")
1456 1457 fl = merge.keys() + get.keys()
1457 1458 fl.sort()
1458 1459 for f in fl:
1459 1460 cf = ""
1460 1461 if f in merge: cf = " (resolve)"
1461 1462 self.ui.status(" %s%s\n" % (f, cf))
1462 1463 self.ui.warn("aborting update spanning branches!\n")
1463 1464 self.ui.status("(use update -m to perform a branch merge)\n")
1464 1465 return 1
1465 1466 # we have to remember what files we needed to get/change
1466 1467 # because any file that's different from either one of its
1467 1468 # parents must be in the changeset
1468 1469 mode = 'm'
1469 1470 if moddirstate:
1470 1471 self.dirstate.update(mark.keys(), "m")
1471 1472
1472 1473 if moddirstate:
1473 1474 self.dirstate.setparents(p1, p2)
1474 1475
1475 1476 # get the files we don't need to change
1476 1477 files = get.keys()
1477 1478 files.sort()
1478 1479 for f in files:
1479 1480 if f[0] == "/": continue
1480 1481 self.ui.note("getting %s\n" % f)
1481 1482 t = self.file(f).read(get[f])
1482 1483 try:
1483 1484 self.wfile(f, "w").write(t)
1484 1485 except IOError:
1485 1486 os.makedirs(os.path.dirname(self.wjoin(f)))
1486 1487 self.wfile(f, "w").write(t)
1487 1488 util.set_exec(self.wjoin(f), mf2[f])
1488 1489 if moddirstate:
1489 1490 self.dirstate.update([f], mode)
1490 1491
1491 1492 # merge the tricky bits
1492 1493 files = merge.keys()
1493 1494 files.sort()
1494 1495 for f in files:
1495 1496 self.ui.status("merging %s\n" % f)
1496 1497 m, o, flag = merge[f]
1497 1498 self.merge3(f, m, o)
1498 1499 util.set_exec(self.wjoin(f), flag)
1499 1500 if moddirstate:
1500 1501 self.dirstate.update([f], 'm')
1501 1502
1502 1503 remove.sort()
1503 1504 for f in remove:
1504 1505 self.ui.note("removing %s\n" % f)
1505 1506 try:
1506 1507 os.unlink(f)
1507 1508 except OSError, inst:
1508 1509 self.ui.warn("update failed to remove %s: %s!\n" % (f, inst))
1509 1510 # try removing directories that might now be empty
1510 1511 try: os.removedirs(os.path.dirname(f))
1511 1512 except: pass
1512 1513 if moddirstate:
1513 1514 if mode == 'n':
1514 1515 self.dirstate.forget(remove)
1515 1516 else:
1516 1517 self.dirstate.update(remove, 'r')
1517 1518
1518 1519 def merge3(self, fn, my, other):
1519 1520 """perform a 3-way merge in the working directory"""
1520 1521
1521 1522 def temp(prefix, node):
1522 1523 pre = "%s~%s." % (os.path.basename(fn), prefix)
1523 1524 (fd, name) = tempfile.mkstemp("", pre)
1524 1525 f = os.fdopen(fd, "wb")
1525 1526 f.write(fl.revision(node))
1526 1527 f.close()
1527 1528 return name
1528 1529
1529 1530 fl = self.file(fn)
1530 1531 base = fl.ancestor(my, other)
1531 1532 a = self.wjoin(fn)
1532 1533 b = temp("base", base)
1533 1534 c = temp("other", other)
1534 1535
1535 1536 self.ui.note("resolving %s\n" % fn)
1536 1537 self.ui.debug("file %s: other %s ancestor %s\n" %
1537 1538 (fn, short(other), short(base)))
1538 1539
1539 1540 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1540 1541 or "hgmerge")
1541 1542 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1542 1543 if r:
1543 1544 self.ui.warn("merging %s failed!\n" % fn)
1544 1545
1545 1546 os.unlink(b)
1546 1547 os.unlink(c)
1547 1548
1548 1549 def verify(self):
1549 1550 filelinkrevs = {}
1550 1551 filenodes = {}
1551 1552 changesets = revisions = files = 0
1552 1553 errors = 0
1553 1554
1554 1555 seen = {}
1555 1556 self.ui.status("checking changesets\n")
1556 1557 for i in range(self.changelog.count()):
1557 1558 changesets += 1
1558 1559 n = self.changelog.node(i)
1559 1560 if n in seen:
1560 1561 self.ui.warn("duplicate changeset at revision %d\n" % i)
1561 1562 errors += 1
1562 1563 seen[n] = 1
1563 1564
1564 1565 for p in self.changelog.parents(n):
1565 1566 if p not in self.changelog.nodemap:
1566 1567 self.ui.warn("changeset %s has unknown parent %s\n" %
1567 1568 (short(n), short(p)))
1568 1569 errors += 1
1569 1570 try:
1570 1571 changes = self.changelog.read(n)
1571 1572 except Exception, inst:
1572 1573 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1573 1574 errors += 1
1574 1575
1575 1576 for f in changes[3]:
1576 1577 filelinkrevs.setdefault(f, []).append(i)
1577 1578
1578 1579 seen = {}
1579 1580 self.ui.status("checking manifests\n")
1580 1581 for i in range(self.manifest.count()):
1581 1582 n = self.manifest.node(i)
1582 1583 if n in seen:
1583 1584 self.ui.warn("duplicate manifest at revision %d\n" % i)
1584 1585 errors += 1
1585 1586 seen[n] = 1
1586 1587
1587 1588 for p in self.manifest.parents(n):
1588 1589 if p not in self.manifest.nodemap:
1589 1590 self.ui.warn("manifest %s has unknown parent %s\n" %
1590 1591 (short(n), short(p)))
1591 1592 errors += 1
1592 1593
1593 1594 try:
1594 1595 delta = mdiff.patchtext(self.manifest.delta(n))
1595 1596 except KeyboardInterrupt:
1596 1597 self.ui.warn("aborted")
1597 1598 sys.exit(0)
1598 1599 except Exception, inst:
1599 1600 self.ui.warn("unpacking manifest %s: %s\n"
1600 1601 % (short(n), inst))
1601 1602 errors += 1
1602 1603
1603 1604 ff = [ l.split('\0') for l in delta.splitlines() ]
1604 1605 for f, fn in ff:
1605 1606 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1606 1607
1607 1608 self.ui.status("crosschecking files in changesets and manifests\n")
1608 1609 for f in filenodes:
1609 1610 if f not in filelinkrevs:
1610 1611 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1611 1612 errors += 1
1612 1613
1613 1614 for f in filelinkrevs:
1614 1615 if f not in filenodes:
1615 1616 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1616 1617 errors += 1
1617 1618
1618 1619 self.ui.status("checking files\n")
1619 1620 ff = filenodes.keys()
1620 1621 ff.sort()
1621 1622 for f in ff:
1622 1623 if f == "/dev/null": continue
1623 1624 files += 1
1624 1625 fl = self.file(f)
1625 1626 nodes = { nullid: 1 }
1626 1627 seen = {}
1627 1628 for i in range(fl.count()):
1628 1629 revisions += 1
1629 1630 n = fl.node(i)
1630 1631
1631 1632 if n in seen:
1632 1633 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1633 1634 errors += 1
1634 1635
1635 1636 if n not in filenodes[f]:
1636 1637 self.ui.warn("%s: %d:%s not in manifests\n"
1637 1638 % (f, i, short(n)))
1638 1639 errors += 1
1639 1640 else:
1640 1641 del filenodes[f][n]
1641 1642
1642 1643 flr = fl.linkrev(n)
1643 1644 if flr not in filelinkrevs[f]:
1644 1645 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1645 1646 % (f, short(n), fl.linkrev(n)))
1646 1647 errors += 1
1647 1648 else:
1648 1649 filelinkrevs[f].remove(flr)
1649 1650
1650 1651 # verify contents
1651 1652 try:
1652 1653 t = fl.read(n)
1653 1654 except Exception, inst:
1654 1655 self.ui.warn("unpacking file %s %s: %s\n"
1655 1656 % (f, short(n), inst))
1656 1657 errors += 1
1657 1658
1658 1659 # verify parents
1659 1660 (p1, p2) = fl.parents(n)
1660 1661 if p1 not in nodes:
1661 1662 self.ui.warn("file %s:%s unknown parent 1 %s" %
1662 1663 (f, short(n), short(p1)))
1663 1664 errors += 1
1664 1665 if p2 not in nodes:
1665 1666 self.ui.warn("file %s:%s unknown parent 2 %s" %
1666 1667 (f, short(n), short(p1)))
1667 1668 errors += 1
1668 1669 nodes[n] = 1
1669 1670
1670 1671 # cross-check
1671 1672 for node in filenodes[f]:
1672 1673 self.ui.warn("node %s in manifests not in %s\n"
1673 1674 % (hex(n), f))
1674 1675 errors += 1
1675 1676
1676 1677 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1677 1678 (files, changesets, revisions))
1678 1679
1679 1680 if errors:
1680 1681 self.ui.warn("%d integrity errors encountered!\n" % errors)
1681 1682 return 1
1682 1683
1683 1684 class httprepository:
1684 1685 def __init__(self, ui, path):
1685 1686 self.url = path
1686 1687 self.ui = ui
1687 1688 no_list = [ "localhost", "127.0.0.1" ]
1688 1689 host = ui.config("http_proxy", "host")
1689 1690 if host is None:
1690 1691 host = os.environ.get("http_proxy")
1691 1692 if host and host.startswith('http://'):
1692 1693 host = host[7:]
1693 1694 user = ui.config("http_proxy", "user")
1694 1695 passwd = ui.config("http_proxy", "passwd")
1695 1696 no = ui.config("http_proxy", "no")
1696 1697 if no is None:
1697 1698 no = os.environ.get("no_proxy")
1698 1699 if no:
1699 1700 no_list = no_list + no.split(",")
1700 1701
1701 1702 no_proxy = 0
1702 1703 for h in no_list:
1703 1704 if (path.startswith("http://" + h + "/") or
1704 1705 path.startswith("http://" + h + ":") or
1705 1706 path == "http://" + h):
1706 1707 no_proxy = 1
1707 1708
1708 1709 # Note: urllib2 takes proxy values from the environment and those will
1709 1710 # take precedence
1710 1711 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1711 1712 if os.environ.has_key(env):
1712 1713 del os.environ[env]
1713 1714
1714 1715 proxy_handler = urllib2.BaseHandler()
1715 1716 if host and not no_proxy:
1716 1717 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1717 1718
1718 1719 authinfo = None
1719 1720 if user and passwd:
1720 1721 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1721 1722 passmgr.add_password(None, host, user, passwd)
1722 1723 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1723 1724
1724 1725 opener = urllib2.build_opener(proxy_handler, authinfo)
1725 1726 urllib2.install_opener(opener)
1726 1727
1727 1728 def dev(self):
1728 1729 return -1
1729 1730
1730 1731 def do_cmd(self, cmd, **args):
1731 1732 self.ui.debug("sending %s command\n" % cmd)
1732 1733 q = {"cmd": cmd}
1733 1734 q.update(args)
1734 1735 qs = urllib.urlencode(q)
1735 1736 cu = "%s?%s" % (self.url, qs)
1736 1737 return urllib2.urlopen(cu)
1737 1738
1738 1739 def heads(self):
1739 1740 d = self.do_cmd("heads").read()
1740 1741 try:
1741 1742 return map(bin, d[:-1].split(" "))
1742 1743 except:
1743 1744 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1744 1745 raise
1745 1746
1746 1747 def branches(self, nodes):
1747 1748 n = " ".join(map(hex, nodes))
1748 1749 d = self.do_cmd("branches", nodes=n).read()
1749 1750 try:
1750 1751 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1751 1752 return br
1752 1753 except:
1753 1754 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1754 1755 raise
1755 1756
1756 1757 def between(self, pairs):
1757 1758 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1758 1759 d = self.do_cmd("between", pairs=n).read()
1759 1760 try:
1760 1761 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1761 1762 return p
1762 1763 except:
1763 1764 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1764 1765 raise
1765 1766
1766 1767 def changegroup(self, nodes):
1767 1768 n = " ".join(map(hex, nodes))
1768 1769 f = self.do_cmd("changegroup", roots=n)
1769 1770 bytes = 0
1770 1771
1771 1772 class zread:
1772 1773 def __init__(self, f):
1773 1774 self.zd = zlib.decompressobj()
1774 1775 self.f = f
1775 1776 self.buf = ""
1776 1777 def read(self, l):
1777 1778 while l > len(self.buf):
1778 1779 r = f.read(4096)
1779 1780 if r:
1780 1781 self.buf += self.zd.decompress(r)
1781 1782 else:
1782 1783 self.buf += self.zd.flush()
1783 1784 break
1784 1785 d, self.buf = self.buf[:l], self.buf[l:]
1785 1786 return d
1786 1787
1787 1788 return zread(f)
1788 1789
1789 1790 class remotelock:
1790 1791 def __init__(self, repo):
1791 1792 self.repo = repo
1792 1793 def release(self):
1793 1794 self.repo.unlock()
1794 1795 self.repo = None
1795 1796 def __del__(self):
1796 1797 if self.repo:
1797 1798 self.release()
1798 1799
1799 1800 class sshrepository:
1800 1801 def __init__(self, ui, path):
1801 1802 self.url = path
1802 1803 self.ui = ui
1803 1804
1804 1805 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1805 1806 if not m:
1806 1807 raise RepoError("couldn't parse destination %s\n" % path)
1807 1808
1808 1809 self.user = m.group(2)
1809 1810 self.host = m.group(3)
1810 1811 self.port = m.group(5)
1811 1812 self.path = m.group(7)
1812 1813
1813 1814 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1814 1815 args = self.port and ("%s -p %s") % (args, self.port) or args
1815 1816 path = self.path or ""
1816 1817
1817 1818 cmd = "ssh %s 'hg -R %s serve --stdio'"
1818 1819 cmd = cmd % (args, path)
1819 1820
1820 1821 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
1821 1822
1822 1823 def readerr(self):
1823 1824 while 1:
1824 1825 r,w,x = select.select([self.pipee], [], [], 0)
1825 1826 if not r: break
1826 1827 l = self.pipee.readline()
1827 1828 if not l: break
1828 1829 self.ui.status("remote: ", l)
1829 1830
1830 1831 def __del__(self):
1831 1832 self.pipeo.close()
1832 1833 self.pipei.close()
1833 1834 for l in self.pipee:
1834 1835 self.ui.status("remote: ", l)
1835 1836 self.pipee.close()
1836 1837
1837 1838 def dev(self):
1838 1839 return -1
1839 1840
1840 1841 def do_cmd(self, cmd, **args):
1841 1842 self.ui.debug("sending %s command\n" % cmd)
1842 1843 self.pipeo.write("%s\n" % cmd)
1843 1844 for k, v in args.items():
1844 1845 self.pipeo.write("%s %d\n" % (k, len(v)))
1845 1846 self.pipeo.write(v)
1846 1847 self.pipeo.flush()
1847 1848
1848 1849 return self.pipei
1849 1850
1850 1851 def call(self, cmd, **args):
1851 1852 r = self.do_cmd(cmd, **args)
1852 1853 l = r.readline()
1853 1854 self.readerr()
1854 1855 try:
1855 1856 l = int(l)
1856 1857 except:
1857 1858 raise RepoError("unexpected response '%s'" % l)
1858 1859 return r.read(l)
1859 1860
1860 1861 def lock(self):
1861 1862 self.call("lock")
1862 1863 return remotelock(self)
1863 1864
1864 1865 def unlock(self):
1865 1866 self.call("unlock")
1866 1867
1867 1868 def heads(self):
1868 1869 d = self.call("heads")
1869 1870 try:
1870 1871 return map(bin, d[:-1].split(" "))
1871 1872 except:
1872 1873 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1873 1874
1874 1875 def branches(self, nodes):
1875 1876 n = " ".join(map(hex, nodes))
1876 1877 d = self.call("branches", nodes=n)
1877 1878 try:
1878 1879 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1879 1880 return br
1880 1881 except:
1881 1882 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1882 1883
1883 1884 def between(self, pairs):
1884 1885 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1885 1886 d = self.call("between", pairs=n)
1886 1887 try:
1887 1888 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1888 1889 return p
1889 1890 except:
1890 1891 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1891 1892
1892 1893 def changegroup(self, nodes):
1893 1894 n = " ".join(map(hex, nodes))
1894 1895 f = self.do_cmd("changegroup", roots=n)
1895 1896 return self.pipei
1896 1897
1897 1898 def addchangegroup(self, cg):
1898 1899 d = self.call("addchangegroup")
1899 1900 if d:
1900 1901 raise RepoError("push refused: %s", d)
1901 1902
1902 1903 while 1:
1903 1904 d = cg.read(4096)
1904 1905 if not d: break
1905 1906 self.pipeo.write(d)
1906 1907 self.readerr()
1907 1908
1908 1909 self.pipeo.flush()
1909 1910
1910 1911 self.readerr()
1911 1912 l = int(self.pipei.readline())
1912 1913 return self.pipei.read(l) != ""
1913 1914
1914 1915 def repository(ui, path=None, create=0):
1915 1916 if path:
1916 1917 if path.startswith("http://"):
1917 1918 return httprepository(ui, path)
1918 1919 if path.startswith("hg://"):
1919 1920 return httprepository(ui, path.replace("hg://", "http://"))
1920 1921 if path.startswith("old-http://"):
1921 1922 return localrepository(ui, path.replace("old-http://", "http://"))
1922 1923 if path.startswith("ssh://"):
1923 1924 return sshrepository(ui, path)
1924 1925
1925 1926 return localrepository(ui, path, create)
@@ -1,126 +1,176 b''
1 1 # util.py - utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.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 import os, errno
9 from demandload import *
10 demandload(globals(), "re")
9 11
10 12 def unique(g):
11 13 seen = {}
12 14 for f in g:
13 15 if f not in seen:
14 16 seen[f] = 1
15 17 yield f
16 18
17 19 class CommandError(Exception): pass
18 20
19 21 def explain_exit(code):
20 22 """return a 2-tuple (desc, code) describing a process's status"""
21 23 if os.WIFEXITED(code):
22 24 val = os.WEXITSTATUS(code)
23 25 return "exited with status %d" % val, val
24 26 elif os.WIFSIGNALED(code):
25 27 val = os.WTERMSIG(code)
26 28 return "killed by signal %d" % val, val
27 29 elif os.WIFSTOPPED(code):
28 30 val = os.WSTOPSIG(code)
29 31 return "stopped by signal %d" % val, val
30 32 raise ValueError("invalid exit code")
31 33
34 def always(fn): return True
35 def never(fn): return False
36
37 def globre(pat, head = '^', tail = '$'):
38 "convert a glob pattern into a regexp"
39 i, n = 0, len(pat)
40 res = ''
41 group = False
42 def peek(): return i < n and pat[i]
43 while i < n:
44 c = pat[i]
45 i = i+1
46 if c == '*':
47 if peek() == '*':
48 i += 1
49 res += '.*'
50 else:
51 res += '[^/]*'
52 elif c == '?':
53 res += '.'
54 elif c == '[':
55 j = i
56 if j < n and pat[j] in '!]':
57 j += 1
58 while j < n and pat[j] != ']':
59 j += 1
60 if j >= n:
61 res += '\\['
62 else:
63 stuff = pat[i:j].replace('\\','\\\\')
64 i = j + 1
65 if stuff[0] == '!':
66 stuff = '^' + stuff[1:]
67 elif stuff[0] == '^':
68 stuff = '\\' + stuff
69 res = '%s[%s]' % (res, stuff)
70 elif c == '{':
71 group = True
72 res += '(?:'
73 elif c == '}' and group:
74 res += ')'
75 group = False
76 elif c == ',' and group:
77 res += '|'
78 else:
79 res += re.escape(c)
80 return head + res + tail
81
32 82 def system(cmd, errprefix=None):
33 83 """execute a shell command that must succeed"""
34 84 rc = os.system(cmd)
35 85 if rc:
36 86 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
37 87 explain_exit(rc)[0])
38 88 if errprefix:
39 89 errmsg = "%s: %s" % (errprefix, errmsg)
40 90 raise CommandError(errmsg)
41 91
42 92 def rename(src, dst):
43 93 try:
44 94 os.rename(src, dst)
45 95 except:
46 96 os.unlink(dst)
47 97 os.rename(src, dst)
48 98
49 99 def copytree(src, dst, copyfile):
50 100 """Copy a directory tree, files are copied using 'copyfile'."""
51 101 names = os.listdir(src)
52 102 os.mkdir(dst)
53 103
54 104 for name in names:
55 105 srcname = os.path.join(src, name)
56 106 dstname = os.path.join(dst, name)
57 107 if os.path.isdir(srcname):
58 108 copytree(srcname, dstname, copyfile)
59 109 elif os.path.isfile(srcname):
60 110 copyfile(srcname, dstname)
61 111 else:
62 112 raise IOError("Not a regular file: %r" % srcname)
63 113
64 114 def _makelock_file(info, pathname):
65 115 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
66 116 os.write(ld, info)
67 117 os.close(ld)
68 118
69 119 def _readlock_file(pathname):
70 120 return file(pathname).read()
71 121
72 122 # Platfor specific varients
73 123 if os.name == 'nt':
74 124 nulldev = 'NUL:'
75 125
76 126 def is_exec(f, last):
77 127 return last
78 128
79 129 def set_exec(f, mode):
80 130 pass
81 131
82 132 def pconvert(path):
83 133 return path.replace("\\", "/")
84 134
85 135 makelock = _makelock_file
86 136 readlock = _readlock_file
87 137
88 138 else:
89 139 nulldev = '/dev/null'
90 140
91 141 def is_exec(f, last):
92 142 return (os.stat(f).st_mode & 0100 != 0)
93 143
94 144 def set_exec(f, mode):
95 145 s = os.stat(f).st_mode
96 146 if (s & 0100 != 0) == mode:
97 147 return
98 148 if mode:
99 149 # Turn on +x for every +r bit when making a file executable
100 150 # and obey umask.
101 151 umask = os.umask(0)
102 152 os.umask(umask)
103 153 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
104 154 else:
105 155 os.chmod(f, s & 0666)
106 156
107 157 def pconvert(path):
108 158 return path
109 159
110 160 def makelock(info, pathname):
111 161 try:
112 162 os.symlink(info, pathname)
113 163 except OSError, why:
114 164 if why.errno == errno.EEXIST:
115 165 raise
116 166 else:
117 167 _makelock_file(info, pathname)
118 168
119 169 def readlock(pathname):
120 170 try:
121 171 return os.readlink(pathname)
122 172 except OSError, why:
123 173 if why.errno == errno.EINVAL:
124 174 return _readlock_file(pathname)
125 175 else:
126 176 raise
General Comments 0
You need to be logged in to leave comments. Login now