##// END OF EJS Templates
[PATCH] Get "hg serve" to optionally log accesses and errors to files...
mpm@selenic.com -
r605:8e82fd76 default
parent child Browse files
Show More
@@ -1,534 +1,539 b''
1 HG(1)
1 HG(1)
2 =====
2 =====
3 Matt Mackall <mpm@selenic.com>
3 Matt Mackall <mpm@selenic.com>
4
4
5 NAME
5 NAME
6 ----
6 ----
7 hg - Mercurial source code management system
7 hg - Mercurial source code management system
8
8
9 SYNOPSIS
9 SYNOPSIS
10 --------
10 --------
11 'hg' [-v -d -q -y] <command> [command options] [files]
11 'hg' [-v -d -q -y] <command> [command options] [files]
12
12
13 DESCRIPTION
13 DESCRIPTION
14 -----------
14 -----------
15 The hg(1) command provides a command line interface to the Mercurial system.
15 The hg(1) command provides a command line interface to the Mercurial system.
16
16
17 OPTIONS
17 OPTIONS
18 -------
18 -------
19
19
20 --debug, -d::
20 --debug, -d::
21 enable debugging output
21 enable debugging output
22
22
23 --quiet, -q::
23 --quiet, -q::
24 suppress output
24 suppress output
25
25
26 --verbose, -v::
26 --verbose, -v::
27 enable additional output
27 enable additional output
28
28
29 --noninteractive, -y::
29 --noninteractive, -y::
30 do not prompt, assume 'yes' for any required answers
30 do not prompt, assume 'yes' for any required answers
31
31
32 COMMAND ELEMENTS
32 COMMAND ELEMENTS
33 ----------------
33 ----------------
34
34
35 files ...::
35 files ...::
36 indicates one or more filename or relative path filenames
36 indicates one or more filename or relative path filenames
37
37
38 path::
38 path::
39 indicates a path on the local machine
39 indicates a path on the local machine
40
40
41 revision::
41 revision::
42 indicates a changeset which can be specified as a changeset revision
42 indicates a changeset which can be specified as a changeset revision
43 number, a tag, or a unique substring of the changeset hash value
43 number, a tag, or a unique substring of the changeset hash value
44
44
45 repository path::
45 repository path::
46 either the pathname of a local repository or the URI of a remote
46 either the pathname of a local repository or the URI of a remote
47 repository. There are two available URI protocols, http:// which is
47 repository. There are two available URI protocols, http:// which is
48 fast and the old-http:// protocol which is much slower but does not
48 fast and the old-http:// protocol which is much slower but does not
49 require a special server on the web host.
49 require a special server on the web host.
50
50
51 COMMANDS
51 COMMANDS
52 --------
52 --------
53
53
54 add [files ...]::
54 add [files ...]::
55 Schedule files to be version controlled and added to the repository.
55 Schedule files to be version controlled and added to the repository.
56
56
57 The files will be added to the repository at the next commit.
57 The files will be added to the repository at the next commit.
58
58
59 addremove::
59 addremove::
60 Add all new files and remove all missing files from the repository.
60 Add all new files and remove all missing files from the repository.
61
61
62 New files are ignored if they match any of the patterns in .hgignore. As
62 New files are ignored if they match any of the patterns in .hgignore. As
63 with add, these changes take effect at the next commit.
63 with add, these changes take effect at the next commit.
64
64
65 annotate [-r <rev> -u -n -c] [files ...]::
65 annotate [-r <rev> -u -n -c] [files ...]::
66 List changes in files, showing the revision id responsible for each line
66 List changes in files, showing the revision id responsible for each line
67
67
68 This command is useful to discover who did a change or when a change took
68 This command is useful to discover who did a change or when a change took
69 place.
69 place.
70
70
71 options:
71 options:
72 -r, --revision <rev> annotate the specified revision
72 -r, --revision <rev> annotate the specified revision
73 -u, --user list the author
73 -u, --user list the author
74 -c, --changeset list the changeset
74 -c, --changeset list the changeset
75 -n, --number list the revision number (default)
75 -n, --number list the revision number (default)
76
76
77 cat <file> [revision]::
77 cat <file> [revision]::
78 Output to stdout the given revision for the specified file.
78 Output to stdout the given revision for the specified file.
79
79
80 If no revision is given then the tip is used.
80 If no revision is given then the tip is used.
81
81
82 clone [-U] <source> [dest]::
82 clone [-U] <source> [dest]::
83 Create a copy of an existing repository in a new directory.
83 Create a copy of an existing repository in a new directory.
84
84
85 If no destination directory name is specified, it defaults to the
85 If no destination directory name is specified, it defaults to the
86 basename of the source.
86 basename of the source.
87
87
88 The source is added to the new repository's .hg/hgrc file to be used in
88 The source is added to the new repository's .hg/hgrc file to be used in
89 future pulls.
89 future pulls.
90
90
91 For efficiency, hardlinks are used for cloning whenever the
91 For efficiency, hardlinks are used for cloning whenever the
92 source and destination are on the same filesystem.
92 source and destination are on the same filesystem.
93
93
94 options:
94 options:
95 -U, --noupdate do not update the new working directory
95 -U, --noupdate do not update the new working directory
96
96
97 commit [-A -t -l <file> -t <text> -u <user> -d <datecode>] [files...]::
97 commit [-A -t -l <file> -t <text> -u <user> -d <datecode>] [files...]::
98 Commit changes to the given files into the repository.
98 Commit changes to the given files into the repository.
99
99
100 If a list of files is omitted, all changes reported by "hg status"
100 If a list of files is omitted, all changes reported by "hg status"
101 will be commited.
101 will be commited.
102
102
103 The HGEDITOR or EDITOR environment variables are used to start an
103 The HGEDITOR or EDITOR environment variables are used to start an
104 editor to add a commit comment.
104 editor to add a commit comment.
105
105
106 Options:
106 Options:
107
107
108 -A, --addremove run addremove during commit
108 -A, --addremove run addremove during commit
109 -t, --text <text> use <text> as commit message
109 -t, --text <text> use <text> as commit message
110 -l, --logfile <file> show the commit message for the given file
110 -l, --logfile <file> show the commit message for the given file
111 -d, --date <datecode> record datecode as commit date
111 -d, --date <datecode> record datecode as commit date
112 -u, --user <user> record user as commiter
112 -u, --user <user> record user as commiter
113
113
114 aliases: ci
114 aliases: ci
115
115
116 copy <source> <dest>::
116 copy <source> <dest>::
117 Mark <dest> file as a copy or rename of a <source> one
117 Mark <dest> file as a copy or rename of a <source> one
118
118
119 This command takes effect for the next commit.
119 This command takes effect for the next commit.
120
120
121 diff [-r revision] [-r revision] [files ...]::
121 diff [-r revision] [-r revision] [files ...]::
122 Show differences between revisions for the specified files.
122 Show differences between revisions for the specified files.
123
123
124 Differences between files are shown using the unified diff format.
124 Differences between files are shown using the unified diff format.
125
125
126 When two revision arguments are given, then changes are shown
126 When two revision arguments are given, then changes are shown
127 between those revisions. If only one revision is specified then
127 between those revisions. If only one revision is specified then
128 that revision is compared to the working directory, and, when no
128 that revision is compared to the working directory, and, when no
129 revisions are specified, the working directory files are compared
129 revisions are specified, the working directory files are compared
130 to its parent.
130 to its parent.
131
131
132 export [-o filespec] [revision] ...::
132 export [-o filespec] [revision] ...::
133 Print the changeset header and diffs for one or more revisions.
133 Print the changeset header and diffs for one or more revisions.
134
134
135 The information shown in the changeset header is: author,
135 The information shown in the changeset header is: author,
136 changeset hash, parent and commit comment.
136 changeset hash, parent and commit comment.
137
137
138 Output may be to a file, in which case the name of the file is
138 Output may be to a file, in which case the name of the file is
139 given using a format string. The formatting rules are as follows:
139 given using a format string. The formatting rules are as follows:
140
140
141 %% literal "%" character
141 %% literal "%" character
142 %H changeset hash (40 bytes of hexadecimal)
142 %H changeset hash (40 bytes of hexadecimal)
143 %N number of patches being generated
143 %N number of patches being generated
144 %R changeset revision number
144 %R changeset revision number
145 %b basename of the exporting repository
145 %b basename of the exporting repository
146 %h short-form changeset hash (12 bytes of hexadecimal)
146 %h short-form changeset hash (12 bytes of hexadecimal)
147 %n zero-padded sequence number, starting at 1
147 %n zero-padded sequence number, starting at 1
148 %r zero-padded changeset revision number
148 %r zero-padded changeset revision number
149
149
150 Options:
150 Options:
151
151
152 -o, --output <filespec> print output to file with formatted named
152 -o, --output <filespec> print output to file with formatted named
153
153
154 forget [files]::
154 forget [files]::
155 Undo an 'hg add' scheduled for the next commit.
155 Undo an 'hg add' scheduled for the next commit.
156
156
157 heads::
157 heads::
158 Show all repository head changesets.
158 Show all repository head changesets.
159
159
160 Repository "heads" are changesets that don't have children
160 Repository "heads" are changesets that don't have children
161 changesets. They are where development generally takes place and
161 changesets. They are where development generally takes place and
162 are the usual targets for update and merge operations.
162 are the usual targets for update and merge operations.
163
163
164 identify::
164 identify::
165 Print a short summary of the current state of the repo.
165 Print a short summary of the current state of the repo.
166
166
167 This summary identifies the repository state using one or two parent
167 This summary identifies the repository state using one or two parent
168 hash identifiers, followed by a "+" if there are uncommitted changes
168 hash identifiers, followed by a "+" if there are uncommitted changes
169 in the working directory, followed by a list of tags for this revision.
169 in the working directory, followed by a list of tags for this revision.
170
170
171 aliases: id
171 aliases: id
172
172
173 import [-p <n> -b <base> -q] <patches>::
173 import [-p <n> -b <base> -q] <patches>::
174 Import a list of patches and commit them individually.
174 Import a list of patches and commit them individually.
175
175
176 options:
176 options:
177 -p, --strip <n> directory strip option for patch. This has the same
177 -p, --strip <n> directory strip option for patch. This has the same
178 meaning as the correnponding patch option
178 meaning as the correnponding patch option
179 -b <path> base directory to read patches from
179 -b <path> base directory to read patches from
180
180
181 aliases: patch
181 aliases: patch
182
182
183 init::
183 init::
184 Initialize a new repository in the current directory.
184 Initialize a new repository in the current directory.
185
185
186 log [-r revision ...] [file]::
186 log [-r revision ...] [file]::
187 Print the revision history of the specified file or the entire project.
187 Print the revision history of the specified file or the entire project.
188
188
189 By default this command outputs: changeset id and hash, tags,
189 By default this command outputs: changeset id and hash, tags,
190 parents, user, date and time, and a summary for each commit. The
190 parents, user, date and time, and a summary for each commit. The
191 -v switch adds some more detail, such as changed files, manifest
191 -v switch adds some more detail, such as changed files, manifest
192 hashes or message signatures.
192 hashes or message signatures.
193
193
194 When a revision argument is given, only this file or changelog revision
194 When a revision argument is given, only this file or changelog revision
195 is displayed. With two revision arguments all revisions in this range
195 is displayed. With two revision arguments all revisions in this range
196 are listed. Additional revision arguments may be given repeating the above
196 are listed. Additional revision arguments may be given repeating the above
197 cycle.
197 cycle.
198
198
199 aliases: history
199 aliases: history
200
200
201 manifest [revision]::
201 manifest [revision]::
202 Print a list of version controlled files for the given revision.
202 Print a list of version controlled files for the given revision.
203
203
204 The manifest is the list of files being version controlled. If no revision
204 The manifest is the list of files being version controlled. If no revision
205 is given then the tip is used.
205 is given then the tip is used.
206
206
207 parents::
207 parents::
208 Print the working directory's parent revisions.
208 Print the working directory's parent revisions.
209
209
210 pull <repository path>::
210 pull <repository path>::
211 Pull changes from a remote repository to a local one.
211 Pull changes from a remote repository to a local one.
212
212
213 This finds all changes from the repository at the specified path
213 This finds all changes from the repository at the specified path
214 or URL and adds them to the local repository. By default, this
214 or URL and adds them to the local repository. By default, this
215 does not update the copy of the project in the working directory.
215 does not update the copy of the project in the working directory.
216
216
217 options:
217 options:
218 -u, --update update the working directory to tip after pull
218 -u, --update update the working directory to tip after pull
219
219
220 push <destination>::
220 push <destination>::
221 Push changes from the local repository to the given destination.
221 Push changes from the local repository to the given destination.
222
222
223 This is the symmetrical operation for pull. It helps to move
223 This is the symmetrical operation for pull. It helps to move
224 changes from the current repository to a different one. If the
224 changes from the current repository to a different one. If the
225 destination is local this is identical to a pull in that directory
225 destination is local this is identical to a pull in that directory
226 from the current one.
226 from the current one.
227
227
228 The other currently available push method is SSH. This requires an
228 The other currently available push method is SSH. This requires an
229 accessible shell account on the destination machine and a copy of
229 accessible shell account on the destination machine and a copy of
230 hg in the remote path. Destinations are specified in the following
230 hg in the remote path. Destinations are specified in the following
231 form:
231 form:
232
232
233 ssh://[user@]host[:port]/path
233 ssh://[user@]host[:port]/path
234
234
235 rawcommit [-p -d -u -F -t -l]::
235 rawcommit [-p -d -u -F -t -l]::
236 Lowlevel commit, for use in helper scripts.
236 Lowlevel commit, for use in helper scripts.
237
237
238 This command is not intended to be used by normal users, as it is
238 This command is not intended to be used by normal users, as it is
239 primarily useful for importing from other SCMs.
239 primarily useful for importing from other SCMs.
240
240
241 recover::
241 recover::
242 Recover from an interrupted commit or pull.
242 Recover from an interrupted commit or pull.
243
243
244 This command tries to fix the repository status after an interrupted
244 This command tries to fix the repository status after an interrupted
245 operation. It should only be necessary when Mercurial suggests it.
245 operation. It should only be necessary when Mercurial suggests it.
246
246
247 remove [files ...]::
247 remove [files ...]::
248 Schedule the indicated files for removal from the repository.
248 Schedule the indicated files for removal from the repository.
249
249
250 This command shedules the files to be removed at the next commit.
250 This command shedules the files to be removed at the next commit.
251 This only removes files from the current branch, not from the
251 This only removes files from the current branch, not from the
252 entire project history.
252 entire project history.
253
253
254 aliases: rm
254 aliases: rm
255
255
256 revert [names ...]::
256 revert [names ...]::
257 Revert any uncommitted modifications made to the named files or
257 Revert any uncommitted modifications made to the named files or
258 directories. This restores the contents of the affected files to
258 directories. This restores the contents of the affected files to
259 an unmodified state.
259 an unmodified state.
260
260
261 If a file has been deleted, it is recreated. If the executable
261 If a file has been deleted, it is recreated. If the executable
262 mode of a file was changed, it is reset.
262 mode of a file was changed, it is reset.
263
263
264 If a directory is given, all files in that directory and its
264 If a directory is given, all files in that directory and its
265 subdirectories are reverted.
265 subdirectories are reverted.
266
266
267 If no arguments are given, all files in the current directory and
267 If no arguments are given, all files in the current directory and
268 its subdirectories are reverted.
268 its subdirectories are reverted.
269
269
270 options:
270 options:
271 -r, --rev <rev> revision to revert to
271 -r, --rev <rev> revision to revert to
272 -n, --nonrecursive do not recurse into subdirectories
272 -n, --nonrecursive do not recurse into subdirectories
273
273
274 root::
274 root::
275 Print the root directory of the current repository.
275 Print the root directory of the current repository.
276
276
277 serve [-a addr -n name -p port -t templatedir]::
277 serve [-a addr -l logfile -n name -p port -t templatedir]::
278 Start a local HTTP repository browser and pull server.
278 Start a local HTTP repository browser and pull server.
279
279
280 By default, the server logs accesses to stdout and errors to
281 stderr. Use the "-A" and "-E" options to log to files.
282
280 options:
283 options:
281 -a, --address <addr> address to use
284 -A, --accesslog <file> name of access log file to write to
282 -p, --port <n> port to use (default: 8000)
285 -E, --errorlog <file> name of error log file to write to
283 -n, --name <name> name to show in web pages (default: working dir)
286 -a, --address <addr> address to use
287 -p, --port <n> port to use (default: 8000)
288 -n, --name <name> name to show in web pages (default: working dir)
284 -t, --templatedir <path> web templates to use
289 -t, --templatedir <path> web templates to use
285
290
286 status::
291 status::
287 Show changed files in the working directory.
292 Show changed files in the working directory.
288
293
289 The codes used to show the status of files are:
294 The codes used to show the status of files are:
290
295
291 C = changed
296 C = changed
292 A = added
297 A = added
293 R = removed
298 R = removed
294 ? = not tracked
299 ? = not tracked
295
300
296 tag [-t <text> -d <datecode> -u <user>] <name> [revision]::
301 tag [-t <text> -d <datecode> -u <user>] <name> [revision]::
297 Name a particular revision using <name>.
302 Name a particular revision using <name>.
298
303
299 Tags are used to name particular revisions of the repository and are
304 Tags are used to name particular revisions of the repository and are
300 very useful to compare different revision, to go back to significant
305 very useful to compare different revision, to go back to significant
301 earlier versions or to mark branch points as releases, etc.
306 earlier versions or to mark branch points as releases, etc.
302
307
303 If no revision is given, the tip is used.
308 If no revision is given, the tip is used.
304
309
305 To facilitate version control, distribution, and merging of tags,
310 To facilitate version control, distribution, and merging of tags,
306 they are stored as a file named ".hgtags" which is managed
311 they are stored as a file named ".hgtags" which is managed
307 similarly to other project files and can be hand-edited if
312 similarly to other project files and can be hand-edited if
308 necessary.
313 necessary.
309
314
310 options:
315 options:
311 -t, --text <text> message for tag commit log entry
316 -t, --text <text> message for tag commit log entry
312 -d, --date <datecode> datecode for commit
317 -d, --date <datecode> datecode for commit
313 -u, --user <user> user for commit
318 -u, --user <user> user for commit
314
319
315 Note: Mercurial also has support for "local tags" that are not
320 Note: Mercurial also has support for "local tags" that are not
316 version-controlled or distributed which are stored in the .hg/hgrc
321 version-controlled or distributed which are stored in the .hg/hgrc
317 file.
322 file.
318
323
319 tags::
324 tags::
320 List the repository tags.
325 List the repository tags.
321
326
322 This lists both regular and local tags.
327 This lists both regular and local tags.
323
328
324 tip::
329 tip::
325 Show the tip revision.
330 Show the tip revision.
326
331
327 undo::
332 undo::
328 Undo the last commit or pull transaction.
333 Undo the last commit or pull transaction.
329
334
330 Roll back the last pull or commit transaction on the
335 Roll back the last pull or commit transaction on the
331 repository, restoring the project to its earlier state.
336 repository, restoring the project to its earlier state.
332
337
333 This command should be used with care. There is only one level of
338 This command should be used with care. There is only one level of
334 undo and there is no redo.
339 undo and there is no redo.
335
340
336 This command is not intended for use on public repositories. Once
341 This command is not intended for use on public repositories. Once
337 a change is visible for pull by other users, undoing it locally is
342 a change is visible for pull by other users, undoing it locally is
338 ineffective.
343 ineffective.
339
344
340 update [-m -C] [revision]::
345 update [-m -C] [revision]::
341 Update the working directory to the specified revision.
346 Update the working directory to the specified revision.
342
347
343 By default, update will refuse to run if doing so would require
348 By default, update will refuse to run if doing so would require
344 merging or discarding local changes.
349 merging or discarding local changes.
345
350
346 With the -m option, a merge will be performed.
351 With the -m option, a merge will be performed.
347
352
348 With the -C option, local changes will be lost.
353 With the -C option, local changes will be lost.
349
354
350 options:
355 options:
351 -m, --merge allow merging of branches
356 -m, --merge allow merging of branches
352 -C, --clean overwrite locally modified files
357 -C, --clean overwrite locally modified files
353
358
354 aliases: up checkout co
359 aliases: up checkout co
355
360
356 verify::
361 verify::
357 Verify the integrity of the current repository.
362 Verify the integrity of the current repository.
358
363
359 This will perform an extensive check of the repository's
364 This will perform an extensive check of the repository's
360 integrity, validating the hashes and checksums of each entry in
365 integrity, validating the hashes and checksums of each entry in
361 the changelog, manifest, and tracked files, as well as the
366 the changelog, manifest, and tracked files, as well as the
362 integrity of their crosslinks and indices.
367 integrity of their crosslinks and indices.
363
368
364 SPECIFYING SINGLE REVISIONS
369 SPECIFYING SINGLE REVISIONS
365 ---------------------------
370 ---------------------------
366
371
367 Mercurial accepts several notations for identifying individual
372 Mercurial accepts several notations for identifying individual
368 revisions.
373 revisions.
369
374
370 A plain integer is treated as a revision number. Negative
375 A plain integer is treated as a revision number. Negative
371 integers are treated as offsets from the tip, with -1 denoting the
376 integers are treated as offsets from the tip, with -1 denoting the
372 tip.
377 tip.
373
378
374 A 40-digit hexadecimal string is treated as a unique revision
379 A 40-digit hexadecimal string is treated as a unique revision
375 identifier.
380 identifier.
376
381
377 A hexadecimal string less than 40 characters long is treated as a
382 A hexadecimal string less than 40 characters long is treated as a
378 unique revision identifier, and referred to as a short-form
383 unique revision identifier, and referred to as a short-form
379 identifier. A short-form identifier is only valid if it is the
384 identifier. A short-form identifier is only valid if it is the
380 prefix of one full-length identifier.
385 prefix of one full-length identifier.
381
386
382 Any other string is treated as a tag name, which is a symbolic
387 Any other string is treated as a tag name, which is a symbolic
383 name associated with a revision identifier. Tag names may not
388 name associated with a revision identifier. Tag names may not
384 contain the ":" character.
389 contain the ":" character.
385
390
386 The reserved name "tip" is a special tag that always identifies
391 The reserved name "tip" is a special tag that always identifies
387 the most recent revision.
392 the most recent revision.
388
393
389 SPECIFYING MULTIPLE REVISIONS
394 SPECIFYING MULTIPLE REVISIONS
390 -----------------------------
395 -----------------------------
391
396
392 When Mercurial accepts more than one revision, they may be
397 When Mercurial accepts more than one revision, they may be
393 specified individually, or provided as a continuous range,
398 specified individually, or provided as a continuous range,
394 separated by the ":" character.
399 separated by the ":" character.
395
400
396 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
401 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
397 are revision identifiers. Both BEGIN and END are optional. If
402 are revision identifiers. Both BEGIN and END are optional. If
398 BEGIN is not specified, it defaults to revision number 0. If END
403 BEGIN is not specified, it defaults to revision number 0. If END
399 is not specified, it defaults to the tip. The range ":" thus
404 is not specified, it defaults to the tip. The range ":" thus
400 means "all revisions".
405 means "all revisions".
401
406
402 If BEGIN is greater than END, revisions are treated in reverse
407 If BEGIN is greater than END, revisions are treated in reverse
403 order.
408 order.
404
409
405 A range acts as an open interval. This means that a range of 3:5
410 A range acts as an open interval. This means that a range of 3:5
406 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
411 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
407
412
408 ENVIRONMENT VARIABLES
413 ENVIRONMENT VARIABLES
409 ---------------------
414 ---------------------
410
415
411 HGEDITOR::
416 HGEDITOR::
412 This is the name of the editor to use when committing. Defaults to the
417 This is the name of the editor to use when committing. Defaults to the
413 value of EDITOR.
418 value of EDITOR.
414
419
415 HGMERGE::
420 HGMERGE::
416 An executable to use for resolving merge conflicts. The program
421 An executable to use for resolving merge conflicts. The program
417 will be executed with three arguments: local file, remote file,
422 will be executed with three arguments: local file, remote file,
418 ancestor file.
423 ancestor file.
419
424
420 The default program is "hgmerge", which is a shell script provided
425 The default program is "hgmerge", which is a shell script provided
421 by Mercurial with some sensible defaults.
426 by Mercurial with some sensible defaults.
422
427
423 HGUSER::
428 HGUSER::
424 This is the string used for the author of a commit.
429 This is the string used for the author of a commit.
425
430
426 EMAIL::
431 EMAIL::
427 If HGUSER is not set, this will be used as the author for a commit.
432 If HGUSER is not set, this will be used as the author for a commit.
428
433
429 LOGNAME::
434 LOGNAME::
430 If neither HGUSER nor EMAIL is set, LOGNAME will be used (with
435 If neither HGUSER nor EMAIL is set, LOGNAME will be used (with
431 '@hostname' appended) as the author value for a commit.
436 '@hostname' appended) as the author value for a commit.
432
437
433 EDITOR::
438 EDITOR::
434 This is the name of the editor used in the hgmerge script. It will be
439 This is the name of the editor used in the hgmerge script. It will be
435 used for commit messages if HGEDITOR isn't set. Defaults to 'vi'.
440 used for commit messages if HGEDITOR isn't set. Defaults to 'vi'.
436
441
437 PYTHONPATH::
442 PYTHONPATH::
438 This is used by Python to find imported modules and may need to be set
443 This is used by Python to find imported modules and may need to be set
439 appropriately if Mercurial is not installed system-wide.
444 appropriately if Mercurial is not installed system-wide.
440
445
441 FILES
446 FILES
442 -----
447 -----
443 .hgignore::
448 .hgignore::
444 This file contains regular expressions (one per line) that describe file
449 This file contains regular expressions (one per line) that describe file
445 names that should be ignored by hg.
450 names that should be ignored by hg.
446
451
447 .hgtags::
452 .hgtags::
448 This file contains changeset hash values and text tag names (one of each
453 This file contains changeset hash values and text tag names (one of each
449 seperated by spaces) that correspond to tagged versions of the repository
454 seperated by spaces) that correspond to tagged versions of the repository
450 contents.
455 contents.
451
456
452 $HOME/.hgrc, .hg/hgrc::
457 $HOME/.hgrc, .hg/hgrc::
453 This file contains defaults and configuration. Values in .hg/hgrc
458 This file contains defaults and configuration. Values in .hg/hgrc
454 override those in .hgrc.
459 override those in .hgrc.
455
460
456 NAMED REPOSITORIES
461 NAMED REPOSITORIES
457 ------------------
462 ------------------
458
463
459 To give symbolic names to a repository, create a section in .hgrc
464 To give symbolic names to a repository, create a section in .hgrc
460 or .hg/hgrc containing assignments of names to paths. Example:
465 or .hg/hgrc containing assignments of names to paths. Example:
461
466
462 -----------------
467 -----------------
463 [paths]
468 [paths]
464 hg = http://selenic.com/hg
469 hg = http://selenic.com/hg
465 tah = http://hg.intevation.org/mercurial-tah/
470 tah = http://hg.intevation.org/mercurial-tah/
466 -----------------
471 -----------------
467
472
468
473
469 LOCAL TAGS
474 LOCAL TAGS
470 ----------
475 ----------
471
476
472 To create tags that are local to the repository and not distributed or
477 To create tags that are local to the repository and not distributed or
473 version-controlled, create an hgrc section like the following:
478 version-controlled, create an hgrc section like the following:
474
479
475 ----------------
480 ----------------
476 [tags]
481 [tags]
477 working = 2dcced388cab3677a8f543c3c47a0ad34ac9d435
482 working = 2dcced388cab3677a8f543c3c47a0ad34ac9d435
478 tested = 12e0fdbc57a0be78f0e817fd1d170a3615cd35da
483 tested = 12e0fdbc57a0be78f0e817fd1d170a3615cd35da
479 ----------------
484 ----------------
480
485
481
486
482 HOOKS
487 HOOKS
483 -----
488 -----
484
489
485 Mercurial supports a set of 'hook', commands that get automatically
490 Mercurial supports a set of 'hook', commands that get automatically
486 executed by various actions such as starting or finishing a commit. To
491 executed by various actions such as starting or finishing a commit. To
487 specify a hook, simply create an hgrc section like the following:
492 specify a hook, simply create an hgrc section like the following:
488
493
489 -----------------
494 -----------------
490 [hooks]
495 [hooks]
491 precommit = echo "this hook gets executed immediately before a commit"
496 precommit = echo "this hook gets executed immediately before a commit"
492 commit = hg export $NODE | mail -s "new commit $NODE" commit-list
497 commit = hg export $NODE | mail -s "new commit $NODE" commit-list
493 -----------------
498 -----------------
494
499
495
500
496 NON_TRANSPARENT PROXY SUPPORT
501 NON_TRANSPARENT PROXY SUPPORT
497 -----------------------------
502 -----------------------------
498
503
499 To access a Mercurial repository through a proxy, create a file
504 To access a Mercurial repository through a proxy, create a file
500 $HOME/.hgrc in the following format:
505 $HOME/.hgrc in the following format:
501
506
502 --------------
507 --------------
503 [http_proxy]
508 [http_proxy]
504 host=myproxy:8080
509 host=myproxy:8080
505 user=<username>
510 user=<username>
506 passwd=<password>
511 passwd=<password>
507 no=<localhost1>,<localhost2>,<localhost3>,...
512 no=<localhost1>,<localhost2>,<localhost3>,...
508 --------------
513 --------------
509
514
510 "user" and "passwd" fields are used for authenticating proxies, "no" is a
515 "user" and "passwd" fields are used for authenticating proxies, "no" is a
511 comma-separated list of local host names to not proxy.
516 comma-separated list of local host names to not proxy.
512
517
513 BUGS
518 BUGS
514 ----
519 ----
515 Probably lots, please post them to the mailing list (See Resources below)
520 Probably lots, please post them to the mailing list (See Resources below)
516 when you find them.
521 when you find them.
517
522
518 AUTHOR
523 AUTHOR
519 ------
524 ------
520 Written by Matt Mackall <mpm@selenic.com>
525 Written by Matt Mackall <mpm@selenic.com>
521
526
522 RESOURCES
527 RESOURCES
523 ---------
528 ---------
524 http://selenic.com/mercurial[Main Web Site]
529 http://selenic.com/mercurial[Main Web Site]
525
530
526 http://selenic.com/hg[Source code repository]
531 http://selenic.com/hg[Source code repository]
527
532
528 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
533 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
529
534
530 COPYING
535 COPYING
531 -------
536 -------
532 Copyright (C) 2005 Matt Mackall.
537 Copyright (C) 2005 Matt Mackall.
533 Free use of this software is granted under the terms of the GNU General
538 Free use of this software is granted under the terms of the GNU General
534 Public License (GPL).
539 Public License (GPL).
@@ -1,1143 +1,1150 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, re, sys, signal
8 import os, re, sys, signal
9 import fancyopts, ui, hg, util
9 import fancyopts, ui, hg, util
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "mdiff time hgweb traceback random signal")
11 demandload(globals(), "mdiff time hgweb traceback random signal")
12 demandload(globals(), "socket errno version")
12 demandload(globals(), "socket errno version")
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 self.dir = dir
334 self.dir = dir
335 os.mkdir(dir)
335 os.mkdir(dir)
336 def close(self):
336 def close(self):
337 self.dir = None
337 self.dir = None
338 def __del__(self):
338 def __del__(self):
339 if self.dir:
339 if self.dir:
340 import shutil
340 import shutil
341 shutil.rmtree(self.dir, True)
341 shutil.rmtree(self.dir, True)
342
342
343 d = dircleanup(dest)
343 d = dircleanup(dest)
344
344
345 link = 0
345 link = 0
346 abspath = source
346 abspath = source
347 if not (source.startswith("http://") or
347 if not (source.startswith("http://") or
348 source.startswith("hg://") or
348 source.startswith("hg://") or
349 source.startswith("old-http://")):
349 source.startswith("old-http://")):
350 abspath = os.path.abspath(source)
350 abspath = os.path.abspath(source)
351 d1 = os.stat(dest).st_dev
351 d1 = os.stat(dest).st_dev
352 d2 = os.stat(source).st_dev
352 d2 = os.stat(source).st_dev
353 if d1 == d2: link = 1
353 if d1 == d2: link = 1
354
354
355 if link:
355 if link:
356 ui.note("copying by hardlink\n")
356 ui.note("copying by hardlink\n")
357 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
357 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
358 try:
358 try:
359 os.remove(os.path.join(dest, ".hg", "dirstate"))
359 os.remove(os.path.join(dest, ".hg", "dirstate"))
360 except: pass
360 except: pass
361
361
362 repo = hg.repository(ui, dest)
362 repo = hg.repository(ui, dest)
363
363
364 else:
364 else:
365 repo = hg.repository(ui, dest, create=1)
365 repo = hg.repository(ui, dest, create=1)
366 other = hg.repository(ui, source)
366 other = hg.repository(ui, source)
367 fetch = repo.findincoming(other)
367 fetch = repo.findincoming(other)
368 if fetch:
368 if fetch:
369 cg = other.changegroup(fetch)
369 cg = other.changegroup(fetch)
370 repo.addchangegroup(cg)
370 repo.addchangegroup(cg)
371
371
372 f = repo.opener("hgrc", "w")
372 f = repo.opener("hgrc", "w")
373 f.write("[paths]\n")
373 f.write("[paths]\n")
374 f.write("default = %s\n" % abspath)
374 f.write("default = %s\n" % abspath)
375
375
376 if not opts['noupdate']:
376 if not opts['noupdate']:
377 update(ui, repo)
377 update(ui, repo)
378
378
379 d.close()
379 d.close()
380
380
381 def commit(ui, repo, *files, **opts):
381 def commit(ui, repo, *files, **opts):
382 """commit the specified files or all outstanding changes"""
382 """commit the specified files or all outstanding changes"""
383 text = opts['text']
383 text = opts['text']
384 if not text and opts['logfile']:
384 if not text and opts['logfile']:
385 try: text = open(opts['logfile']).read()
385 try: text = open(opts['logfile']).read()
386 except IOError: pass
386 except IOError: pass
387
387
388 if opts['addremove']:
388 if opts['addremove']:
389 addremove(ui, repo, *files)
389 addremove(ui, repo, *files)
390 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
390 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
391
391
392 def copy(ui, repo, source, dest):
392 def copy(ui, repo, source, dest):
393 """mark a file as copied or renamed for the next commit"""
393 """mark a file as copied or renamed for the next commit"""
394 return repo.copy(*relpath(repo, (source, dest)))
394 return repo.copy(*relpath(repo, (source, dest)))
395
395
396 def debugcheckstate(ui, repo):
396 def debugcheckstate(ui, repo):
397 """validate the correctness of the current dirstate"""
397 """validate the correctness of the current dirstate"""
398 parent1, parent2 = repo.dirstate.parents()
398 parent1, parent2 = repo.dirstate.parents()
399 repo.dirstate.read()
399 repo.dirstate.read()
400 dc = repo.dirstate.map
400 dc = repo.dirstate.map
401 keys = dc.keys()
401 keys = dc.keys()
402 keys.sort()
402 keys.sort()
403 m1n = repo.changelog.read(parent1)[0]
403 m1n = repo.changelog.read(parent1)[0]
404 m2n = repo.changelog.read(parent2)[0]
404 m2n = repo.changelog.read(parent2)[0]
405 m1 = repo.manifest.read(m1n)
405 m1 = repo.manifest.read(m1n)
406 m2 = repo.manifest.read(m2n)
406 m2 = repo.manifest.read(m2n)
407 errors = 0
407 errors = 0
408 for f in dc:
408 for f in dc:
409 state = repo.dirstate.state(f)
409 state = repo.dirstate.state(f)
410 if state in "nr" and f not in m1:
410 if state in "nr" and f not in m1:
411 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
411 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
412 errors += 1
412 errors += 1
413 if state in "a" and f in m1:
413 if state in "a" and f in m1:
414 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
414 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
415 errors += 1
415 errors += 1
416 if state in "m" and f not in m1 and f not in m2:
416 if state in "m" and f not in m1 and f not in m2:
417 ui.warn("%s in state %s, but not in either manifest\n" %
417 ui.warn("%s in state %s, but not in either manifest\n" %
418 (f, state))
418 (f, state))
419 errors += 1
419 errors += 1
420 for f in m1:
420 for f in m1:
421 state = repo.dirstate.state(f)
421 state = repo.dirstate.state(f)
422 if state not in "nrm":
422 if state not in "nrm":
423 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
423 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
424 errors += 1
424 errors += 1
425 if errors:
425 if errors:
426 ui.warn(".hg/dirstate inconsistent with current parent's manifest\n")
426 ui.warn(".hg/dirstate inconsistent with current parent's manifest\n")
427 sys.exit(1)
427 sys.exit(1)
428
428
429 def debugstate(ui, repo):
429 def debugstate(ui, repo):
430 """show the contents of the current dirstate"""
430 """show the contents of the current dirstate"""
431 repo.dirstate.read()
431 repo.dirstate.read()
432 dc = repo.dirstate.map
432 dc = repo.dirstate.map
433 keys = dc.keys()
433 keys = dc.keys()
434 keys.sort()
434 keys.sort()
435 for file in keys:
435 for file in keys:
436 ui.write("%c %s\n" % (dc[file][0], file))
436 ui.write("%c %s\n" % (dc[file][0], file))
437
437
438 def debugindex(ui, file):
438 def debugindex(ui, file):
439 """dump the contents of an index file"""
439 """dump the contents of an index file"""
440 r = hg.revlog(hg.opener(""), file, "")
440 r = hg.revlog(hg.opener(""), file, "")
441 ui.write(" rev offset length base linkrev" +
441 ui.write(" rev offset length base linkrev" +
442 " p1 p2 nodeid\n")
442 " p1 p2 nodeid\n")
443 for i in range(r.count()):
443 for i in range(r.count()):
444 e = r.index[i]
444 e = r.index[i]
445 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
445 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
446 i, e[0], e[1], e[2], e[3],
446 i, e[0], e[1], e[2], e[3],
447 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
447 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
448
448
449 def debugindexdot(ui, file):
449 def debugindexdot(ui, file):
450 """dump an index DAG as a .dot file"""
450 """dump an index DAG as a .dot file"""
451 r = hg.revlog(hg.opener(""), file, "")
451 r = hg.revlog(hg.opener(""), file, "")
452 ui.write("digraph G {\n")
452 ui.write("digraph G {\n")
453 for i in range(r.count()):
453 for i in range(r.count()):
454 e = r.index[i]
454 e = r.index[i]
455 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
455 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
456 if e[5] != hg.nullid:
456 if e[5] != hg.nullid:
457 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
457 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
458 ui.write("}\n")
458 ui.write("}\n")
459
459
460 def diff(ui, repo, *files, **opts):
460 def diff(ui, repo, *files, **opts):
461 """diff working directory (or selected files)"""
461 """diff working directory (or selected files)"""
462 revs = []
462 revs = []
463 if opts['rev']:
463 if opts['rev']:
464 revs = map(lambda x: repo.lookup(x), opts['rev'])
464 revs = map(lambda x: repo.lookup(x), opts['rev'])
465
465
466 if len(revs) > 2:
466 if len(revs) > 2:
467 ui.warn("too many revisions to diff\n")
467 ui.warn("too many revisions to diff\n")
468 sys.exit(1)
468 sys.exit(1)
469
469
470 if files:
470 if files:
471 files = relpath(repo, files)
471 files = relpath(repo, files)
472 else:
472 else:
473 files = relpath(repo, [""])
473 files = relpath(repo, [""])
474
474
475 dodiff(sys.stdout, ui, repo, files, *revs)
475 dodiff(sys.stdout, ui, repo, files, *revs)
476
476
477 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
477 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
478 node = repo.lookup(changeset)
478 node = repo.lookup(changeset)
479 prev, other = repo.changelog.parents(node)
479 prev, other = repo.changelog.parents(node)
480 change = repo.changelog.read(node)
480 change = repo.changelog.read(node)
481
481
482 def expand(name):
482 def expand(name):
483 expansions = {
483 expansions = {
484 '%': lambda: '%',
484 '%': lambda: '%',
485 'H': lambda: hg.hex(node),
485 'H': lambda: hg.hex(node),
486 'N': lambda: str(total),
486 'N': lambda: str(total),
487 'R': lambda: str(repo.changelog.rev(node)),
487 'R': lambda: str(repo.changelog.rev(node)),
488 'b': lambda: os.path.basename(repo.root),
488 'b': lambda: os.path.basename(repo.root),
489 'h': lambda: hg.short(node),
489 'h': lambda: hg.short(node),
490 'n': lambda: str(seqno).zfill(len(str(total))),
490 'n': lambda: str(seqno).zfill(len(str(total))),
491 'r': lambda: str(repo.changelog.rev(node)).zfill(revwidth),
491 'r': lambda: str(repo.changelog.rev(node)).zfill(revwidth),
492 }
492 }
493 newname = []
493 newname = []
494 namelen = len(name)
494 namelen = len(name)
495 i = 0
495 i = 0
496 while i < namelen:
496 while i < namelen:
497 c = name[i]
497 c = name[i]
498 if c == '%':
498 if c == '%':
499 i += 1
499 i += 1
500 c = name[i]
500 c = name[i]
501 c = expansions[c]()
501 c = expansions[c]()
502 newname.append(c)
502 newname.append(c)
503 i += 1
503 i += 1
504 return ''.join(newname)
504 return ''.join(newname)
505
505
506 if opts['output'] and opts['output'] != '-':
506 if opts['output'] and opts['output'] != '-':
507 try:
507 try:
508 fp = open(expand(opts['output']), 'w')
508 fp = open(expand(opts['output']), 'w')
509 except KeyError, inst:
509 except KeyError, inst:
510 ui.warn("error: invalid format spec '%%%s' in output file name\n" %
510 ui.warn("error: invalid format spec '%%%s' in output file name\n" %
511 inst.args[0])
511 inst.args[0])
512 sys.exit(1)
512 sys.exit(1)
513 else:
513 else:
514 fp = sys.stdout
514 fp = sys.stdout
515
515
516 fp.write("# HG changeset patch\n")
516 fp.write("# HG changeset patch\n")
517 fp.write("# User %s\n" % change[1])
517 fp.write("# User %s\n" % change[1])
518 fp.write("# Node ID %s\n" % hg.hex(node))
518 fp.write("# Node ID %s\n" % hg.hex(node))
519 fp.write("# Parent %s\n" % hg.hex(prev))
519 fp.write("# Parent %s\n" % hg.hex(prev))
520 if other != hg.nullid:
520 if other != hg.nullid:
521 fp.write("# Parent %s\n" % hg.hex(other))
521 fp.write("# Parent %s\n" % hg.hex(other))
522 fp.write(change[4].rstrip())
522 fp.write(change[4].rstrip())
523 fp.write("\n\n")
523 fp.write("\n\n")
524
524
525 dodiff(fp, ui, repo, None, prev, node)
525 dodiff(fp, ui, repo, None, prev, node)
526
526
527 def export(ui, repo, *changesets, **opts):
527 def export(ui, repo, *changesets, **opts):
528 """dump the header and diffs for one or more changesets"""
528 """dump the header and diffs for one or more changesets"""
529 seqno = 0
529 seqno = 0
530 revs = list(revrange(ui, repo, changesets))
530 revs = list(revrange(ui, repo, changesets))
531 total = len(revs)
531 total = len(revs)
532 revwidth = max(len(revs[0]), len(revs[-1]))
532 revwidth = max(len(revs[0]), len(revs[-1]))
533 for cset in revs:
533 for cset in revs:
534 seqno += 1
534 seqno += 1
535 doexport(ui, repo, cset, seqno, total, revwidth, opts)
535 doexport(ui, repo, cset, seqno, total, revwidth, opts)
536
536
537 def forget(ui, repo, file, *files):
537 def forget(ui, repo, file, *files):
538 """don't add the specified files on the next commit"""
538 """don't add the specified files on the next commit"""
539 repo.forget(relpath(repo, (file,) + files))
539 repo.forget(relpath(repo, (file,) + files))
540
540
541 def heads(ui, repo):
541 def heads(ui, repo):
542 """show current repository heads"""
542 """show current repository heads"""
543 for n in repo.changelog.heads():
543 for n in repo.changelog.heads():
544 show_changeset(ui, repo, changenode=n)
544 show_changeset(ui, repo, changenode=n)
545
545
546 def identify(ui, repo):
546 def identify(ui, repo):
547 """print information about the working copy"""
547 """print information about the working copy"""
548 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
548 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
549 if not parents:
549 if not parents:
550 ui.write("unknown\n")
550 ui.write("unknown\n")
551 return
551 return
552
552
553 hexfunc = ui.verbose and hg.hex or hg.short
553 hexfunc = ui.verbose and hg.hex or hg.short
554 (c, a, d, u) = repo.changes(None, None)
554 (c, a, d, u) = repo.changes(None, None)
555 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
555 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
556 (c or a or d) and "+" or "")]
556 (c or a or d) and "+" or "")]
557
557
558 if not ui.quiet:
558 if not ui.quiet:
559 # multiple tags for a single parent separated by '/'
559 # multiple tags for a single parent separated by '/'
560 parenttags = ['/'.join(tags)
560 parenttags = ['/'.join(tags)
561 for tags in map(repo.nodetags, parents) if tags]
561 for tags in map(repo.nodetags, parents) if tags]
562 # tags for multiple parents separated by ' + '
562 # tags for multiple parents separated by ' + '
563 output.append(' + '.join(parenttags))
563 output.append(' + '.join(parenttags))
564
564
565 ui.write("%s\n" % ' '.join(output))
565 ui.write("%s\n" % ' '.join(output))
566
566
567 def import_(ui, repo, patch1, *patches, **opts):
567 def import_(ui, repo, patch1, *patches, **opts):
568 """import an ordered set of patches"""
568 """import an ordered set of patches"""
569 try:
569 try:
570 import psyco
570 import psyco
571 psyco.full()
571 psyco.full()
572 except:
572 except:
573 pass
573 pass
574
574
575 patches = (patch1,) + patches
575 patches = (patch1,) + patches
576
576
577 d = opts["base"]
577 d = opts["base"]
578 strip = opts["strip"]
578 strip = opts["strip"]
579
579
580 for patch in patches:
580 for patch in patches:
581 ui.status("applying %s\n" % patch)
581 ui.status("applying %s\n" % patch)
582 pf = os.path.join(d, patch)
582 pf = os.path.join(d, patch)
583
583
584 text = ""
584 text = ""
585 for l in file(pf):
585 for l in file(pf):
586 if l[:4] == "--- ": break
586 if l[:4] == "--- ": break
587 text += l
587 text += l
588
588
589 # make sure text isn't empty
589 # make sure text isn't empty
590 if not text: text = "imported patch %s\n" % patch
590 if not text: text = "imported patch %s\n" % patch
591
591
592 f = os.popen("patch -p%d < %s" % (strip, pf))
592 f = os.popen("patch -p%d < %s" % (strip, pf))
593 files = []
593 files = []
594 for l in f.read().splitlines():
594 for l in f.read().splitlines():
595 l.rstrip('\r\n');
595 l.rstrip('\r\n');
596 ui.status("%s\n" % l)
596 ui.status("%s\n" % l)
597 if l[:14] == 'patching file ':
597 if l[:14] == 'patching file ':
598 pf = l[14:]
598 pf = l[14:]
599 if pf not in files:
599 if pf not in files:
600 files.append(pf)
600 files.append(pf)
601 patcherr = f.close()
601 patcherr = f.close()
602 if patcherr:
602 if patcherr:
603 sys.stderr.write("patch failed")
603 sys.stderr.write("patch failed")
604 sys.exit(1)
604 sys.exit(1)
605
605
606 if len(files) > 0:
606 if len(files) > 0:
607 addremove(ui, repo, *files)
607 addremove(ui, repo, *files)
608 repo.commit(files, text)
608 repo.commit(files, text)
609
609
610 def init(ui, source=None):
610 def init(ui, source=None):
611 """create a new repository in the current directory"""
611 """create a new repository in the current directory"""
612
612
613 if source:
613 if source:
614 ui.warn("no longer supported: use \"hg clone\" instead\n")
614 ui.warn("no longer supported: use \"hg clone\" instead\n")
615 sys.exit(1)
615 sys.exit(1)
616 repo = hg.repository(ui, ".", create=1)
616 repo = hg.repository(ui, ".", create=1)
617
617
618 def log(ui, repo, f=None, **opts):
618 def log(ui, repo, f=None, **opts):
619 """show the revision history of the repository or a single file"""
619 """show the revision history of the repository or a single file"""
620 if f:
620 if f:
621 filelog = repo.file(relpath(repo, [f])[0])
621 filelog = repo.file(relpath(repo, [f])[0])
622 log = filelog
622 log = filelog
623 lookup = filelog.lookup
623 lookup = filelog.lookup
624 else:
624 else:
625 filelog = None
625 filelog = None
626 log = repo.changelog
626 log = repo.changelog
627 lookup = repo.lookup
627 lookup = repo.lookup
628 revlist = []
628 revlist = []
629 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
629 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
630 while revs:
630 while revs:
631 if len(revs) == 1:
631 if len(revs) == 1:
632 revlist.append(revs.pop(0))
632 revlist.append(revs.pop(0))
633 else:
633 else:
634 a = revs.pop(0)
634 a = revs.pop(0)
635 b = revs.pop(0)
635 b = revs.pop(0)
636 off = a > b and -1 or 1
636 off = a > b and -1 or 1
637 revlist.extend(range(a, b + off, off))
637 revlist.extend(range(a, b + off, off))
638 for i in revlist or range(log.count() - 1, -1, -1):
638 for i in revlist or range(log.count() - 1, -1, -1):
639 show_changeset(ui, repo, filelog=filelog, rev=i)
639 show_changeset(ui, repo, filelog=filelog, rev=i)
640
640
641 def manifest(ui, repo, rev = []):
641 def manifest(ui, repo, rev = []):
642 """output the latest or given revision of the project manifest"""
642 """output the latest or given revision of the project manifest"""
643 n = repo.manifest.tip()
643 n = repo.manifest.tip()
644 if rev:
644 if rev:
645 n = repo.manifest.lookup(rev)
645 n = repo.manifest.lookup(rev)
646 m = repo.manifest.read(n)
646 m = repo.manifest.read(n)
647 mf = repo.manifest.readflags(n)
647 mf = repo.manifest.readflags(n)
648 files = m.keys()
648 files = m.keys()
649 files.sort()
649 files.sort()
650
650
651 for f in files:
651 for f in files:
652 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
652 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
653
653
654 def parents(ui, repo, node = None):
654 def parents(ui, repo, node = None):
655 '''show the parents of the current working dir'''
655 '''show the parents of the current working dir'''
656 if node:
656 if node:
657 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
657 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
658 else:
658 else:
659 p = repo.dirstate.parents()
659 p = repo.dirstate.parents()
660
660
661 for n in p:
661 for n in p:
662 if n != hg.nullid:
662 if n != hg.nullid:
663 show_changeset(ui, repo, changenode=n)
663 show_changeset(ui, repo, changenode=n)
664
664
665 def pull(ui, repo, source="default", **opts):
665 def pull(ui, repo, source="default", **opts):
666 """pull changes from the specified source"""
666 """pull changes from the specified source"""
667 source = ui.expandpath(source)
667 source = ui.expandpath(source)
668
668
669 ui.status('pulling from %s\n' % (source))
669 ui.status('pulling from %s\n' % (source))
670
670
671 other = hg.repository(ui, source)
671 other = hg.repository(ui, source)
672 fetch = repo.findincoming(other)
672 fetch = repo.findincoming(other)
673 if not fetch:
673 if not fetch:
674 ui.status("no changes found\n")
674 ui.status("no changes found\n")
675 return
675 return
676
676
677 cg = other.changegroup(fetch)
677 cg = other.changegroup(fetch)
678 r = repo.addchangegroup(cg)
678 r = repo.addchangegroup(cg)
679 if cg and not r:
679 if cg and not r:
680 if opts['update']:
680 if opts['update']:
681 return update(ui, repo)
681 return update(ui, repo)
682 else:
682 else:
683 ui.status("(run 'hg update' to get a working copy)\n")
683 ui.status("(run 'hg update' to get a working copy)\n")
684
684
685 return r
685 return r
686
686
687 def push(ui, repo, dest="default-push"):
687 def push(ui, repo, dest="default-push"):
688 """push changes to the specified destination"""
688 """push changes to the specified destination"""
689 dest = ui.expandpath(dest)
689 dest = ui.expandpath(dest)
690
690
691 if not dest.startswith("ssh://"):
691 if not dest.startswith("ssh://"):
692 ui.warn("abort: can only push to ssh:// destinations currently\n")
692 ui.warn("abort: can only push to ssh:// destinations currently\n")
693 return 1
693 return 1
694
694
695 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
695 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
696 if not m:
696 if not m:
697 ui.warn("abort: couldn't parse destination %s\n" % dest)
697 ui.warn("abort: couldn't parse destination %s\n" % dest)
698 return 1
698 return 1
699
699
700 user, host, port, path = map(m.group, (2, 3, 5, 7))
700 user, host, port, path = map(m.group, (2, 3, 5, 7))
701 uhost = user and ("%s@%s" % (user, host)) or host
701 uhost = user and ("%s@%s" % (user, host)) or host
702 port = port and (" -p %s") % port or ""
702 port = port and (" -p %s") % port or ""
703 path = path or ""
703 path = path or ""
704
704
705 sport = random.randrange(30000, 60000)
705 sport = random.randrange(30000, 60000)
706 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
706 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
707 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
707 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
708
708
709 child = os.fork()
709 child = os.fork()
710 if not child:
710 if not child:
711 sys.stdout = file("/dev/null", "w")
711 sys.stdout = file("/dev/null", "w")
712 sys.stderr = sys.stdout
712 sys.stderr = sys.stdout
713 hgweb.server(repo.root, "pull", "", "localhost", sport)
713 hgweb.server(repo.root, "pull", "", "localhost", sport)
714 else:
714 else:
715 ui.status("connecting to %s\n" % host)
715 ui.status("connecting to %s\n" % host)
716 r = os.system(cmd)
716 r = os.system(cmd)
717 os.kill(child, signal.SIGTERM)
717 os.kill(child, signal.SIGTERM)
718 return r
718 return r
719
719
720 def rawcommit(ui, repo, *flist, **rc):
720 def rawcommit(ui, repo, *flist, **rc):
721 "raw commit interface"
721 "raw commit interface"
722
722
723 text = rc['text']
723 text = rc['text']
724 if not text and rc['logfile']:
724 if not text and rc['logfile']:
725 try: text = open(rc['logfile']).read()
725 try: text = open(rc['logfile']).read()
726 except IOError: pass
726 except IOError: pass
727 if not text and not rc['logfile']:
727 if not text and not rc['logfile']:
728 ui.warn("abort: missing commit text\n")
728 ui.warn("abort: missing commit text\n")
729 return 1
729 return 1
730
730
731 files = relpath(repo, list(flist))
731 files = relpath(repo, list(flist))
732 if rc['files']:
732 if rc['files']:
733 files += open(rc['files']).read().splitlines()
733 files += open(rc['files']).read().splitlines()
734
734
735 rc['parent'] = map(repo.lookup, rc['parent'])
735 rc['parent'] = map(repo.lookup, rc['parent'])
736
736
737 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
737 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
738
738
739 def recover(ui, repo):
739 def recover(ui, repo):
740 """roll back an interrupted transaction"""
740 """roll back an interrupted transaction"""
741 repo.recover()
741 repo.recover()
742
742
743 def remove(ui, repo, file, *files):
743 def remove(ui, repo, file, *files):
744 """remove the specified files on the next commit"""
744 """remove the specified files on the next commit"""
745 repo.remove(relpath(repo, (file,) + files))
745 repo.remove(relpath(repo, (file,) + files))
746
746
747 def revert(ui, repo, *names, **opts):
747 def revert(ui, repo, *names, **opts):
748 """revert modified files or dirs back to their unmodified states"""
748 """revert modified files or dirs back to their unmodified states"""
749 node = opts['rev'] and repo.lookup(opts['rev']) or \
749 node = opts['rev'] and repo.lookup(opts['rev']) or \
750 repo.dirstate.parents()[0]
750 repo.dirstate.parents()[0]
751 root = os.path.realpath(repo.root)
751 root = os.path.realpath(repo.root)
752
752
753 def trimpath(p):
753 def trimpath(p):
754 p = os.path.realpath(p)
754 p = os.path.realpath(p)
755 if p.startswith(root):
755 if p.startswith(root):
756 rest = p[len(root):]
756 rest = p[len(root):]
757 if not rest:
757 if not rest:
758 return rest
758 return rest
759 if p.startswith(os.sep):
759 if p.startswith(os.sep):
760 return rest[1:]
760 return rest[1:]
761 return p
761 return p
762
762
763 relnames = map(trimpath, names or [os.getcwd()])
763 relnames = map(trimpath, names or [os.getcwd()])
764 chosen = {}
764 chosen = {}
765
765
766 def choose(name):
766 def choose(name):
767 def body(name):
767 def body(name):
768 for r in relnames:
768 for r in relnames:
769 if not name.startswith(r): continue
769 if not name.startswith(r): continue
770 rest = name[len(r):]
770 rest = name[len(r):]
771 if not rest: return r, True
771 if not rest: return r, True
772 depth = rest.count(os.sep)
772 depth = rest.count(os.sep)
773 if not r:
773 if not r:
774 if depth == 0 or not opts['nonrecursive']: return r, True
774 if depth == 0 or not opts['nonrecursive']: return r, True
775 elif rest[0] == os.sep:
775 elif rest[0] == os.sep:
776 if depth == 1 or not opts['nonrecursive']: return r, True
776 if depth == 1 or not opts['nonrecursive']: return r, True
777 return None, False
777 return None, False
778 relname, ret = body(name)
778 relname, ret = body(name)
779 if ret:
779 if ret:
780 chosen[relname] = 1
780 chosen[relname] = 1
781 return ret
781 return ret
782
782
783 r = repo.update(node, False, True, choose, False)
783 r = repo.update(node, False, True, choose, False)
784 for n in relnames:
784 for n in relnames:
785 if n not in chosen:
785 if n not in chosen:
786 ui.warn('error: no matches for %s\n' % n)
786 ui.warn('error: no matches for %s\n' % n)
787 r = 1
787 r = 1
788 sys.stdout.flush()
788 sys.stdout.flush()
789 return r
789 return r
790
790
791 def root(ui, repo):
791 def root(ui, repo):
792 """print the root (top) of the current working dir"""
792 """print the root (top) of the current working dir"""
793 ui.write(repo.root + "\n")
793 ui.write(repo.root + "\n")
794
794
795 def serve(ui, repo, **opts):
795 def serve(ui, repo, **opts):
796 """export the repository via HTTP"""
796 """export the repository via HTTP"""
797 def openlog(opt, default):
798 if opts[opt] and opts[opt] != '-': return open(opts[opt], 'w')
799 else: return default
797 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
800 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
798 opts["address"], opts["port"])
801 opts["address"], opts["port"],
802 openlog('accesslog', sys.stdout),
803 openlog('errorlog', sys.stderr))
799 if ui.verbose:
804 if ui.verbose:
800 addr, port = httpd.socket.getsockname()
805 addr, port = httpd.socket.getsockname()
801 if addr == '0.0.0.0':
806 if addr == '0.0.0.0':
802 addr = socket.gethostname()
807 addr = socket.gethostname()
803 else:
808 else:
804 try:
809 try:
805 addr = socket.gethostbyaddr(addr)[0]
810 addr = socket.gethostbyaddr(addr)[0]
806 except: pass
811 except: pass
807 if port != 80:
812 if port != 80:
808 ui.status('listening on http://%s:%d/\n' % (addr, port))
813 ui.status('listening at http://%s:%d/\n' % (addr, port))
809 else:
814 else:
810 ui.status('listening on http://%s/\n' % addr)
815 ui.status('listening at http://%s/\n' % addr)
811 httpd.serve_forever()
816 httpd.serve_forever()
812
817
813 def status(ui, repo):
818 def status(ui, repo):
814 '''show changed files in the working directory
819 '''show changed files in the working directory
815
820
816 C = changed
821 C = changed
817 A = added
822 A = added
818 R = removed
823 R = removed
819 ? = not tracked'''
824 ? = not tracked'''
820
825
821 (c, a, d, u) = repo.changes(None, None)
826 (c, a, d, u) = repo.changes(None, None)
822 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
827 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
823
828
824 for f in c: ui.write("C ", f, "\n")
829 for f in c: ui.write("C ", f, "\n")
825 for f in a: ui.write("A ", f, "\n")
830 for f in a: ui.write("A ", f, "\n")
826 for f in d: ui.write("R ", f, "\n")
831 for f in d: ui.write("R ", f, "\n")
827 for f in u: ui.write("? ", f, "\n")
832 for f in u: ui.write("? ", f, "\n")
828
833
829 def tag(ui, repo, name, rev = None, **opts):
834 def tag(ui, repo, name, rev = None, **opts):
830 """add a tag for the current tip or a given revision"""
835 """add a tag for the current tip or a given revision"""
831
836
832 if name == "tip":
837 if name == "tip":
833 ui.warn("abort: 'tip' is a reserved name!\n")
838 ui.warn("abort: 'tip' is a reserved name!\n")
834 return -1
839 return -1
835 if name.find(revrangesep) >= 0:
840 if name.find(revrangesep) >= 0:
836 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
841 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
837 return -1
842 return -1
838
843
839 (c, a, d, u) = repo.changes(None, None)
844 (c, a, d, u) = repo.changes(None, None)
840 for x in (c, a, d, u):
845 for x in (c, a, d, u):
841 if ".hgtags" in x:
846 if ".hgtags" in x:
842 ui.warn("abort: working copy of .hgtags is changed!\n")
847 ui.warn("abort: working copy of .hgtags is changed!\n")
843 ui.status("(please commit .hgtags manually)\n")
848 ui.status("(please commit .hgtags manually)\n")
844 return -1
849 return -1
845
850
846 if rev:
851 if rev:
847 r = hg.hex(repo.lookup(rev))
852 r = hg.hex(repo.lookup(rev))
848 else:
853 else:
849 r = hg.hex(repo.changelog.tip())
854 r = hg.hex(repo.changelog.tip())
850
855
851 add = 0
856 add = 0
852 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
857 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
853 repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
858 repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
854 if add: repo.add([".hgtags"])
859 if add: repo.add([".hgtags"])
855
860
856 if not opts['text']:
861 if not opts['text']:
857 opts['text'] = "Added tag %s for changeset %s" % (name, r)
862 opts['text'] = "Added tag %s for changeset %s" % (name, r)
858
863
859 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
864 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
860
865
861 def tags(ui, repo):
866 def tags(ui, repo):
862 """list repository tags"""
867 """list repository tags"""
863
868
864 l = repo.tagslist()
869 l = repo.tagslist()
865 l.reverse()
870 l.reverse()
866 for t, n in l:
871 for t, n in l:
867 try:
872 try:
868 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
873 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
869 except KeyError:
874 except KeyError:
870 r = " ?:?"
875 r = " ?:?"
871 ui.write("%-30s %s\n" % (t, r))
876 ui.write("%-30s %s\n" % (t, r))
872
877
873 def tip(ui, repo):
878 def tip(ui, repo):
874 """show the tip revision"""
879 """show the tip revision"""
875 n = repo.changelog.tip()
880 n = repo.changelog.tip()
876 show_changeset(ui, repo, changenode=n)
881 show_changeset(ui, repo, changenode=n)
877
882
878 def undo(ui, repo):
883 def undo(ui, repo):
879 """undo the last commit or pull
884 """undo the last commit or pull
880
885
881 Roll back the last pull or commit transaction on the
886 Roll back the last pull or commit transaction on the
882 repository, restoring the project to its earlier state.
887 repository, restoring the project to its earlier state.
883
888
884 This command should be used with care. There is only one level of
889 This command should be used with care. There is only one level of
885 undo and there is no redo.
890 undo and there is no redo.
886
891
887 This command is not intended for use on public repositories. Once
892 This command is not intended for use on public repositories. Once
888 a change is visible for pull by other users, undoing it locally is
893 a change is visible for pull by other users, undoing it locally is
889 ineffective.
894 ineffective.
890 """
895 """
891 repo.undo()
896 repo.undo()
892
897
893 def update(ui, repo, node=None, merge=False, clean=False):
898 def update(ui, repo, node=None, merge=False, clean=False):
894 '''update or merge working directory
899 '''update or merge working directory
895
900
896 If there are no outstanding changes in the working directory and
901 If there are no outstanding changes in the working directory and
897 there is a linear relationship between the current version and the
902 there is a linear relationship between the current version and the
898 requested version, the result is the requested version.
903 requested version, the result is the requested version.
899
904
900 Otherwise the result is a merge between the contents of the
905 Otherwise the result is a merge between the contents of the
901 current working directory and the requested version. Files that
906 current working directory and the requested version. Files that
902 changed between either parent are marked as changed for the next
907 changed between either parent are marked as changed for the next
903 commit and a commit must be performed before any further updates
908 commit and a commit must be performed before any further updates
904 are allowed.
909 are allowed.
905 '''
910 '''
906 node = node and repo.lookup(node) or repo.changelog.tip()
911 node = node and repo.lookup(node) or repo.changelog.tip()
907 return repo.update(node, allow=merge, force=clean)
912 return repo.update(node, allow=merge, force=clean)
908
913
909 def verify(ui, repo):
914 def verify(ui, repo):
910 """verify the integrity of the repository"""
915 """verify the integrity of the repository"""
911 return repo.verify()
916 return repo.verify()
912
917
913 # Command options and aliases are listed here, alphabetically
918 # Command options and aliases are listed here, alphabetically
914
919
915 table = {
920 table = {
916 "^add": (add, [], "hg add [files]"),
921 "^add": (add, [], "hg add [files]"),
917 "addremove": (addremove, [], "hg addremove [files]"),
922 "addremove": (addremove, [], "hg addremove [files]"),
918 "^annotate": (annotate,
923 "^annotate": (annotate,
919 [('r', 'revision', '', 'revision'),
924 [('r', 'revision', '', 'revision'),
920 ('u', 'user', None, 'show user'),
925 ('u', 'user', None, 'show user'),
921 ('n', 'number', None, 'show revision number'),
926 ('n', 'number', None, 'show revision number'),
922 ('c', 'changeset', None, 'show changeset')],
927 ('c', 'changeset', None, 'show changeset')],
923 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
928 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
924 "cat": (cat, [], 'hg cat <file> [rev]'),
929 "cat": (cat, [], 'hg cat <file> [rev]'),
925 "^clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
930 "^clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
926 'hg clone [options] <source> [dest]'),
931 'hg clone [options] <source> [dest]'),
927 "^commit|ci": (commit,
932 "^commit|ci": (commit,
928 [('t', 'text', "", 'commit text'),
933 [('t', 'text', "", 'commit text'),
929 ('A', 'addremove', None, 'run add/remove during commit'),
934 ('A', 'addremove', None, 'run add/remove during commit'),
930 ('l', 'logfile', "", 'commit text file'),
935 ('l', 'logfile', "", 'commit text file'),
931 ('d', 'date', "", 'date code'),
936 ('d', 'date', "", 'date code'),
932 ('u', 'user', "", 'user')],
937 ('u', 'user', "", 'user')],
933 'hg commit [files]'),
938 'hg commit [files]'),
934 "copy": (copy, [], 'hg copy <source> <dest>'),
939 "copy": (copy, [], 'hg copy <source> <dest>'),
935 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
940 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
936 "debugstate": (debugstate, [], 'debugstate'),
941 "debugstate": (debugstate, [], 'debugstate'),
937 "debugindex": (debugindex, [], 'debugindex <file>'),
942 "debugindex": (debugindex, [], 'debugindex <file>'),
938 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
943 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
939 "^diff": (diff, [('r', 'rev', [], 'revision')],
944 "^diff": (diff, [('r', 'rev', [], 'revision')],
940 'hg diff [-r A] [-r B] [files]'),
945 'hg diff [-r A] [-r B] [files]'),
941 "^export": (export, [('o', 'output', "", 'output to file')],
946 "^export": (export, [('o', 'output', "", 'output to file')],
942 "hg export [-o file] <changeset> ..."),
947 "hg export [-o file] <changeset> ..."),
943 "forget": (forget, [], "hg forget [files]"),
948 "forget": (forget, [], "hg forget [files]"),
944 "heads": (heads, [], 'hg heads'),
949 "heads": (heads, [], 'hg heads'),
945 "help": (help, [], 'hg help [command]'),
950 "help": (help, [], 'hg help [command]'),
946 "identify|id": (identify, [], 'hg identify'),
951 "identify|id": (identify, [], 'hg identify'),
947 "import|patch": (import_,
952 "import|patch": (import_,
948 [('p', 'strip', 1, 'path strip'),
953 [('p', 'strip', 1, 'path strip'),
949 ('b', 'base', "", 'base path')],
954 ('b', 'base', "", 'base path')],
950 "hg import [options] <patches>"),
955 "hg import [options] <patches>"),
951 "^init": (init, [], 'hg init'),
956 "^init": (init, [], 'hg init'),
952 "^log|history": (log,
957 "^log|history": (log,
953 [('r', 'rev', [], 'revision')],
958 [('r', 'rev', [], 'revision')],
954 'hg log [-r A] [-r B] [file]'),
959 'hg log [-r A] [-r B] [file]'),
955 "manifest": (manifest, [], 'hg manifest [rev]'),
960 "manifest": (manifest, [], 'hg manifest [rev]'),
956 "parents": (parents, [], 'hg parents [node]'),
961 "parents": (parents, [], 'hg parents [node]'),
957 "^pull": (pull,
962 "^pull": (pull,
958 [('u', 'update', None, 'update working directory')],
963 [('u', 'update', None, 'update working directory')],
959 'hg pull [options] [source]'),
964 'hg pull [options] [source]'),
960 "^push": (push, [], 'hg push <destination>'),
965 "^push": (push, [], 'hg push <destination>'),
961 "rawcommit": (rawcommit,
966 "rawcommit": (rawcommit,
962 [('p', 'parent', [], 'parent'),
967 [('p', 'parent', [], 'parent'),
963 ('d', 'date', "", 'date code'),
968 ('d', 'date', "", 'date code'),
964 ('u', 'user', "", 'user'),
969 ('u', 'user', "", 'user'),
965 ('F', 'files', "", 'file list'),
970 ('F', 'files', "", 'file list'),
966 ('t', 'text', "", 'commit text'),
971 ('t', 'text', "", 'commit text'),
967 ('l', 'logfile', "", 'commit text file')],
972 ('l', 'logfile', "", 'commit text file')],
968 'hg rawcommit [options] [files]'),
973 'hg rawcommit [options] [files]'),
969 "recover": (recover, [], "hg recover"),
974 "recover": (recover, [], "hg recover"),
970 "^remove|rm": (remove, [], "hg remove [files]"),
975 "^remove|rm": (remove, [], "hg remove [files]"),
971 "^revert": (revert,
976 "^revert": (revert,
972 [("n", "nonrecursive", None, "don't recurse into subdirs"),
977 [("n", "nonrecursive", None, "don't recurse into subdirs"),
973 ("r", "rev", "", "revision")],
978 ("r", "rev", "", "revision")],
974 "hg revert [files|dirs]"),
979 "hg revert [files|dirs]"),
975 "root": (root, [], "hg root"),
980 "root": (root, [], "hg root"),
976 "^serve": (serve, [('p', 'port', 8000, 'listen port'),
981 "^serve": (serve, [('A', 'accesslog', '', 'access log file'),
977 ('a', 'address', '', 'interface address'),
982 ('E', 'errorlog', '', 'error log file'),
978 ('n', 'name', os.getcwd(), 'repository name'),
983 ('p', 'port', 8000, 'listen port'),
979 ('t', 'templates', "", 'template map')],
984 ('a', 'address', '', 'interface address'),
985 ('n', 'name', os.getcwd(), 'repository name'),
986 ('t', 'templates', "", 'template map')],
980 "hg serve [options]"),
987 "hg serve [options]"),
981 "^status": (status, [], 'hg status'),
988 "^status": (status, [], 'hg status'),
982 "tag": (tag, [('t', 'text', "", 'commit text'),
989 "tag": (tag, [('t', 'text', "", 'commit text'),
983 ('d', 'date', "", 'date code'),
990 ('d', 'date', "", 'date code'),
984 ('u', 'user', "", 'user')],
991 ('u', 'user', "", 'user')],
985 'hg tag [options] <name> [rev]'),
992 'hg tag [options] <name> [rev]'),
986 "tags": (tags, [], 'hg tags'),
993 "tags": (tags, [], 'hg tags'),
987 "tip": (tip, [], 'hg tip'),
994 "tip": (tip, [], 'hg tip'),
988 "undo": (undo, [], 'hg undo'),
995 "undo": (undo, [], 'hg undo'),
989 "^update|up|checkout|co":
996 "^update|up|checkout|co":
990 (update,
997 (update,
991 [('m', 'merge', None, 'allow merging of conflicts'),
998 [('m', 'merge', None, 'allow merging of conflicts'),
992 ('C', 'clean', None, 'overwrite locally modified files')],
999 ('C', 'clean', None, 'overwrite locally modified files')],
993 'hg update [options] [node]'),
1000 'hg update [options] [node]'),
994 "verify": (verify, [], 'hg verify'),
1001 "verify": (verify, [], 'hg verify'),
995 "version": (show_version, [], 'hg version'),
1002 "version": (show_version, [], 'hg version'),
996 }
1003 }
997
1004
998 globalopts = [('v', 'verbose', None, 'verbose'),
1005 globalopts = [('v', 'verbose', None, 'verbose'),
999 ('', 'debug', None, 'debug'),
1006 ('', 'debug', None, 'debug'),
1000 ('q', 'quiet', None, 'quiet'),
1007 ('q', 'quiet', None, 'quiet'),
1001 ('', 'profile', None, 'profile'),
1008 ('', 'profile', None, 'profile'),
1002 ('R', 'repository', "", 'repository root directory'),
1009 ('R', 'repository', "", 'repository root directory'),
1003 ('', 'traceback', None, 'print traceback on exception'),
1010 ('', 'traceback', None, 'print traceback on exception'),
1004 ('y', 'noninteractive', None, 'run non-interactively'),
1011 ('y', 'noninteractive', None, 'run non-interactively'),
1005 ('', 'version', None, 'output version information and exit'),
1012 ('', 'version', None, 'output version information and exit'),
1006 ]
1013 ]
1007
1014
1008 norepo = "clone init version help debugindex debugindexdot"
1015 norepo = "clone init version help debugindex debugindexdot"
1009
1016
1010 def find(cmd):
1017 def find(cmd):
1011 for e in table.keys():
1018 for e in table.keys():
1012 if re.match("(%s)$" % e, cmd):
1019 if re.match("(%s)$" % e, cmd):
1013 return table[e]
1020 return table[e]
1014
1021
1015 raise UnknownCommand(cmd)
1022 raise UnknownCommand(cmd)
1016
1023
1017 class SignalInterrupt(Exception): pass
1024 class SignalInterrupt(Exception): pass
1018
1025
1019 def catchterm(*args):
1026 def catchterm(*args):
1020 raise SignalInterrupt
1027 raise SignalInterrupt
1021
1028
1022 def run():
1029 def run():
1023 sys.exit(dispatch(sys.argv[1:]))
1030 sys.exit(dispatch(sys.argv[1:]))
1024
1031
1025 class ParseError(Exception): pass
1032 class ParseError(Exception): pass
1026
1033
1027 def parse(args):
1034 def parse(args):
1028 options = {}
1035 options = {}
1029 cmdoptions = {}
1036 cmdoptions = {}
1030
1037
1031 try:
1038 try:
1032 args = fancyopts.fancyopts(args, globalopts, options)
1039 args = fancyopts.fancyopts(args, globalopts, options)
1033 except fancyopts.getopt.GetoptError, inst:
1040 except fancyopts.getopt.GetoptError, inst:
1034 raise ParseError(cmd, inst)
1041 raise ParseError(cmd, inst)
1035
1042
1036 if options["version"]:
1043 if options["version"]:
1037 return ("version", show_version, [], options, cmdoptions)
1044 return ("version", show_version, [], options, cmdoptions)
1038 elif not args:
1045 elif not args:
1039 return ("help", help, [], options, cmdoptions)
1046 return ("help", help, [], options, cmdoptions)
1040 else:
1047 else:
1041 cmd, args = args[0], args[1:]
1048 cmd, args = args[0], args[1:]
1042
1049
1043 i = find(cmd)
1050 i = find(cmd)
1044
1051
1045 # combine global options into local
1052 # combine global options into local
1046 c = list(i[1])
1053 c = list(i[1])
1047 l = len(c)
1054 l = len(c)
1048 for o in globalopts:
1055 for o in globalopts:
1049 c.append((o[0], o[1], options[o[1]], o[3]))
1056 c.append((o[0], o[1], options[o[1]], o[3]))
1050
1057
1051 try:
1058 try:
1052 args = fancyopts.fancyopts(args, c, cmdoptions)
1059 args = fancyopts.fancyopts(args, c, cmdoptions)
1053 except fancyopts.getopt.GetoptError, inst:
1060 except fancyopts.getopt.GetoptError, inst:
1054 raise ParseError(cmd, inst)
1061 raise ParseError(cmd, inst)
1055
1062
1056 # separate global options back out
1063 # separate global options back out
1057 for o in globalopts:
1064 for o in globalopts:
1058 n = o[1]
1065 n = o[1]
1059 options[n] = cmdoptions[n]
1066 options[n] = cmdoptions[n]
1060 del cmdoptions[n]
1067 del cmdoptions[n]
1061
1068
1062 return (cmd, i[0], args, options, cmdoptions)
1069 return (cmd, i[0], args, options, cmdoptions)
1063
1070
1064 def dispatch(args):
1071 def dispatch(args):
1065 signal.signal(signal.SIGTERM, catchterm)
1072 signal.signal(signal.SIGTERM, catchterm)
1066
1073
1067 try:
1074 try:
1068 cmd, func, args, options, cmdoptions = parse(args)
1075 cmd, func, args, options, cmdoptions = parse(args)
1069 except ParseError, inst:
1076 except ParseError, inst:
1070 u = ui.ui()
1077 u = ui.ui()
1071 if inst.args[0]:
1078 if inst.args[0]:
1072 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1079 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1073 help(u, inst.args[0])
1080 help(u, inst.args[0])
1074 else:
1081 else:
1075 u.warn("hg: %s\n" % inst.args[1])
1082 u.warn("hg: %s\n" % inst.args[1])
1076 help(u)
1083 help(u)
1077 sys.exit(-1)
1084 sys.exit(-1)
1078 except UnknownCommand, inst:
1085 except UnknownCommand, inst:
1079 u = ui.ui()
1086 u = ui.ui()
1080 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1087 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1081 help(u)
1088 help(u)
1082 sys.exit(1)
1089 sys.exit(1)
1083
1090
1084 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1091 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1085 not options["noninteractive"])
1092 not options["noninteractive"])
1086
1093
1087 try:
1094 try:
1088 try:
1095 try:
1089 if cmd not in norepo.split():
1096 if cmd not in norepo.split():
1090 path = options["repository"] or ""
1097 path = options["repository"] or ""
1091 repo = hg.repository(ui=u, path=path)
1098 repo = hg.repository(ui=u, path=path)
1092 d = lambda: func(u, repo, *args, **cmdoptions)
1099 d = lambda: func(u, repo, *args, **cmdoptions)
1093 else:
1100 else:
1094 d = lambda: func(u, *args, **cmdoptions)
1101 d = lambda: func(u, *args, **cmdoptions)
1095
1102
1096 if options['profile']:
1103 if options['profile']:
1097 import hotshot, hotshot.stats
1104 import hotshot, hotshot.stats
1098 prof = hotshot.Profile("hg.prof")
1105 prof = hotshot.Profile("hg.prof")
1099 r = prof.runcall(d)
1106 r = prof.runcall(d)
1100 prof.close()
1107 prof.close()
1101 stats = hotshot.stats.load("hg.prof")
1108 stats = hotshot.stats.load("hg.prof")
1102 stats.strip_dirs()
1109 stats.strip_dirs()
1103 stats.sort_stats('time', 'calls')
1110 stats.sort_stats('time', 'calls')
1104 stats.print_stats(40)
1111 stats.print_stats(40)
1105 return r
1112 return r
1106 else:
1113 else:
1107 return d()
1114 return d()
1108 except:
1115 except:
1109 if options['traceback']:
1116 if options['traceback']:
1110 traceback.print_exc()
1117 traceback.print_exc()
1111 raise
1118 raise
1112 except util.CommandError, inst:
1119 except util.CommandError, inst:
1113 u.warn("abort: %s\n" % inst.args)
1120 u.warn("abort: %s\n" % inst.args)
1114 except hg.RepoError, inst:
1121 except hg.RepoError, inst:
1115 u.warn("abort: ", inst, "!\n")
1122 u.warn("abort: ", inst, "!\n")
1116 except SignalInterrupt:
1123 except SignalInterrupt:
1117 u.warn("killed!\n")
1124 u.warn("killed!\n")
1118 except KeyboardInterrupt:
1125 except KeyboardInterrupt:
1119 u.warn("interrupted!\n")
1126 u.warn("interrupted!\n")
1120 except IOError, inst:
1127 except IOError, inst:
1121 if hasattr(inst, "code"):
1128 if hasattr(inst, "code"):
1122 u.warn("abort: %s\n" % inst)
1129 u.warn("abort: %s\n" % inst)
1123 elif hasattr(inst, "reason"):
1130 elif hasattr(inst, "reason"):
1124 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
1131 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
1125 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1132 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1126 u.warn("broken pipe\n")
1133 u.warn("broken pipe\n")
1127 else:
1134 else:
1128 raise
1135 raise
1129 except OSError, inst:
1136 except OSError, inst:
1130 if hasattr(inst, "filename"):
1137 if hasattr(inst, "filename"):
1131 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1138 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1132 else:
1139 else:
1133 u.warn("abort: %s\n" % inst.strerror)
1140 u.warn("abort: %s\n" % inst.strerror)
1134 except TypeError, inst:
1141 except TypeError, inst:
1135 # was this an argument error?
1142 # was this an argument error?
1136 tb = traceback.extract_tb(sys.exc_info()[2])
1143 tb = traceback.extract_tb(sys.exc_info()[2])
1137 if len(tb) > 2: # no
1144 if len(tb) > 2: # no
1138 raise
1145 raise
1139 u.debug(inst, "\n")
1146 u.debug(inst, "\n")
1140 u.warn("%s: invalid arguments\n" % cmd)
1147 u.warn("%s: invalid arguments\n" % cmd)
1141 help(u, cmd)
1148 help(u, cmd)
1142
1149
1143 sys.exit(-1)
1150 sys.exit(-1)
@@ -1,766 +1,778 b''
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, cgi, time, re, difflib, sys, zlib
9 import os, cgi, time, re, difflib, sys, zlib
10 from mercurial.hg import *
10 from mercurial.hg import *
11 from mercurial.ui import *
11 from mercurial.ui import *
12
12
13 def templatepath():
13 def templatepath():
14 for f in "templates", "../templates":
14 for f in "templates", "../templates":
15 p = os.path.join(os.path.dirname(__file__), f)
15 p = os.path.join(os.path.dirname(__file__), f)
16 if os.path.isdir(p): return p
16 if os.path.isdir(p): return p
17
17
18 def age(t):
18 def age(t):
19 def plural(t, c):
19 def plural(t, c):
20 if c == 1: return t
20 if c == 1: return t
21 return t + "s"
21 return t + "s"
22 def fmt(t, c):
22 def fmt(t, c):
23 return "%d %s" % (c, plural(t, c))
23 return "%d %s" % (c, plural(t, c))
24
24
25 now = time.time()
25 now = time.time()
26 delta = max(1, int(now - t))
26 delta = max(1, int(now - t))
27
27
28 scales = [["second", 1],
28 scales = [["second", 1],
29 ["minute", 60],
29 ["minute", 60],
30 ["hour", 3600],
30 ["hour", 3600],
31 ["day", 3600 * 24],
31 ["day", 3600 * 24],
32 ["week", 3600 * 24 * 7],
32 ["week", 3600 * 24 * 7],
33 ["month", 3600 * 24 * 30],
33 ["month", 3600 * 24 * 30],
34 ["year", 3600 * 24 * 365]]
34 ["year", 3600 * 24 * 365]]
35
35
36 scales.reverse()
36 scales.reverse()
37
37
38 for t, s in scales:
38 for t, s in scales:
39 n = delta / s
39 n = delta / s
40 if n >= 2 or s == 1: return fmt(t, n)
40 if n >= 2 or s == 1: return fmt(t, n)
41
41
42 def nl2br(text):
42 def nl2br(text):
43 return text.replace('\n', '<br/>\n')
43 return text.replace('\n', '<br/>\n')
44
44
45 def obfuscate(text):
45 def obfuscate(text):
46 return ''.join([ '&#%d;' % ord(c) for c in text ])
46 return ''.join([ '&#%d;' % ord(c) for c in text ])
47
47
48 def up(p):
48 def up(p):
49 if p[0] != "/": p = "/" + p
49 if p[0] != "/": p = "/" + p
50 if p[-1] == "/": p = p[:-1]
50 if p[-1] == "/": p = p[:-1]
51 up = os.path.dirname(p)
51 up = os.path.dirname(p)
52 if up == "/":
52 if up == "/":
53 return "/"
53 return "/"
54 return up + "/"
54 return up + "/"
55
55
56 def httphdr(type):
56 def httphdr(type):
57 sys.stdout.write('Content-type: %s\n\n' % type)
57 sys.stdout.write('Content-type: %s\n\n' % type)
58
58
59 def write(*things):
59 def write(*things):
60 for thing in things:
60 for thing in things:
61 if hasattr(thing, "__iter__"):
61 if hasattr(thing, "__iter__"):
62 for part in thing:
62 for part in thing:
63 write(part)
63 write(part)
64 else:
64 else:
65 sys.stdout.write(str(thing))
65 sys.stdout.write(str(thing))
66
66
67 def template(tmpl, filters = {}, **map):
67 def template(tmpl, filters = {}, **map):
68 while tmpl:
68 while tmpl:
69 m = re.search(r"#([a-zA-Z0-9]+)((\|[a-zA-Z0-9]+)*)#", tmpl)
69 m = re.search(r"#([a-zA-Z0-9]+)((\|[a-zA-Z0-9]+)*)#", tmpl)
70 if m:
70 if m:
71 yield tmpl[:m.start(0)]
71 yield tmpl[:m.start(0)]
72 v = map.get(m.group(1), "")
72 v = map.get(m.group(1), "")
73 v = callable(v) and v() or v
73 v = callable(v) and v() or v
74
74
75 fl = m.group(2)
75 fl = m.group(2)
76 if fl:
76 if fl:
77 for f in fl.split("|")[1:]:
77 for f in fl.split("|")[1:]:
78 v = filters[f](v)
78 v = filters[f](v)
79
79
80 yield v
80 yield v
81 tmpl = tmpl[m.end(0):]
81 tmpl = tmpl[m.end(0):]
82 else:
82 else:
83 yield tmpl
83 yield tmpl
84 return
84 return
85
85
86 class templater:
86 class templater:
87 def __init__(self, mapfile, filters = {}, defaults = {}):
87 def __init__(self, mapfile, filters = {}, defaults = {}):
88 self.cache = {}
88 self.cache = {}
89 self.map = {}
89 self.map = {}
90 self.base = os.path.dirname(mapfile)
90 self.base = os.path.dirname(mapfile)
91 self.filters = filters
91 self.filters = filters
92 self.defaults = defaults
92 self.defaults = defaults
93
93
94 for l in file(mapfile):
94 for l in file(mapfile):
95 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
95 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
96 if m:
96 if m:
97 self.cache[m.group(1)] = m.group(2)
97 self.cache[m.group(1)] = m.group(2)
98 else:
98 else:
99 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
99 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
100 if m:
100 if m:
101 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
101 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
102 else:
102 else:
103 raise "unknown map entry '%s'" % l
103 raise "unknown map entry '%s'" % l
104
104
105 def __call__(self, t, **map):
105 def __call__(self, t, **map):
106 m = self.defaults.copy()
106 m = self.defaults.copy()
107 m.update(map)
107 m.update(map)
108 try:
108 try:
109 tmpl = self.cache[t]
109 tmpl = self.cache[t]
110 except KeyError:
110 except KeyError:
111 tmpl = self.cache[t] = file(self.map[t]).read()
111 tmpl = self.cache[t] = file(self.map[t]).read()
112 return template(tmpl, self.filters, **m)
112 return template(tmpl, self.filters, **m)
113
113
114 def rfc822date(x):
114 def rfc822date(x):
115 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
115 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
116
116
117 class hgweb:
117 class hgweb:
118 maxchanges = 10
118 maxchanges = 10
119 maxfiles = 10
119 maxfiles = 10
120
120
121 def __init__(self, path, name, templates = ""):
121 def __init__(self, path, name, templates = ""):
122 self.templates = templates or templatepath()
122 self.templates = templates or templatepath()
123 self.reponame = name
123 self.reponame = name
124 self.path = path
124 self.path = path
125 self.mtime = -1
125 self.mtime = -1
126 self.viewonly = 0
126 self.viewonly = 0
127
127
128 self.filters = {
128 self.filters = {
129 "escape": cgi.escape,
129 "escape": cgi.escape,
130 "age": age,
130 "age": age,
131 "date": (lambda x: time.asctime(time.gmtime(x))),
131 "date": (lambda x: time.asctime(time.gmtime(x))),
132 "addbreaks": nl2br,
132 "addbreaks": nl2br,
133 "obfuscate": obfuscate,
133 "obfuscate": obfuscate,
134 "short": (lambda x: x[:12]),
134 "short": (lambda x: x[:12]),
135 "firstline": (lambda x: x.splitlines(1)[0]),
135 "firstline": (lambda x: x.splitlines(1)[0]),
136 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
136 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
137 "rfc822date": rfc822date,
137 "rfc822date": rfc822date,
138 }
138 }
139
139
140 def refresh(self):
140 def refresh(self):
141 s = os.stat(os.path.join(self.path, ".hg", "00changelog.i"))
141 s = os.stat(os.path.join(self.path, ".hg", "00changelog.i"))
142 if s.st_mtime != self.mtime:
142 if s.st_mtime != self.mtime:
143 self.mtime = s.st_mtime
143 self.mtime = s.st_mtime
144 self.repo = repository(ui(), self.path)
144 self.repo = repository(ui(), self.path)
145
145
146 def date(self, cs):
146 def date(self, cs):
147 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
147 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
148
148
149 def listfiles(self, files, mf):
149 def listfiles(self, files, mf):
150 for f in files[:self.maxfiles]:
150 for f in files[:self.maxfiles]:
151 yield self.t("filenodelink", node = hex(mf[f]), file = f)
151 yield self.t("filenodelink", node = hex(mf[f]), file = f)
152 if len(files) > self.maxfiles:
152 if len(files) > self.maxfiles:
153 yield self.t("fileellipses")
153 yield self.t("fileellipses")
154
154
155 def listfilediffs(self, files, changeset):
155 def listfilediffs(self, files, changeset):
156 for f in files[:self.maxfiles]:
156 for f in files[:self.maxfiles]:
157 yield self.t("filedifflink", node = hex(changeset), file = f)
157 yield self.t("filedifflink", node = hex(changeset), file = f)
158 if len(files) > self.maxfiles:
158 if len(files) > self.maxfiles:
159 yield self.t("fileellipses")
159 yield self.t("fileellipses")
160
160
161 def parents(self, t1, nodes=[], rev=None,**args):
161 def parents(self, t1, nodes=[], rev=None,**args):
162 if not rev: rev = lambda x: ""
162 if not rev: rev = lambda x: ""
163 for node in nodes:
163 for node in nodes:
164 if node != nullid:
164 if node != nullid:
165 yield self.t(t1, node = hex(node), rev = rev(node), **args)
165 yield self.t(t1, node = hex(node), rev = rev(node), **args)
166
166
167 def showtag(self, t1, node=nullid, **args):
167 def showtag(self, t1, node=nullid, **args):
168 for t in self.repo.nodetags(node):
168 for t in self.repo.nodetags(node):
169 yield self.t(t1, tag = t, **args)
169 yield self.t(t1, tag = t, **args)
170
170
171 def diff(self, node1, node2, files):
171 def diff(self, node1, node2, files):
172 def filterfiles(list, files):
172 def filterfiles(list, files):
173 l = [ x for x in list if x in files ]
173 l = [ x for x in list if x in files ]
174
174
175 for f in files:
175 for f in files:
176 if f[-1] != os.sep: f += os.sep
176 if f[-1] != os.sep: f += os.sep
177 l += [ x for x in list if x.startswith(f) ]
177 l += [ x for x in list if x.startswith(f) ]
178 return l
178 return l
179
179
180 parity = [0]
180 parity = [0]
181 def diffblock(diff, f, fn):
181 def diffblock(diff, f, fn):
182 yield self.t("diffblock",
182 yield self.t("diffblock",
183 lines = prettyprintlines(diff),
183 lines = prettyprintlines(diff),
184 parity = parity[0],
184 parity = parity[0],
185 file = f,
185 file = f,
186 filenode = hex(fn or nullid))
186 filenode = hex(fn or nullid))
187 parity[0] = 1 - parity[0]
187 parity[0] = 1 - parity[0]
188
188
189 def prettyprintlines(diff):
189 def prettyprintlines(diff):
190 for l in diff.splitlines(1):
190 for l in diff.splitlines(1):
191 if l.startswith('+'):
191 if l.startswith('+'):
192 yield self.t("difflineplus", line = l)
192 yield self.t("difflineplus", line = l)
193 elif l.startswith('-'):
193 elif l.startswith('-'):
194 yield self.t("difflineminus", line = l)
194 yield self.t("difflineminus", line = l)
195 elif l.startswith('@'):
195 elif l.startswith('@'):
196 yield self.t("difflineat", line = l)
196 yield self.t("difflineat", line = l)
197 else:
197 else:
198 yield self.t("diffline", line = l)
198 yield self.t("diffline", line = l)
199
199
200 r = self.repo
200 r = self.repo
201 cl = r.changelog
201 cl = r.changelog
202 mf = r.manifest
202 mf = r.manifest
203 change1 = cl.read(node1)
203 change1 = cl.read(node1)
204 change2 = cl.read(node2)
204 change2 = cl.read(node2)
205 mmap1 = mf.read(change1[0])
205 mmap1 = mf.read(change1[0])
206 mmap2 = mf.read(change2[0])
206 mmap2 = mf.read(change2[0])
207 date1 = self.date(change1)
207 date1 = self.date(change1)
208 date2 = self.date(change2)
208 date2 = self.date(change2)
209
209
210 c, a, d, u = r.changes(node1, node2)
210 c, a, d, u = r.changes(node1, node2)
211 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
211 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
212
212
213 for f in c:
213 for f in c:
214 to = r.file(f).read(mmap1[f])
214 to = r.file(f).read(mmap1[f])
215 tn = r.file(f).read(mmap2[f])
215 tn = r.file(f).read(mmap2[f])
216 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
216 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
217 for f in a:
217 for f in a:
218 to = None
218 to = None
219 tn = r.file(f).read(mmap2[f])
219 tn = r.file(f).read(mmap2[f])
220 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
220 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
221 for f in d:
221 for f in d:
222 to = r.file(f).read(mmap1[f])
222 to = r.file(f).read(mmap1[f])
223 tn = None
223 tn = None
224 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
224 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
225
225
226 def header(self):
226 def header(self):
227 yield self.t("header")
227 yield self.t("header")
228
228
229 def footer(self):
229 def footer(self):
230 yield self.t("footer")
230 yield self.t("footer")
231
231
232 def changelog(self, pos):
232 def changelog(self, pos):
233 def changenav():
233 def changenav():
234 def seq(factor = 1):
234 def seq(factor = 1):
235 yield 1 * factor
235 yield 1 * factor
236 yield 3 * factor
236 yield 3 * factor
237 #yield 5 * factor
237 #yield 5 * factor
238 for f in seq(factor * 10):
238 for f in seq(factor * 10):
239 yield f
239 yield f
240
240
241 l = []
241 l = []
242 for f in seq():
242 for f in seq():
243 if f < self.maxchanges / 2: continue
243 if f < self.maxchanges / 2: continue
244 if f > count: break
244 if f > count: break
245 r = "%d" % f
245 r = "%d" % f
246 if pos + f < count: l.append(("+" + r, pos + f))
246 if pos + f < count: l.append(("+" + r, pos + f))
247 if pos - f >= 0: l.insert(0, ("-" + r, pos - f))
247 if pos - f >= 0: l.insert(0, ("-" + r, pos - f))
248
248
249 yield self.t("naventry", rev = 0, label="(0)")
249 yield self.t("naventry", rev = 0, label="(0)")
250
250
251 for label, rev in l:
251 for label, rev in l:
252 yield self.t("naventry", label = label, rev = rev)
252 yield self.t("naventry", label = label, rev = rev)
253
253
254 yield self.t("naventry", label="tip")
254 yield self.t("naventry", label="tip")
255
255
256 def changelist():
256 def changelist():
257 parity = (start - end) & 1
257 parity = (start - end) & 1
258 cl = self.repo.changelog
258 cl = self.repo.changelog
259 l = [] # build a list in forward order for efficiency
259 l = [] # build a list in forward order for efficiency
260 for i in range(start, end):
260 for i in range(start, end):
261 n = cl.node(i)
261 n = cl.node(i)
262 changes = cl.read(n)
262 changes = cl.read(n)
263 hn = hex(n)
263 hn = hex(n)
264 t = float(changes[2].split(' ')[0])
264 t = float(changes[2].split(' ')[0])
265
265
266 l.insert(0, self.t(
266 l.insert(0, self.t(
267 'changelogentry',
267 'changelogentry',
268 parity = parity,
268 parity = parity,
269 author = changes[1],
269 author = changes[1],
270 parent = self.parents("changelogparent",
270 parent = self.parents("changelogparent",
271 cl.parents(n), cl.rev),
271 cl.parents(n), cl.rev),
272 changelogtag = self.showtag("changelogtag",n),
272 changelogtag = self.showtag("changelogtag",n),
273 manifest = hex(changes[0]),
273 manifest = hex(changes[0]),
274 desc = changes[4],
274 desc = changes[4],
275 date = t,
275 date = t,
276 files = self.listfilediffs(changes[3], n),
276 files = self.listfilediffs(changes[3], n),
277 rev = i,
277 rev = i,
278 node = hn))
278 node = hn))
279 parity = 1 - parity
279 parity = 1 - parity
280
280
281 yield l
281 yield l
282
282
283 cl = self.repo.changelog
283 cl = self.repo.changelog
284 mf = cl.read(cl.tip())[0]
284 mf = cl.read(cl.tip())[0]
285 count = cl.count()
285 count = cl.count()
286 start = max(0, pos - self.maxchanges + 1)
286 start = max(0, pos - self.maxchanges + 1)
287 end = min(count, start + self.maxchanges)
287 end = min(count, start + self.maxchanges)
288 pos = end - 1
288 pos = end - 1
289
289
290 yield self.t('changelog',
290 yield self.t('changelog',
291 changenav = changenav,
291 changenav = changenav,
292 manifest = hex(mf),
292 manifest = hex(mf),
293 rev = pos, changesets = count, entries = changelist)
293 rev = pos, changesets = count, entries = changelist)
294
294
295 def search(self, query):
295 def search(self, query):
296
296
297 def changelist():
297 def changelist():
298 cl = self.repo.changelog
298 cl = self.repo.changelog
299 count = 0
299 count = 0
300 qw = query.lower().split()
300 qw = query.lower().split()
301
301
302 def revgen():
302 def revgen():
303 for i in range(cl.count() - 1, 0, -100):
303 for i in range(cl.count() - 1, 0, -100):
304 l = []
304 l = []
305 for j in range(max(0, i - 100), i):
305 for j in range(max(0, i - 100), i):
306 n = cl.node(j)
306 n = cl.node(j)
307 changes = cl.read(n)
307 changes = cl.read(n)
308 l.insert(0, (n, j, changes))
308 l.insert(0, (n, j, changes))
309 for e in l:
309 for e in l:
310 yield e
310 yield e
311
311
312 for n, i, changes in revgen():
312 for n, i, changes in revgen():
313 miss = 0
313 miss = 0
314 for q in qw:
314 for q in qw:
315 if not (q in changes[1].lower() or
315 if not (q in changes[1].lower() or
316 q in changes[4].lower() or
316 q in changes[4].lower() or
317 q in " ".join(changes[3][:20]).lower()):
317 q in " ".join(changes[3][:20]).lower()):
318 miss = 1
318 miss = 1
319 break
319 break
320 if miss: continue
320 if miss: continue
321
321
322 count += 1
322 count += 1
323 hn = hex(n)
323 hn = hex(n)
324 t = float(changes[2].split(' ')[0])
324 t = float(changes[2].split(' ')[0])
325
325
326 yield self.t(
326 yield self.t(
327 'searchentry',
327 'searchentry',
328 parity = count & 1,
328 parity = count & 1,
329 author = changes[1],
329 author = changes[1],
330 parent = self.parents("changelogparent",
330 parent = self.parents("changelogparent",
331 cl.parents(n), cl.rev),
331 cl.parents(n), cl.rev),
332 changelogtag = self.showtag("changelogtag",n),
332 changelogtag = self.showtag("changelogtag",n),
333 manifest = hex(changes[0]),
333 manifest = hex(changes[0]),
334 desc = changes[4],
334 desc = changes[4],
335 date = t,
335 date = t,
336 files = self.listfilediffs(changes[3], n),
336 files = self.listfilediffs(changes[3], n),
337 rev = i,
337 rev = i,
338 node = hn)
338 node = hn)
339
339
340 if count >= self.maxchanges: break
340 if count >= self.maxchanges: break
341
341
342 cl = self.repo.changelog
342 cl = self.repo.changelog
343 mf = cl.read(cl.tip())[0]
343 mf = cl.read(cl.tip())[0]
344
344
345 yield self.t('search',
345 yield self.t('search',
346 query = query,
346 query = query,
347 manifest = hex(mf),
347 manifest = hex(mf),
348 entries = changelist)
348 entries = changelist)
349
349
350 def changeset(self, nodeid):
350 def changeset(self, nodeid):
351 n = bin(nodeid)
351 n = bin(nodeid)
352 cl = self.repo.changelog
352 cl = self.repo.changelog
353 changes = cl.read(n)
353 changes = cl.read(n)
354 p1 = cl.parents(n)[0]
354 p1 = cl.parents(n)[0]
355 t = float(changes[2].split(' ')[0])
355 t = float(changes[2].split(' ')[0])
356
356
357 files = []
357 files = []
358 mf = self.repo.manifest.read(changes[0])
358 mf = self.repo.manifest.read(changes[0])
359 for f in changes[3]:
359 for f in changes[3]:
360 files.append(self.t("filenodelink",
360 files.append(self.t("filenodelink",
361 filenode = hex(mf.get(f, nullid)), file = f))
361 filenode = hex(mf.get(f, nullid)), file = f))
362
362
363 def diff():
363 def diff():
364 yield self.diff(p1, n, changes[3])
364 yield self.diff(p1, n, changes[3])
365
365
366 yield self.t('changeset',
366 yield self.t('changeset',
367 diff = diff,
367 diff = diff,
368 rev = cl.rev(n),
368 rev = cl.rev(n),
369 node = nodeid,
369 node = nodeid,
370 parent = self.parents("changesetparent",
370 parent = self.parents("changesetparent",
371 cl.parents(n), cl.rev),
371 cl.parents(n), cl.rev),
372 changesettag = self.showtag("changesettag",n),
372 changesettag = self.showtag("changesettag",n),
373 manifest = hex(changes[0]),
373 manifest = hex(changes[0]),
374 author = changes[1],
374 author = changes[1],
375 desc = changes[4],
375 desc = changes[4],
376 date = t,
376 date = t,
377 files = files)
377 files = files)
378
378
379 def filelog(self, f, filenode):
379 def filelog(self, f, filenode):
380 cl = self.repo.changelog
380 cl = self.repo.changelog
381 fl = self.repo.file(f)
381 fl = self.repo.file(f)
382 count = fl.count()
382 count = fl.count()
383
383
384 def entries():
384 def entries():
385 l = []
385 l = []
386 parity = (count - 1) & 1
386 parity = (count - 1) & 1
387
387
388 for i in range(count):
388 for i in range(count):
389
389
390 n = fl.node(i)
390 n = fl.node(i)
391 lr = fl.linkrev(n)
391 lr = fl.linkrev(n)
392 cn = cl.node(lr)
392 cn = cl.node(lr)
393 cs = cl.read(cl.node(lr))
393 cs = cl.read(cl.node(lr))
394 t = float(cs[2].split(' ')[0])
394 t = float(cs[2].split(' ')[0])
395
395
396 l.insert(0, self.t("filelogentry",
396 l.insert(0, self.t("filelogentry",
397 parity = parity,
397 parity = parity,
398 filenode = hex(n),
398 filenode = hex(n),
399 filerev = i,
399 filerev = i,
400 file = f,
400 file = f,
401 node = hex(cn),
401 node = hex(cn),
402 author = cs[1],
402 author = cs[1],
403 date = t,
403 date = t,
404 parent = self.parents("filelogparent",
404 parent = self.parents("filelogparent",
405 fl.parents(n), fl.rev, file=f),
405 fl.parents(n), fl.rev, file=f),
406 desc = cs[4]))
406 desc = cs[4]))
407 parity = 1 - parity
407 parity = 1 - parity
408
408
409 yield l
409 yield l
410
410
411 yield self.t("filelog",
411 yield self.t("filelog",
412 file = f,
412 file = f,
413 filenode = filenode,
413 filenode = filenode,
414 entries = entries)
414 entries = entries)
415
415
416 def filerevision(self, f, node):
416 def filerevision(self, f, node):
417 fl = self.repo.file(f)
417 fl = self.repo.file(f)
418 n = bin(node)
418 n = bin(node)
419 text = fl.read(n)
419 text = fl.read(n)
420 changerev = fl.linkrev(n)
420 changerev = fl.linkrev(n)
421 cl = self.repo.changelog
421 cl = self.repo.changelog
422 cn = cl.node(changerev)
422 cn = cl.node(changerev)
423 cs = cl.read(cn)
423 cs = cl.read(cn)
424 t = float(cs[2].split(' ')[0])
424 t = float(cs[2].split(' ')[0])
425 mfn = cs[0]
425 mfn = cs[0]
426
426
427 def lines():
427 def lines():
428 for l, t in enumerate(text.splitlines(1)):
428 for l, t in enumerate(text.splitlines(1)):
429 yield self.t("fileline", line = t,
429 yield self.t("fileline", line = t,
430 linenumber = "% 6d" % (l + 1),
430 linenumber = "% 6d" % (l + 1),
431 parity = l & 1)
431 parity = l & 1)
432
432
433 yield self.t("filerevision", file = f,
433 yield self.t("filerevision", file = f,
434 filenode = node,
434 filenode = node,
435 path = up(f),
435 path = up(f),
436 text = lines(),
436 text = lines(),
437 rev = changerev,
437 rev = changerev,
438 node = hex(cn),
438 node = hex(cn),
439 manifest = hex(mfn),
439 manifest = hex(mfn),
440 author = cs[1],
440 author = cs[1],
441 date = t,
441 date = t,
442 parent = self.parents("filerevparent",
442 parent = self.parents("filerevparent",
443 fl.parents(n), fl.rev, file=f),
443 fl.parents(n), fl.rev, file=f),
444 permissions = self.repo.manifest.readflags(mfn)[f])
444 permissions = self.repo.manifest.readflags(mfn)[f])
445
445
446 def fileannotate(self, f, node):
446 def fileannotate(self, f, node):
447 bcache = {}
447 bcache = {}
448 ncache = {}
448 ncache = {}
449 fl = self.repo.file(f)
449 fl = self.repo.file(f)
450 n = bin(node)
450 n = bin(node)
451 changerev = fl.linkrev(n)
451 changerev = fl.linkrev(n)
452
452
453 cl = self.repo.changelog
453 cl = self.repo.changelog
454 cn = cl.node(changerev)
454 cn = cl.node(changerev)
455 cs = cl.read(cn)
455 cs = cl.read(cn)
456 t = float(cs[2].split(' ')[0])
456 t = float(cs[2].split(' ')[0])
457 mfn = cs[0]
457 mfn = cs[0]
458
458
459 def annotate():
459 def annotate():
460 parity = 1
460 parity = 1
461 last = None
461 last = None
462 for r, l in fl.annotate(n):
462 for r, l in fl.annotate(n):
463 try:
463 try:
464 cnode = ncache[r]
464 cnode = ncache[r]
465 except KeyError:
465 except KeyError:
466 cnode = ncache[r] = self.repo.changelog.node(r)
466 cnode = ncache[r] = self.repo.changelog.node(r)
467
467
468 try:
468 try:
469 name = bcache[r]
469 name = bcache[r]
470 except KeyError:
470 except KeyError:
471 cl = self.repo.changelog.read(cnode)
471 cl = self.repo.changelog.read(cnode)
472 name = cl[1]
472 name = cl[1]
473 f = name.find('@')
473 f = name.find('@')
474 if f >= 0:
474 if f >= 0:
475 name = name[:f]
475 name = name[:f]
476 f = name.find('<')
476 f = name.find('<')
477 if f >= 0:
477 if f >= 0:
478 name = name[f+1:]
478 name = name[f+1:]
479 bcache[r] = name
479 bcache[r] = name
480
480
481 if last != cnode:
481 if last != cnode:
482 parity = 1 - parity
482 parity = 1 - parity
483 last = cnode
483 last = cnode
484
484
485 yield self.t("annotateline",
485 yield self.t("annotateline",
486 parity = parity,
486 parity = parity,
487 node = hex(cnode),
487 node = hex(cnode),
488 rev = r,
488 rev = r,
489 author = name,
489 author = name,
490 file = f,
490 file = f,
491 line = l)
491 line = l)
492
492
493 yield self.t("fileannotate",
493 yield self.t("fileannotate",
494 file = f,
494 file = f,
495 filenode = node,
495 filenode = node,
496 annotate = annotate,
496 annotate = annotate,
497 path = up(f),
497 path = up(f),
498 rev = changerev,
498 rev = changerev,
499 node = hex(cn),
499 node = hex(cn),
500 manifest = hex(mfn),
500 manifest = hex(mfn),
501 author = cs[1],
501 author = cs[1],
502 date = t,
502 date = t,
503 parent = self.parents("fileannotateparent",
503 parent = self.parents("fileannotateparent",
504 fl.parents(n), fl.rev, file=f),
504 fl.parents(n), fl.rev, file=f),
505 permissions = self.repo.manifest.readflags(mfn)[f])
505 permissions = self.repo.manifest.readflags(mfn)[f])
506
506
507 def manifest(self, mnode, path):
507 def manifest(self, mnode, path):
508 mf = self.repo.manifest.read(bin(mnode))
508 mf = self.repo.manifest.read(bin(mnode))
509 rev = self.repo.manifest.rev(bin(mnode))
509 rev = self.repo.manifest.rev(bin(mnode))
510 node = self.repo.changelog.node(rev)
510 node = self.repo.changelog.node(rev)
511 mff=self.repo.manifest.readflags(bin(mnode))
511 mff=self.repo.manifest.readflags(bin(mnode))
512
512
513 files = {}
513 files = {}
514
514
515 p = path[1:]
515 p = path[1:]
516 l = len(p)
516 l = len(p)
517
517
518 for f,n in mf.items():
518 for f,n in mf.items():
519 if f[:l] != p:
519 if f[:l] != p:
520 continue
520 continue
521 remain = f[l:]
521 remain = f[l:]
522 if "/" in remain:
522 if "/" in remain:
523 short = remain[:remain.find("/") + 1] # bleah
523 short = remain[:remain.find("/") + 1] # bleah
524 files[short] = (f, None)
524 files[short] = (f, None)
525 else:
525 else:
526 short = os.path.basename(remain)
526 short = os.path.basename(remain)
527 files[short] = (f, n)
527 files[short] = (f, n)
528
528
529 def filelist():
529 def filelist():
530 parity = 0
530 parity = 0
531 fl = files.keys()
531 fl = files.keys()
532 fl.sort()
532 fl.sort()
533 for f in fl:
533 for f in fl:
534 full, fnode = files[f]
534 full, fnode = files[f]
535 if fnode:
535 if fnode:
536 yield self.t("manifestfileentry",
536 yield self.t("manifestfileentry",
537 file = full,
537 file = full,
538 manifest = mnode,
538 manifest = mnode,
539 filenode = hex(fnode),
539 filenode = hex(fnode),
540 parity = parity,
540 parity = parity,
541 basename = f,
541 basename = f,
542 permissions = mff[full])
542 permissions = mff[full])
543 else:
543 else:
544 yield self.t("manifestdirentry",
544 yield self.t("manifestdirentry",
545 parity = parity,
545 parity = parity,
546 path = os.path.join(path, f),
546 path = os.path.join(path, f),
547 manifest = mnode, basename = f[:-1])
547 manifest = mnode, basename = f[:-1])
548 parity = 1 - parity
548 parity = 1 - parity
549
549
550 yield self.t("manifest",
550 yield self.t("manifest",
551 manifest = mnode,
551 manifest = mnode,
552 rev = rev,
552 rev = rev,
553 node = hex(node),
553 node = hex(node),
554 path = path,
554 path = path,
555 up = up(path),
555 up = up(path),
556 entries = filelist)
556 entries = filelist)
557
557
558 def tags(self):
558 def tags(self):
559 cl = self.repo.changelog
559 cl = self.repo.changelog
560 mf = cl.read(cl.tip())[0]
560 mf = cl.read(cl.tip())[0]
561
561
562 i = self.repo.tagslist()
562 i = self.repo.tagslist()
563 i.reverse()
563 i.reverse()
564
564
565 def entries():
565 def entries():
566 parity = 0
566 parity = 0
567 for k,n in i:
567 for k,n in i:
568 yield self.t("tagentry",
568 yield self.t("tagentry",
569 parity = parity,
569 parity = parity,
570 tag = k,
570 tag = k,
571 node = hex(n))
571 node = hex(n))
572 parity = 1 - parity
572 parity = 1 - parity
573
573
574 yield self.t("tags",
574 yield self.t("tags",
575 manifest = hex(mf),
575 manifest = hex(mf),
576 entries = entries)
576 entries = entries)
577
577
578 def filediff(self, file, changeset):
578 def filediff(self, file, changeset):
579 n = bin(changeset)
579 n = bin(changeset)
580 cl = self.repo.changelog
580 cl = self.repo.changelog
581 p1 = cl.parents(n)[0]
581 p1 = cl.parents(n)[0]
582 cs = cl.read(n)
582 cs = cl.read(n)
583 mf = self.repo.manifest.read(cs[0])
583 mf = self.repo.manifest.read(cs[0])
584
584
585 def diff():
585 def diff():
586 yield self.diff(p1, n, file)
586 yield self.diff(p1, n, file)
587
587
588 yield self.t("filediff",
588 yield self.t("filediff",
589 file = file,
589 file = file,
590 filenode = hex(mf.get(file, nullid)),
590 filenode = hex(mf.get(file, nullid)),
591 node = changeset,
591 node = changeset,
592 rev = self.repo.changelog.rev(n),
592 rev = self.repo.changelog.rev(n),
593 parent = self.parents("filediffparent",
593 parent = self.parents("filediffparent",
594 cl.parents(n), cl.rev),
594 cl.parents(n), cl.rev),
595 diff = diff)
595 diff = diff)
596
596
597 # add tags to things
597 # add tags to things
598 # tags -> list of changesets corresponding to tags
598 # tags -> list of changesets corresponding to tags
599 # find tag, changeset, file
599 # find tag, changeset, file
600
600
601 def run(self):
601 def run(self):
602 self.refresh()
602 self.refresh()
603 args = cgi.parse()
603 args = cgi.parse()
604
604
605 m = os.path.join(self.templates, "map")
605 m = os.path.join(self.templates, "map")
606 if args.has_key('style'):
606 if args.has_key('style'):
607 b = os.path.basename("map-" + args['style'][0])
607 b = os.path.basename("map-" + args['style'][0])
608 p = os.path.join(self.templates, b)
608 p = os.path.join(self.templates, b)
609 if os.path.isfile(p): m = p
609 if os.path.isfile(p): m = p
610
610
611 port = os.environ["SERVER_PORT"]
611 port = os.environ["SERVER_PORT"]
612 port = port != "80" and (":" + port) or ""
612 port = port != "80" and (":" + port) or ""
613 url = "http://%s%s%s" % \
613 url = "http://%s%s%s" % \
614 (os.environ["SERVER_NAME"], port, os.environ["REQUEST_URI"])
614 (os.environ["SERVER_NAME"], port, os.environ["REQUEST_URI"])
615
615
616 self.t = templater(m, self.filters,
616 self.t = templater(m, self.filters,
617 {"url":url,
617 {"url":url,
618 "repo":self.reponame,
618 "repo":self.reponame,
619 "header":self.header(),
619 "header":self.header(),
620 "footer":self.footer(),
620 "footer":self.footer(),
621 })
621 })
622
622
623 if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
623 if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
624 c = self.repo.changelog.count() - 1
624 c = self.repo.changelog.count() - 1
625 hi = c
625 hi = c
626 if args.has_key('rev'):
626 if args.has_key('rev'):
627 hi = args['rev'][0]
627 hi = args['rev'][0]
628 try:
628 try:
629 hi = self.repo.changelog.rev(self.repo.lookup(hi))
629 hi = self.repo.changelog.rev(self.repo.lookup(hi))
630 except KeyError:
630 except KeyError:
631 write(self.search(hi))
631 write(self.search(hi))
632 return
632 return
633
633
634 write(self.changelog(hi))
634 write(self.changelog(hi))
635
635
636 elif args['cmd'][0] == 'changeset':
636 elif args['cmd'][0] == 'changeset':
637 write(self.changeset(args['node'][0]))
637 write(self.changeset(args['node'][0]))
638
638
639 elif args['cmd'][0] == 'manifest':
639 elif args['cmd'][0] == 'manifest':
640 write(self.manifest(args['manifest'][0], args['path'][0]))
640 write(self.manifest(args['manifest'][0], args['path'][0]))
641
641
642 elif args['cmd'][0] == 'tags':
642 elif args['cmd'][0] == 'tags':
643 write(self.tags())
643 write(self.tags())
644
644
645 elif args['cmd'][0] == 'filediff':
645 elif args['cmd'][0] == 'filediff':
646 write(self.filediff(args['file'][0], args['node'][0]))
646 write(self.filediff(args['file'][0], args['node'][0]))
647
647
648 elif args['cmd'][0] == 'file':
648 elif args['cmd'][0] == 'file':
649 write(self.filerevision(args['file'][0], args['filenode'][0]))
649 write(self.filerevision(args['file'][0], args['filenode'][0]))
650
650
651 elif args['cmd'][0] == 'annotate':
651 elif args['cmd'][0] == 'annotate':
652 write(self.fileannotate(args['file'][0], args['filenode'][0]))
652 write(self.fileannotate(args['file'][0], args['filenode'][0]))
653
653
654 elif args['cmd'][0] == 'filelog':
654 elif args['cmd'][0] == 'filelog':
655 write(self.filelog(args['file'][0], args['filenode'][0]))
655 write(self.filelog(args['file'][0], args['filenode'][0]))
656
656
657 elif args['cmd'][0] == 'heads':
657 elif args['cmd'][0] == 'heads':
658 httphdr("text/plain")
658 httphdr("text/plain")
659 h = self.repo.heads()
659 h = self.repo.heads()
660 sys.stdout.write(" ".join(map(hex, h)) + "\n")
660 sys.stdout.write(" ".join(map(hex, h)) + "\n")
661
661
662 elif args['cmd'][0] == 'branches':
662 elif args['cmd'][0] == 'branches':
663 httphdr("text/plain")
663 httphdr("text/plain")
664 nodes = []
664 nodes = []
665 if args.has_key('nodes'):
665 if args.has_key('nodes'):
666 nodes = map(bin, args['nodes'][0].split(" "))
666 nodes = map(bin, args['nodes'][0].split(" "))
667 for b in self.repo.branches(nodes):
667 for b in self.repo.branches(nodes):
668 sys.stdout.write(" ".join(map(hex, b)) + "\n")
668 sys.stdout.write(" ".join(map(hex, b)) + "\n")
669
669
670 elif args['cmd'][0] == 'between':
670 elif args['cmd'][0] == 'between':
671 httphdr("text/plain")
671 httphdr("text/plain")
672 nodes = []
672 nodes = []
673 if args.has_key('pairs'):
673 if args.has_key('pairs'):
674 pairs = [ map(bin, p.split("-"))
674 pairs = [ map(bin, p.split("-"))
675 for p in args['pairs'][0].split(" ") ]
675 for p in args['pairs'][0].split(" ") ]
676 for b in self.repo.between(pairs):
676 for b in self.repo.between(pairs):
677 sys.stdout.write(" ".join(map(hex, b)) + "\n")
677 sys.stdout.write(" ".join(map(hex, b)) + "\n")
678
678
679 elif args['cmd'][0] == 'changegroup':
679 elif args['cmd'][0] == 'changegroup':
680 httphdr("application/hg-changegroup")
680 httphdr("application/hg-changegroup")
681 nodes = []
681 nodes = []
682 if self.viewonly:
682 if self.viewonly:
683 return
683 return
684
684
685 if args.has_key('roots'):
685 if args.has_key('roots'):
686 nodes = map(bin, args['roots'][0].split(" "))
686 nodes = map(bin, args['roots'][0].split(" "))
687
687
688 z = zlib.compressobj()
688 z = zlib.compressobj()
689 for chunk in self.repo.changegroup(nodes):
689 for chunk in self.repo.changegroup(nodes):
690 sys.stdout.write(z.compress(chunk))
690 sys.stdout.write(z.compress(chunk))
691
691
692 sys.stdout.write(z.flush())
692 sys.stdout.write(z.flush())
693
693
694 else:
694 else:
695 write(self.t("error"))
695 write(self.t("error"))
696
696
697 def create_server(path, name, templates, address, port):
697 def create_server(path, name, templates, address, port,
698 accesslog = sys.stdout, errorlog = sys.stderr):
698
699
699 import BaseHTTPServer
700 import BaseHTTPServer
700 import sys, os
701
701
702 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
702 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
703 def log_error(self, format, *args):
704 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
705 self.log_date_time_string(),
706 format % args))
707
708 def log_message(self, format, *args):
709 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
710 self.log_date_time_string(),
711 format % args))
712
703 def do_POST(self):
713 def do_POST(self):
704 try:
714 try:
705 self.do_hgweb()
715 self.do_hgweb()
706 except socket.error, inst:
716 except socket.error, inst:
707 if inst.args[0] != 32: raise
717 if inst.args[0] != 32: raise
708
718
709 def do_GET(self):
719 def do_GET(self):
710 self.do_POST()
720 self.do_POST()
711
721
712 def do_hgweb(self):
722 def do_hgweb(self):
713 query = ""
723 query = ""
714 p = self.path.find("?")
724 p = self.path.find("?")
715 if p:
725 if p:
716 query = self.path[p + 1:]
726 query = self.path[p + 1:]
717 query = query.replace('+', ' ')
727 query = query.replace('+', ' ')
718
728
719 env = {}
729 env = {}
720 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
730 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
721 env['REQUEST_METHOD'] = self.command
731 env['REQUEST_METHOD'] = self.command
722 env['SERVER_NAME'] = self.server.server_name
732 env['SERVER_NAME'] = self.server.server_name
723 env['SERVER_PORT'] = str(self.server.server_port)
733 env['SERVER_PORT'] = str(self.server.server_port)
724 env['REQUEST_URI'] = "/"
734 env['REQUEST_URI'] = "/"
725 if query:
735 if query:
726 env['QUERY_STRING'] = query
736 env['QUERY_STRING'] = query
727 host = self.address_string()
737 host = self.address_string()
728 if host != self.client_address[0]:
738 if host != self.client_address[0]:
729 env['REMOTE_HOST'] = host
739 env['REMOTE_HOST'] = host
730 env['REMOTE_ADDR'] = self.client_address[0]
740 env['REMOTE_ADDR'] = self.client_address[0]
731
741
732 if self.headers.typeheader is None:
742 if self.headers.typeheader is None:
733 env['CONTENT_TYPE'] = self.headers.type
743 env['CONTENT_TYPE'] = self.headers.type
734 else:
744 else:
735 env['CONTENT_TYPE'] = self.headers.typeheader
745 env['CONTENT_TYPE'] = self.headers.typeheader
736 length = self.headers.getheader('content-length')
746 length = self.headers.getheader('content-length')
737 if length:
747 if length:
738 env['CONTENT_LENGTH'] = length
748 env['CONTENT_LENGTH'] = length
739 accept = []
749 accept = []
740 for line in self.headers.getallmatchingheaders('accept'):
750 for line in self.headers.getallmatchingheaders('accept'):
741 if line[:1] in "\t\n\r ":
751 if line[:1] in "\t\n\r ":
742 accept.append(line.strip())
752 accept.append(line.strip())
743 else:
753 else:
744 accept = accept + line[7:].split(',')
754 accept = accept + line[7:].split(',')
745 env['HTTP_ACCEPT'] = ','.join(accept)
755 env['HTTP_ACCEPT'] = ','.join(accept)
746
756
747 os.environ.update(env)
757 os.environ.update(env)
748
758
749 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
759 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
750 try:
760 try:
751 sys.stdin = self.rfile
761 sys.stdin = self.rfile
752 sys.stdout = self.wfile
762 sys.stdout = self.wfile
753 sys.argv = ["hgweb.py"]
763 sys.argv = ["hgweb.py"]
754 if '=' not in query:
764 if '=' not in query:
755 sys.argv.append(query)
765 sys.argv.append(query)
756 self.send_response(200, "Script output follows")
766 self.send_response(200, "Script output follows")
757 hg.run()
767 hg.run()
758 finally:
768 finally:
759 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
769 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
760
770
761 hg = hgweb(path, name, templates)
771 hg = hgweb(path, name, templates)
762 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
772 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
763
773
764 def server(path, name, templates, address, port):
774 def server(path, name, templates, address, port,
765 httpd = create_server(path, name, templates, address, port)
775 accesslog = sys.stdout, errorlog = sys.stderr):
776 httpd = create_server(path, name, templates, address, port,
777 accesslog, errorlog)
766 httpd.serve_forever()
778 httpd.serve_forever()
General Comments 0
You need to be logged in to leave comments. Login now