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