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