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