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