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