##// END OF EJS Templates
Merge with mpm
Brendan Cully -
r4712:f49fcbb3 merge default
parent child Browse files
Show More
@@ -0,0 +1,127 b''
1 # repair.py - functions for repository repair for mercurial
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 # Copyright 2007 Matt Mackall
5 #
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
8
9 import changegroup, revlog, os, commands
10
11 def strip(ui, repo, rev, backup="all"):
12 def limitheads(chlog, stop):
13 """return the list of all nodes that have no children"""
14 p = {}
15 h = []
16 stoprev = 0
17 if stop in chlog.nodemap:
18 stoprev = chlog.rev(stop)
19
20 for r in xrange(chlog.count() - 1, -1, -1):
21 n = chlog.node(r)
22 if n not in p:
23 h.append(n)
24 if n == stop:
25 break
26 if r < stoprev:
27 break
28 for pn in chlog.parents(n):
29 p[pn] = 1
30 return h
31
32 def bundle(repo, bases, heads, rev, suffix):
33 cg = repo.changegroupsubset(bases, heads, 'strip')
34 backupdir = repo.join("strip-backup")
35 if not os.path.isdir(backupdir):
36 os.mkdir(backupdir)
37 name = os.path.join(backupdir, "%s-%s" % (revlog.short(rev), suffix))
38 ui.warn("saving bundle to %s\n" % name)
39 return changegroup.writebundle(cg, name, "HG10BZ")
40
41 def stripall(revnum):
42 mm = repo.changectx(rev).manifest()
43 seen = {}
44
45 for x in xrange(revnum, repo.changelog.count()):
46 for f in repo.changectx(x).files():
47 if f in seen:
48 continue
49 seen[f] = 1
50 if f in mm:
51 filerev = mm[f]
52 else:
53 filerev = 0
54 seen[f] = filerev
55 # we go in two steps here so the strip loop happens in a
56 # sensible order. When stripping many files, this helps keep
57 # our disk access patterns under control.
58 seen_list = seen.keys()
59 seen_list.sort()
60 for f in seen_list:
61 ff = repo.file(f)
62 filerev = seen[f]
63 if filerev != 0:
64 if filerev in ff.nodemap:
65 filerev = ff.rev(filerev)
66 else:
67 filerev = 0
68 ff.strip(filerev, revnum)
69
70 chlog = repo.changelog
71 # TODO delete the undo files, and handle undo of merge sets
72 pp = chlog.parents(rev)
73 revnum = chlog.rev(rev)
74
75 # save is a list of all the branches we are truncating away
76 # that we actually want to keep. changegroup will be used
77 # to preserve them and add them back after the truncate
78 saveheads = []
79 savebases = {}
80
81 heads = limitheads(chlog, rev)
82 seen = {}
83
84 # search through all the heads, finding those where the revision
85 # we want to strip away is an ancestor. Also look for merges
86 # that might be turned into new heads by the strip.
87 while heads:
88 h = heads.pop()
89 n = h
90 while True:
91 seen[n] = 1
92 pp = chlog.parents(n)
93 if pp[1] != revlog.nullid:
94 for p in pp:
95 if chlog.rev(p) > revnum and p not in seen:
96 heads.append(p)
97 if pp[0] == revlog.nullid:
98 break
99 if chlog.rev(pp[0]) < revnum:
100 break
101 n = pp[0]
102 if n == rev:
103 break
104 r = chlog.reachable(h, rev)
105 if rev not in r:
106 saveheads.append(h)
107 for x in r:
108 if chlog.rev(x) > revnum:
109 savebases[x] = 1
110
111 # create a changegroup for all the branches we need to keep
112 if backup == "all":
113 bundle(repo, [rev], chlog.heads(), rev, 'backup')
114 if saveheads:
115 chgrpfile = bundle(repo, savebases.keys(), saveheads, rev, 'temp')
116
117 stripall(revnum)
118
119 change = chlog.read(rev)
120 chlog.strip(revnum, revnum)
121 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
122 if saveheads:
123 ui.status("adding branch\n")
124 commands.unbundle(ui, repo, "file:%s" % chgrpfile, update=False)
125 if backup != "strip":
126 os.unlink(chgrpfile)
127
@@ -1,77 +1,79 b''
1 PREFIX=/usr/local
1 PREFIX=/usr/local
2 export PREFIX
2 export PREFIX
3 PYTHON=python
3 PYTHON=python
4
4
5 help:
5 help:
6 @echo 'Commonly used make targets:'
6 @echo 'Commonly used make targets:'
7 @echo ' all - build program and documentation'
7 @echo ' all - build program and documentation'
8 @echo ' install - install program and man pages to PREFIX ($(PREFIX))'
8 @echo ' install - install program and man pages to PREFIX ($(PREFIX))'
9 @echo ' install-home - install with setup.py install --home=HOME ($(HOME))'
9 @echo ' install-home - install with setup.py install --home=HOME ($(HOME))'
10 @echo ' local - build C extensions for inplace usage'
10 @echo ' local - build for inplace usage'
11 @echo ' tests - run all tests in the automatic test suite'
11 @echo ' tests - run all tests in the automatic test suite'
12 @echo ' test-foo - run only specified tests (e.g. test-merge1)'
12 @echo ' test-foo - run only specified tests (e.g. test-merge1)'
13 @echo ' dist - run all tests and create a source tarball in dist/'
13 @echo ' dist - run all tests and create a source tarball in dist/'
14 @echo ' clean - remove files created by other targets'
14 @echo ' clean - remove files created by other targets'
15 @echo ' (except installed files or dist source tarball)'
15 @echo ' (except installed files or dist source tarball)'
16 @echo
16 @echo
17 @echo 'Example for a system-wide installation under /usr/local:'
17 @echo 'Example for a system-wide installation under /usr/local:'
18 @echo ' make all && su -c "make install" && hg version'
18 @echo ' make all && su -c "make install" && hg version'
19 @echo
19 @echo
20 @echo 'Example for a local installation (usable in this directory):'
20 @echo 'Example for a local installation (usable in this directory):'
21 @echo ' make local && ./hg version'
21 @echo ' make local && ./hg version'
22
22
23 all: build doc
23 all: build doc
24
24
25 local:
25 local:
26 $(PYTHON) setup.py build_ext -i
26 $(PYTHON) setup.py build_ext -i
27 $(PYTHON) setup.py build_py -c -d .
28 $(PYTHON) hg version
27
29
28 build:
30 build:
29 $(PYTHON) setup.py build
31 $(PYTHON) setup.py build
30
32
31 doc:
33 doc:
32 $(MAKE) -C doc
34 $(MAKE) -C doc
33
35
34 clean:
36 clean:
35 -$(PYTHON) setup.py clean --all # ignore errors of this command
37 -$(PYTHON) setup.py clean --all # ignore errors of this command
36 find . -name '*.py[co]' -exec rm -f '{}' ';'
38 find . -name '*.py[cdo]' -exec rm -f '{}' ';'
37 rm -f MANIFEST mercurial/__version__.py mercurial/*.so tests/*.err
39 rm -f MANIFEST mercurial/__version__.py mercurial/*.so tests/*.err
38 $(MAKE) -C doc clean
40 $(MAKE) -C doc clean
39
41
40 install: install-bin install-doc
42 install: install-bin install-doc
41
43
42 install-bin: build
44 install-bin: build
43 $(PYTHON) setup.py install --prefix="$(PREFIX)" --force
45 $(PYTHON) setup.py install --prefix="$(PREFIX)" --force
44
46
45 install-doc: doc
47 install-doc: doc
46 cd doc && $(MAKE) $(MFLAGS) install
48 cd doc && $(MAKE) $(MFLAGS) install
47
49
48 install-home: install-home-bin install-home-doc
50 install-home: install-home-bin install-home-doc
49
51
50 install-home-bin: build
52 install-home-bin: build
51 $(PYTHON) setup.py install --home="$(HOME)" --force
53 $(PYTHON) setup.py install --home="$(HOME)" --force
52
54
53 install-home-doc: doc
55 install-home-doc: doc
54 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
56 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
55
57
56 MANIFEST-doc:
58 MANIFEST-doc:
57 $(MAKE) -C doc MANIFEST
59 $(MAKE) -C doc MANIFEST
58
60
59 MANIFEST: MANIFEST-doc
61 MANIFEST: MANIFEST-doc
60 hg manifest > MANIFEST
62 hg manifest > MANIFEST
61 echo mercurial/__version__.py >> MANIFEST
63 echo mercurial/__version__.py >> MANIFEST
62 cat doc/MANIFEST >> MANIFEST
64 cat doc/MANIFEST >> MANIFEST
63
65
64 dist: tests dist-notests
66 dist: tests dist-notests
65
67
66 dist-notests: doc MANIFEST
68 dist-notests: doc MANIFEST
67 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
69 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
68
70
69 tests:
71 tests:
70 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
72 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
71
73
72 test-%:
74 test-%:
73 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
75 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
74
76
75
77
76 .PHONY: help all local build doc clean install install-bin install-doc \
78 .PHONY: help all local build doc clean install install-bin install-doc \
77 install-home install-home-bin install-home-doc dist dist-notests tests
79 install-home install-home-bin install-home-doc dist dist-notests tests
@@ -1,574 +1,576 b''
1 HGRC(5)
1 HGRC(5)
2 =======
2 =======
3 Bryan O'Sullivan <bos@serpentine.com>
3 Bryan O'Sullivan <bos@serpentine.com>
4
4
5 NAME
5 NAME
6 ----
6 ----
7 hgrc - configuration files for Mercurial
7 hgrc - configuration files for Mercurial
8
8
9 SYNOPSIS
9 SYNOPSIS
10 --------
10 --------
11
11
12 The Mercurial system uses a set of configuration files to control
12 The Mercurial system uses a set of configuration files to control
13 aspects of its behaviour.
13 aspects of its behaviour.
14
14
15 FILES
15 FILES
16 -----
16 -----
17
17
18 Mercurial reads configuration data from several files, if they exist.
18 Mercurial reads configuration data from several files, if they exist.
19 The names of these files depend on the system on which Mercurial is
19 The names of these files depend on the system on which Mercurial is
20 installed.
20 installed.
21
21
22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 (Unix) <install-root>/etc/mercurial/hgrc::
23 (Unix) <install-root>/etc/mercurial/hgrc::
24 Per-installation configuration files, searched for in the
24 Per-installation configuration files, searched for in the
25 directory where Mercurial is installed. For example, if installed
25 directory where Mercurial is installed. For example, if installed
26 in /shared/tools, Mercurial will look in
26 in /shared/tools, Mercurial will look in
27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 all Mercurial commands executed by any user in any directory.
28 all Mercurial commands executed by any user in any directory.
29
29
30 (Unix) /etc/mercurial/hgrc.d/*.rc::
30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 (Unix) /etc/mercurial/hgrc::
31 (Unix) /etc/mercurial/hgrc::
32 (Windows) C:\Mercurial\Mercurial.ini::
32 (Windows) C:\Mercurial\Mercurial.ini::
33 Per-system configuration files, for the system on which Mercurial
33 Per-system configuration files, for the system on which Mercurial
34 is running. Options in these files apply to all Mercurial
34 is running. Options in these files apply to all Mercurial
35 commands executed by any user in any directory. Options in these
35 commands executed by any user in any directory. Options in these
36 files override per-installation options.
36 files override per-installation options.
37
37
38 (Unix) $HOME/.hgrc::
38 (Unix) $HOME/.hgrc::
39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
40 (Windows) $HOME\Mercurial.ini::
40 (Windows) $HOME\Mercurial.ini::
41 Per-user configuration file, for the user running Mercurial.
41 Per-user configuration file, for the user running Mercurial.
42 Options in this file apply to all Mercurial commands executed by
42 Options in this file apply to all Mercurial commands executed by
43 any user in any directory. Options in this file override
43 any user in any directory. Options in this file override
44 per-installation and per-system options.
44 per-installation and per-system options.
45 On Windows system, one of these is chosen exclusively according
45 On Windows system, one of these is chosen exclusively according
46 to definition of HOME environment variable.
46 to definition of HOME environment variable.
47
47
48 (Unix, Windows) <repo>/.hg/hgrc::
48 (Unix, Windows) <repo>/.hg/hgrc::
49 Per-repository configuration options that only apply in a
49 Per-repository configuration options that only apply in a
50 particular repository. This file is not version-controlled, and
50 particular repository. This file is not version-controlled, and
51 will not get transferred during a "clone" operation. Options in
51 will not get transferred during a "clone" operation. Options in
52 this file override options in all other configuration files.
52 this file override options in all other configuration files.
53 On Unix, most of this file will be ignored if it doesn't belong
53 On Unix, most of this file will be ignored if it doesn't belong
54 to a trusted user or to a trusted group. See the documentation
54 to a trusted user or to a trusted group. See the documentation
55 for the trusted section below for more details.
55 for the trusted section below for more details.
56
56
57 SYNTAX
57 SYNTAX
58 ------
58 ------
59
59
60 A configuration file consists of sections, led by a "[section]" header
60 A configuration file consists of sections, led by a "[section]" header
61 and followed by "name: value" entries; "name=value" is also accepted.
61 and followed by "name: value" entries; "name=value" is also accepted.
62
62
63 [spam]
63 [spam]
64 eggs=ham
64 eggs=ham
65 green=
65 green=
66 eggs
66 eggs
67
67
68 Each line contains one entry. If the lines that follow are indented,
68 Each line contains one entry. If the lines that follow are indented,
69 they are treated as continuations of that entry.
69 they are treated as continuations of that entry.
70
70
71 Leading whitespace is removed from values. Empty lines are skipped.
71 Leading whitespace is removed from values. Empty lines are skipped.
72
72
73 The optional values can contain format strings which refer to other
73 The optional values can contain format strings which refer to other
74 values in the same section, or values in a special DEFAULT section.
74 values in the same section, or values in a special DEFAULT section.
75
75
76 Lines beginning with "#" or ";" are ignored and may be used to provide
76 Lines beginning with "#" or ";" are ignored and may be used to provide
77 comments.
77 comments.
78
78
79 SECTIONS
79 SECTIONS
80 --------
80 --------
81
81
82 This section describes the different sections that may appear in a
82 This section describes the different sections that may appear in a
83 Mercurial "hgrc" file, the purpose of each section, its possible
83 Mercurial "hgrc" file, the purpose of each section, its possible
84 keys, and their possible values.
84 keys, and their possible values.
85
85
86 decode/encode::
86 decode/encode::
87 Filters for transforming files on checkout/checkin. This would
87 Filters for transforming files on checkout/checkin. This would
88 typically be used for newline processing or other
88 typically be used for newline processing or other
89 localization/canonicalization of files.
89 localization/canonicalization of files.
90
90
91 Filters consist of a filter pattern followed by a filter command.
91 Filters consist of a filter pattern followed by a filter command.
92 Filter patterns are globs by default, rooted at the repository
92 Filter patterns are globs by default, rooted at the repository
93 root. For example, to match any file ending in ".txt" in the root
93 root. For example, to match any file ending in ".txt" in the root
94 directory only, use the pattern "*.txt". To match any file ending
94 directory only, use the pattern "*.txt". To match any file ending
95 in ".c" anywhere in the repository, use the pattern "**.c".
95 in ".c" anywhere in the repository, use the pattern "**.c".
96
96
97 The filter command can start with a specifier, either "pipe:" or
97 The filter command can start with a specifier, either "pipe:" or
98 "tempfile:". If no specifier is given, "pipe:" is used by default.
98 "tempfile:". If no specifier is given, "pipe:" is used by default.
99
99
100 A "pipe:" command must accept data on stdin and return the
100 A "pipe:" command must accept data on stdin and return the
101 transformed data on stdout.
101 transformed data on stdout.
102
102
103 Pipe example:
103 Pipe example:
104
104
105 [encode]
105 [encode]
106 # uncompress gzip files on checkin to improve delta compression
106 # uncompress gzip files on checkin to improve delta compression
107 # note: not necessarily a good idea, just an example
107 # note: not necessarily a good idea, just an example
108 *.gz = pipe: gunzip
108 *.gz = pipe: gunzip
109
109
110 [decode]
110 [decode]
111 # recompress gzip files when writing them to the working dir (we
111 # recompress gzip files when writing them to the working dir (we
112 # can safely omit "pipe:", because it's the default)
112 # can safely omit "pipe:", because it's the default)
113 *.gz = gzip
113 *.gz = gzip
114
114
115 A "tempfile:" command is a template. The string INFILE is replaced
115 A "tempfile:" command is a template. The string INFILE is replaced
116 with the name of a temporary file that contains the data to be
116 with the name of a temporary file that contains the data to be
117 filtered by the command. The string OUTFILE is replaced with the
117 filtered by the command. The string OUTFILE is replaced with the
118 name of an empty temporary file, where the filtered data must be
118 name of an empty temporary file, where the filtered data must be
119 written by the command.
119 written by the command.
120
120
121 NOTE: the tempfile mechanism is recommended for Windows systems,
121 NOTE: the tempfile mechanism is recommended for Windows systems,
122 where the standard shell I/O redirection operators often have
122 where the standard shell I/O redirection operators often have
123 strange effects. In particular, if you are doing line ending
123 strange effects. In particular, if you are doing line ending
124 conversion on Windows using the popular dos2unix and unix2dos
124 conversion on Windows using the popular dos2unix and unix2dos
125 programs, you *must* use the tempfile mechanism, as using pipes will
125 programs, you *must* use the tempfile mechanism, as using pipes will
126 corrupt the contents of your files.
126 corrupt the contents of your files.
127
127
128 Tempfile example:
128 Tempfile example:
129
129
130 [encode]
130 [encode]
131 # convert files to unix line ending conventions on checkin
131 # convert files to unix line ending conventions on checkin
132 **.txt = tempfile: dos2unix -n INFILE OUTFILE
132 **.txt = tempfile: dos2unix -n INFILE OUTFILE
133
133
134 [decode]
134 [decode]
135 # convert files to windows line ending conventions when writing
135 # convert files to windows line ending conventions when writing
136 # them to the working dir
136 # them to the working dir
137 **.txt = tempfile: unix2dos -n INFILE OUTFILE
137 **.txt = tempfile: unix2dos -n INFILE OUTFILE
138
138
139 defaults::
139 defaults::
140 Use the [defaults] section to define command defaults, i.e. the
140 Use the [defaults] section to define command defaults, i.e. the
141 default options/arguments to pass to the specified commands.
141 default options/arguments to pass to the specified commands.
142
142
143 The following example makes 'hg log' run in verbose mode, and
143 The following example makes 'hg log' run in verbose mode, and
144 'hg status' show only the modified files, by default.
144 'hg status' show only the modified files, by default.
145
145
146 [defaults]
146 [defaults]
147 log = -v
147 log = -v
148 status = -m
148 status = -m
149
149
150 The actual commands, instead of their aliases, must be used when
150 The actual commands, instead of their aliases, must be used when
151 defining command defaults. The command defaults will also be
151 defining command defaults. The command defaults will also be
152 applied to the aliases of the commands defined.
152 applied to the aliases of the commands defined.
153
153
154 diff::
154 diff::
155 Settings used when displaying diffs. They are all boolean and
155 Settings used when displaying diffs. They are all boolean and
156 defaults to False.
156 defaults to False.
157 git;;
157 git;;
158 Use git extended diff format.
158 Use git extended diff format.
159 nodates;;
159 nodates;;
160 Don't include dates in diff headers.
160 Don't include dates in diff headers.
161 showfunc;;
161 showfunc;;
162 Show which function each change is in.
162 Show which function each change is in.
163 ignorews;;
163 ignorews;;
164 Ignore white space when comparing lines.
164 Ignore white space when comparing lines.
165 ignorewsamount;;
165 ignorewsamount;;
166 Ignore changes in the amount of white space.
166 Ignore changes in the amount of white space.
167 ignoreblanklines;;
167 ignoreblanklines;;
168 Ignore changes whose lines are all blank.
168 Ignore changes whose lines are all blank.
169
169
170 email::
170 email::
171 Settings for extensions that send email messages.
171 Settings for extensions that send email messages.
172 from;;
172 from;;
173 Optional. Email address to use in "From" header and SMTP envelope
173 Optional. Email address to use in "From" header and SMTP envelope
174 of outgoing messages.
174 of outgoing messages.
175 to;;
175 to;;
176 Optional. Comma-separated list of recipients' email addresses.
176 Optional. Comma-separated list of recipients' email addresses.
177 cc;;
177 cc;;
178 Optional. Comma-separated list of carbon copy recipients'
178 Optional. Comma-separated list of carbon copy recipients'
179 email addresses.
179 email addresses.
180 bcc;;
180 bcc;;
181 Optional. Comma-separated list of blind carbon copy
181 Optional. Comma-separated list of blind carbon copy
182 recipients' email addresses. Cannot be set interactively.
182 recipients' email addresses. Cannot be set interactively.
183 method;;
183 method;;
184 Optional. Method to use to send email messages. If value is
184 Optional. Method to use to send email messages. If value is
185 "smtp" (default), use SMTP (see section "[smtp]" for
185 "smtp" (default), use SMTP (see section "[smtp]" for
186 configuration). Otherwise, use as name of program to run that
186 configuration). Otherwise, use as name of program to run that
187 acts like sendmail (takes "-f" option for sender, list of
187 acts like sendmail (takes "-f" option for sender, list of
188 recipients on command line, message on stdin). Normally, setting
188 recipients on command line, message on stdin). Normally, setting
189 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
189 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
190 sendmail to send messages.
190 sendmail to send messages.
191
191
192 Email example:
192 Email example:
193
193
194 [email]
194 [email]
195 from = Joseph User <joe.user@example.com>
195 from = Joseph User <joe.user@example.com>
196 method = /usr/sbin/sendmail
196 method = /usr/sbin/sendmail
197
197
198 extensions::
198 extensions::
199 Mercurial has an extension mechanism for adding new features. To
199 Mercurial has an extension mechanism for adding new features. To
200 enable an extension, create an entry for it in this section.
200 enable an extension, create an entry for it in this section.
201
201
202 If you know that the extension is already in Python's search path,
202 If you know that the extension is already in Python's search path,
203 you can give the name of the module, followed by "=", with nothing
203 you can give the name of the module, followed by "=", with nothing
204 after the "=".
204 after the "=".
205
205
206 Otherwise, give a name that you choose, followed by "=", followed by
206 Otherwise, give a name that you choose, followed by "=", followed by
207 the path to the ".py" file (including the file name extension) that
207 the path to the ".py" file (including the file name extension) that
208 defines the extension.
208 defines the extension.
209
209
210 Example for ~/.hgrc:
210 Example for ~/.hgrc:
211
211
212 [extensions]
212 [extensions]
213 # (the mq extension will get loaded from mercurial's path)
213 # (the mq extension will get loaded from mercurial's path)
214 hgext.mq =
214 hgext.mq =
215 # (this extension will get loaded from the file specified)
215 # (this extension will get loaded from the file specified)
216 myfeature = ~/.hgext/myfeature.py
216 myfeature = ~/.hgext/myfeature.py
217
217
218 format::
218 format::
219
219
220 usestore;;
220 usestore;;
221 Enable or disable the "store" repository format which improves
221 Enable or disable the "store" repository format which improves
222 compatibility with systems that fold case or otherwise mangle
222 compatibility with systems that fold case or otherwise mangle
223 filenames. Enabled by default. Disabling this option will allow
223 filenames. Enabled by default. Disabling this option will allow
224 you to store longer filenames in some situations at the expense of
224 you to store longer filenames in some situations at the expense of
225 compatibility.
225 compatibility.
226
226
227 hooks::
227 hooks::
228 Commands or Python functions that get automatically executed by
228 Commands or Python functions that get automatically executed by
229 various actions such as starting or finishing a commit. Multiple
229 various actions such as starting or finishing a commit. Multiple
230 hooks can be run for the same action by appending a suffix to the
230 hooks can be run for the same action by appending a suffix to the
231 action. Overriding a site-wide hook can be done by changing its
231 action. Overriding a site-wide hook can be done by changing its
232 value or setting it to an empty string.
232 value or setting it to an empty string.
233
233
234 Example .hg/hgrc:
234 Example .hg/hgrc:
235
235
236 [hooks]
236 [hooks]
237 # do not use the site-wide hook
237 # do not use the site-wide hook
238 incoming =
238 incoming =
239 incoming.email = /my/email/hook
239 incoming.email = /my/email/hook
240 incoming.autobuild = /my/build/hook
240 incoming.autobuild = /my/build/hook
241
241
242 Most hooks are run with environment variables set that give added
242 Most hooks are run with environment variables set that give added
243 useful information. For each hook below, the environment variables
243 useful information. For each hook below, the environment variables
244 it is passed are listed with names of the form "$HG_foo".
244 it is passed are listed with names of the form "$HG_foo".
245
245
246 changegroup;;
246 changegroup;;
247 Run after a changegroup has been added via push, pull or
247 Run after a changegroup has been added via push, pull or
248 unbundle. ID of the first new changeset is in $HG_NODE. URL from
248 unbundle. ID of the first new changeset is in $HG_NODE. URL from
249 which changes came is in $HG_URL.
249 which changes came is in $HG_URL.
250 commit;;
250 commit;;
251 Run after a changeset has been created in the local repository.
251 Run after a changeset has been created in the local repository.
252 ID of the newly created changeset is in $HG_NODE. Parent
252 ID of the newly created changeset is in $HG_NODE. Parent
253 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
253 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
254 incoming;;
254 incoming;;
255 Run after a changeset has been pulled, pushed, or unbundled into
255 Run after a changeset has been pulled, pushed, or unbundled into
256 the local repository. The ID of the newly arrived changeset is in
256 the local repository. The ID of the newly arrived changeset is in
257 $HG_NODE. URL that was source of changes came is in $HG_URL.
257 $HG_NODE. URL that was source of changes came is in $HG_URL.
258 outgoing;;
258 outgoing;;
259 Run after sending changes from local repository to another. ID of
259 Run after sending changes from local repository to another. ID of
260 first changeset sent is in $HG_NODE. Source of operation is in
260 first changeset sent is in $HG_NODE. Source of operation is in
261 $HG_SOURCE; see "preoutgoing" hook for description.
261 $HG_SOURCE; see "preoutgoing" hook for description.
262 prechangegroup;;
262 prechangegroup;;
263 Run before a changegroup is added via push, pull or unbundle.
263 Run before a changegroup is added via push, pull or unbundle.
264 Exit status 0 allows the changegroup to proceed. Non-zero status
264 Exit status 0 allows the changegroup to proceed. Non-zero status
265 will cause the push, pull or unbundle to fail. URL from which
265 will cause the push, pull or unbundle to fail. URL from which
266 changes will come is in $HG_URL.
266 changes will come is in $HG_URL.
267 precommit;;
267 precommit;;
268 Run before starting a local commit. Exit status 0 allows the
268 Run before starting a local commit. Exit status 0 allows the
269 commit to proceed. Non-zero status will cause the commit to fail.
269 commit to proceed. Non-zero status will cause the commit to fail.
270 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
270 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
271 preoutgoing;;
271 preoutgoing;;
272 Run before computing changes to send from the local repository to
272 Run before computing changes to send from the local repository to
273 another. Non-zero status will cause failure. This lets you
273 another. Non-zero status will cause failure. This lets you
274 prevent pull over http or ssh. Also prevents against local pull,
274 prevent pull over http or ssh. Also prevents against local pull,
275 push (outbound) or bundle commands, but not effective, since you
275 push (outbound) or bundle commands, but not effective, since you
276 can just copy files instead then. Source of operation is in
276 can just copy files instead then. Source of operation is in
277 $HG_SOURCE. If "serve", operation is happening on behalf of
277 $HG_SOURCE. If "serve", operation is happening on behalf of
278 remote ssh or http repository. If "push", "pull" or "bundle",
278 remote ssh or http repository. If "push", "pull" or "bundle",
279 operation is happening on behalf of repository on same system.
279 operation is happening on behalf of repository on same system.
280 pretag;;
280 pretag;;
281 Run before creating a tag. Exit status 0 allows the tag to be
281 Run before creating a tag. Exit status 0 allows the tag to be
282 created. Non-zero status will cause the tag to fail. ID of
282 created. Non-zero status will cause the tag to fail. ID of
283 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
283 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
284 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
284 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
285 pretxnchangegroup;;
285 pretxnchangegroup;;
286 Run after a changegroup has been added via push, pull or unbundle,
286 Run after a changegroup has been added via push, pull or unbundle,
287 but before the transaction has been committed. Changegroup is
287 but before the transaction has been committed. Changegroup is
288 visible to hook program. This lets you validate incoming changes
288 visible to hook program. This lets you validate incoming changes
289 before accepting them. Passed the ID of the first new changeset
289 before accepting them. Passed the ID of the first new changeset
290 in $HG_NODE. Exit status 0 allows the transaction to commit.
290 in $HG_NODE. Exit status 0 allows the transaction to commit.
291 Non-zero status will cause the transaction to be rolled back and
291 Non-zero status will cause the transaction to be rolled back and
292 the push, pull or unbundle will fail. URL that was source of
292 the push, pull or unbundle will fail. URL that was source of
293 changes is in $HG_URL.
293 changes is in $HG_URL.
294 pretxncommit;;
294 pretxncommit;;
295 Run after a changeset has been created but the transaction not yet
295 Run after a changeset has been created but the transaction not yet
296 committed. Changeset is visible to hook program. This lets you
296 committed. Changeset is visible to hook program. This lets you
297 validate commit message and changes. Exit status 0 allows the
297 validate commit message and changes. Exit status 0 allows the
298 commit to proceed. Non-zero status will cause the transaction to
298 commit to proceed. Non-zero status will cause the transaction to
299 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
299 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
300 IDs are in $HG_PARENT1 and $HG_PARENT2.
300 IDs are in $HG_PARENT1 and $HG_PARENT2.
301 preupdate;;
301 preupdate;;
302 Run before updating the working directory. Exit status 0 allows
302 Run before updating the working directory. Exit status 0 allows
303 the update to proceed. Non-zero status will prevent the update.
303 the update to proceed. Non-zero status will prevent the update.
304 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
304 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
305 of second new parent is in $HG_PARENT2.
305 of second new parent is in $HG_PARENT2.
306 tag;;
306 tag;;
307 Run after a tag is created. ID of tagged changeset is in
307 Run after a tag is created. ID of tagged changeset is in
308 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
308 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
309 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
309 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
310 update;;
310 update;;
311 Run after updating the working directory. Changeset ID of first
311 Run after updating the working directory. Changeset ID of first
312 new parent is in $HG_PARENT1. If merge, ID of second new parent
312 new parent is in $HG_PARENT1. If merge, ID of second new parent
313 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
313 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
314 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
314 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
315 pre-<command>;;
315 pre-<command>;;
316 Run before executing the associated command. The contents of the
316 Run before executing the associated command. The contents of the
317 command line are passed as $HG_ARGS. If the hook returns failure,
317 command line are passed as $HG_ARGS. If the hook returns failure,
318 the command doesn't execute and Mercurial returns the failure code.
318 the command doesn't execute and Mercurial returns the failure code.
319 post-<command>;;
319 post-<command>;;
320 Run after successful invocations of the associated command. The
320 Run after successful invocations of the associated command. The
321 contents of the command line are passed as $HG_ARGS and the result
321 contents of the command line are passed as $HG_ARGS and the result
322 code in $HG_RESULT. Hook failure is ignored.
322 code in $HG_RESULT. Hook failure is ignored.
323
323
324 Note: it is generally better to use standard hooks rather than the
324 Note: it is generally better to use standard hooks rather than the
325 generic pre- and post- command hooks as they are guaranteed to be
325 generic pre- and post- command hooks as they are guaranteed to be
326 called in the appropriate contexts for influencing transactions.
326 called in the appropriate contexts for influencing transactions.
327 Also, hooks like "commit" will be called in all contexts that
327 Also, hooks like "commit" will be called in all contexts that
328 generate a commit (eg. tag) and not just the commit command.
328 generate a commit (eg. tag) and not just the commit command.
329
329
330 Note2: Environment variables with empty values may not be passed to
330 Note2: Environment variables with empty values may not be passed to
331 hooks on platforms like Windows. For instance, $HG_PARENT2 will
331 hooks on platforms like Windows. For instance, $HG_PARENT2 will
332 not be available under Windows for non-merge changesets while being
332 not be available under Windows for non-merge changesets while being
333 set to an empty value under Unix-like systems.
333 set to an empty value under Unix-like systems.
334
334
335 The syntax for Python hooks is as follows:
335 The syntax for Python hooks is as follows:
336
336
337 hookname = python:modulename.submodule.callable
337 hookname = python:modulename.submodule.callable
338
338
339 Python hooks are run within the Mercurial process. Each hook is
339 Python hooks are run within the Mercurial process. Each hook is
340 called with at least three keyword arguments: a ui object (keyword
340 called with at least three keyword arguments: a ui object (keyword
341 "ui"), a repository object (keyword "repo"), and a "hooktype"
341 "ui"), a repository object (keyword "repo"), and a "hooktype"
342 keyword that tells what kind of hook is used. Arguments listed as
342 keyword that tells what kind of hook is used. Arguments listed as
343 environment variables above are passed as keyword arguments, with no
343 environment variables above are passed as keyword arguments, with no
344 "HG_" prefix, and names in lower case.
344 "HG_" prefix, and names in lower case.
345
345
346 If a Python hook returns a "true" value or raises an exception, this
346 If a Python hook returns a "true" value or raises an exception, this
347 is treated as failure of the hook.
347 is treated as failure of the hook.
348
348
349 http_proxy::
349 http_proxy::
350 Used to access web-based Mercurial repositories through a HTTP
350 Used to access web-based Mercurial repositories through a HTTP
351 proxy.
351 proxy.
352 host;;
352 host;;
353 Host name and (optional) port of the proxy server, for example
353 Host name and (optional) port of the proxy server, for example
354 "myproxy:8000".
354 "myproxy:8000".
355 no;;
355 no;;
356 Optional. Comma-separated list of host names that should bypass
356 Optional. Comma-separated list of host names that should bypass
357 the proxy.
357 the proxy.
358 passwd;;
358 passwd;;
359 Optional. Password to authenticate with at the proxy server.
359 Optional. Password to authenticate with at the proxy server.
360 user;;
360 user;;
361 Optional. User name to authenticate with at the proxy server.
361 Optional. User name to authenticate with at the proxy server.
362
362
363 smtp::
363 smtp::
364 Configuration for extensions that need to send email messages.
364 Configuration for extensions that need to send email messages.
365 host;;
365 host;;
366 Host name of mail server, e.g. "mail.example.com".
366 Host name of mail server, e.g. "mail.example.com".
367 port;;
367 port;;
368 Optional. Port to connect to on mail server. Default: 25.
368 Optional. Port to connect to on mail server. Default: 25.
369 tls;;
369 tls;;
370 Optional. Whether to connect to mail server using TLS. True or
370 Optional. Whether to connect to mail server using TLS. True or
371 False. Default: False.
371 False. Default: False.
372 username;;
372 username;;
373 Optional. User name to authenticate to SMTP server with.
373 Optional. User name to authenticate to SMTP server with.
374 If username is specified, password must also be specified.
374 If username is specified, password must also be specified.
375 Default: none.
375 Default: none.
376 password;;
376 password;;
377 Optional. Password to authenticate to SMTP server with.
377 Optional. Password to authenticate to SMTP server with.
378 If username is specified, password must also be specified.
378 If username is specified, password must also be specified.
379 Default: none.
379 Default: none.
380 local_hostname;;
380 local_hostname;;
381 Optional. It's the hostname that the sender can use to identify itself
381 Optional. It's the hostname that the sender can use to identify itself
382 to the MTA.
382 to the MTA.
383
383
384 paths::
384 paths::
385 Assigns symbolic names to repositories. The left side is the
385 Assigns symbolic names to repositories. The left side is the
386 symbolic name, and the right gives the directory or URL that is the
386 symbolic name, and the right gives the directory or URL that is the
387 location of the repository. Default paths can be declared by
387 location of the repository. Default paths can be declared by
388 setting the following entries.
388 setting the following entries.
389 default;;
389 default;;
390 Directory or URL to use when pulling if no source is specified.
390 Directory or URL to use when pulling if no source is specified.
391 Default is set to repository from which the current repository
391 Default is set to repository from which the current repository
392 was cloned.
392 was cloned.
393 default-push;;
393 default-push;;
394 Optional. Directory or URL to use when pushing if no destination
394 Optional. Directory or URL to use when pushing if no destination
395 is specified.
395 is specified.
396
396
397 server::
397 server::
398 Controls generic server settings.
398 Controls generic server settings.
399 uncompressed;;
399 uncompressed;;
400 Whether to allow clients to clone a repo using the uncompressed
400 Whether to allow clients to clone a repo using the uncompressed
401 streaming protocol. This transfers about 40% more data than a
401 streaming protocol. This transfers about 40% more data than a
402 regular clone, but uses less memory and CPU on both server and
402 regular clone, but uses less memory and CPU on both server and
403 client. Over a LAN (100Mbps or better) or a very fast WAN, an
403 client. Over a LAN (100Mbps or better) or a very fast WAN, an
404 uncompressed streaming clone is a lot faster (~10x) than a regular
404 uncompressed streaming clone is a lot faster (~10x) than a regular
405 clone. Over most WAN connections (anything slower than about
405 clone. Over most WAN connections (anything slower than about
406 6Mbps), uncompressed streaming is slower, because of the extra
406 6Mbps), uncompressed streaming is slower, because of the extra
407 data transfer overhead. Default is False.
407 data transfer overhead. Default is False.
408
408
409 trusted::
409 trusted::
410 For security reasons, Mercurial will not use the settings in
410 For security reasons, Mercurial will not use the settings in
411 the .hg/hgrc file from a repository if it doesn't belong to a
411 the .hg/hgrc file from a repository if it doesn't belong to a
412 trusted user or to a trusted group. The main exception is the
412 trusted user or to a trusted group. The main exception is the
413 web interface, which automatically uses some safe settings, since
413 web interface, which automatically uses some safe settings, since
414 it's common to serve repositories from different users.
414 it's common to serve repositories from different users.
415
415
416 This section specifies what users and groups are trusted. The
416 This section specifies what users and groups are trusted. The
417 current user is always trusted. To trust everybody, list a user
417 current user is always trusted. To trust everybody, list a user
418 or a group with name "*".
418 or a group with name "*".
419
419
420 users;;
420 users;;
421 Comma-separated list of trusted users.
421 Comma-separated list of trusted users.
422 groups;;
422 groups;;
423 Comma-separated list of trusted groups.
423 Comma-separated list of trusted groups.
424
424
425 ui::
425 ui::
426 User interface controls.
426 User interface controls.
427 debug;;
427 debug;;
428 Print debugging information. True or False. Default is False.
428 Print debugging information. True or False. Default is False.
429 editor;;
429 editor;;
430 The editor to use during a commit. Default is $EDITOR or "vi".
430 The editor to use during a commit. Default is $EDITOR or "vi".
431 fallbackencoding;;
431 fallbackencoding;;
432 Encoding to try if it's not possible to decode the changelog using
432 Encoding to try if it's not possible to decode the changelog using
433 UTF-8. Default is ISO-8859-1.
433 UTF-8. Default is ISO-8859-1.
434 ignore;;
434 ignore;;
435 A file to read per-user ignore patterns from. This file should be in
435 A file to read per-user ignore patterns from. This file should be in
436 the same format as a repository-wide .hgignore file. This option
436 the same format as a repository-wide .hgignore file. This option
437 supports hook syntax, so if you want to specify multiple ignore
437 supports hook syntax, so if you want to specify multiple ignore
438 files, you can do so by setting something like
438 files, you can do so by setting something like
439 "ignore.other = ~/.hgignore2". For details of the ignore file
439 "ignore.other = ~/.hgignore2". For details of the ignore file
440 format, see the hgignore(5) man page.
440 format, see the hgignore(5) man page.
441 interactive;;
441 interactive;;
442 Allow to prompt the user. True or False. Default is True.
442 Allow to prompt the user. True or False. Default is True.
443 logtemplate;;
443 logtemplate;;
444 Template string for commands that print changesets.
444 Template string for commands that print changesets.
445 style;;
445 style;;
446 Name of style to use for command output.
446 Name of style to use for command output.
447 merge;;
447 merge;;
448 The conflict resolution program to use during a manual merge.
448 The conflict resolution program to use during a manual merge.
449 Default is "hgmerge".
449 Default is "hgmerge".
450 patch;;
450 patch;;
451 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
451 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
452 unset.
452 unset.
453 quiet;;
453 quiet;;
454 Reduce the amount of output printed. True or False. Default is False.
454 Reduce the amount of output printed. True or False. Default is False.
455 remotecmd;;
455 remotecmd;;
456 remote command to use for clone/push/pull operations. Default is 'hg'.
456 remote command to use for clone/push/pull operations. Default is 'hg'.
457 slash;;
457 slash;;
458 Display paths using a slash ("/") as the path separator. This only
458 Display paths using a slash ("/") as the path separator. This only
459 makes a difference on systems where the default path separator is not
459 makes a difference on systems where the default path separator is not
460 the slash character (e.g. Windows uses the backslash character ("\")).
460 the slash character (e.g. Windows uses the backslash character ("\")).
461 Default is False.
461 Default is False.
462 ssh;;
462 ssh;;
463 command to use for SSH connections. Default is 'ssh'.
463 command to use for SSH connections. Default is 'ssh'.
464 strict;;
464 strict;;
465 Require exact command names, instead of allowing unambiguous
465 Require exact command names, instead of allowing unambiguous
466 abbreviations. True or False. Default is False.
466 abbreviations. True or False. Default is False.
467 timeout;;
467 timeout;;
468 The timeout used when a lock is held (in seconds), a negative value
468 The timeout used when a lock is held (in seconds), a negative value
469 means no timeout. Default is 600.
469 means no timeout. Default is 600.
470 username;;
470 username;;
471 The committer of a changeset created when running "commit".
471 The committer of a changeset created when running "commit".
472 Typically a person's name and email address, e.g. "Fred Widget
472 Typically a person's name and email address, e.g. "Fred Widget
473 <fred@example.com>". Default is $EMAIL or username@hostname.
473 <fred@example.com>". Default is $EMAIL or username@hostname.
474 If the username in hgrc is empty, it has to be specified manually or
474 If the username in hgrc is empty, it has to be specified manually or
475 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
475 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
476 in the system hgrc).
476 in the system hgrc).
477 verbose;;
477 verbose;;
478 Increase the amount of output printed. True or False. Default is False.
478 Increase the amount of output printed. True or False. Default is False.
479
479
480
480
481 web::
481 web::
482 Web interface configuration.
482 Web interface configuration.
483 accesslog;;
483 accesslog;;
484 Where to output the access log. Default is stdout.
484 Where to output the access log. Default is stdout.
485 address;;
485 address;;
486 Interface address to bind to. Default is all.
486 Interface address to bind to. Default is all.
487 allow_archive;;
487 allow_archive;;
488 List of archive format (bz2, gz, zip) allowed for downloading.
488 List of archive format (bz2, gz, zip) allowed for downloading.
489 Default is empty.
489 Default is empty.
490 allowbz2;;
490 allowbz2;;
491 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
491 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
492 Default is false.
492 Default is false.
493 allowgz;;
493 allowgz;;
494 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
494 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
495 Default is false.
495 Default is false.
496 allowpull;;
496 allowpull;;
497 Whether to allow pulling from the repository. Default is true.
497 Whether to allow pulling from the repository. Default is true.
498 allow_push;;
498 allow_push;;
499 Whether to allow pushing to the repository. If empty or not set,
499 Whether to allow pushing to the repository. If empty or not set,
500 push is not allowed. If the special value "*", any remote user
500 push is not allowed. If the special value "*", any remote user
501 can push, including unauthenticated users. Otherwise, the remote
501 can push, including unauthenticated users. Otherwise, the remote
502 user must have been authenticated, and the authenticated user name
502 user must have been authenticated, and the authenticated user name
503 must be present in this list (separated by whitespace or ",").
503 must be present in this list (separated by whitespace or ",").
504 The contents of the allow_push list are examined after the
504 The contents of the allow_push list are examined after the
505 deny_push list.
505 deny_push list.
506 allowzip;;
506 allowzip;;
507 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
507 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
508 Default is false. This feature creates temporary files.
508 Default is false. This feature creates temporary files.
509 baseurl;;
509 baseurl;;
510 Base URL to use when publishing URLs in other locations, so
510 Base URL to use when publishing URLs in other locations, so
511 third-party tools like email notification hooks can construct URLs.
511 third-party tools like email notification hooks can construct URLs.
512 Example: "http://hgserver/repos/"
512 Example: "http://hgserver/repos/"
513 contact;;
513 contact;;
514 Name or email address of the person in charge of the repository.
514 Name or email address of the person in charge of the repository.
515 Default is "unknown".
515 Default is "unknown".
516 deny_push;;
516 deny_push;;
517 Whether to deny pushing to the repository. If empty or not set,
517 Whether to deny pushing to the repository. If empty or not set,
518 push is not denied. If the special value "*", all remote users
518 push is not denied. If the special value "*", all remote users
519 are denied push. Otherwise, unauthenticated users are all denied,
519 are denied push. Otherwise, unauthenticated users are all denied,
520 and any authenticated user name present in this list (separated by
520 and any authenticated user name present in this list (separated by
521 whitespace or ",") is also denied. The contents of the deny_push
521 whitespace or ",") is also denied. The contents of the deny_push
522 list are examined before the allow_push list.
522 list are examined before the allow_push list.
523 description;;
523 description;;
524 Textual description of the repository's purpose or contents.
524 Textual description of the repository's purpose or contents.
525 Default is "unknown".
525 Default is "unknown".
526 errorlog;;
526 errorlog;;
527 Where to output the error log. Default is stderr.
527 Where to output the error log. Default is stderr.
528 hidden;;
529 Whether to hide the repository in the hgwebdir index. Default is false.
528 ipv6;;
530 ipv6;;
529 Whether to use IPv6. Default is false.
531 Whether to use IPv6. Default is false.
530 name;;
532 name;;
531 Repository name to use in the web interface. Default is current
533 Repository name to use in the web interface. Default is current
532 working directory.
534 working directory.
533 maxchanges;;
535 maxchanges;;
534 Maximum number of changes to list on the changelog. Default is 10.
536 Maximum number of changes to list on the changelog. Default is 10.
535 maxfiles;;
537 maxfiles;;
536 Maximum number of files to list per changeset. Default is 10.
538 Maximum number of files to list per changeset. Default is 10.
537 port;;
539 port;;
538 Port to listen on. Default is 8000.
540 Port to listen on. Default is 8000.
539 push_ssl;;
541 push_ssl;;
540 Whether to require that inbound pushes be transported over SSL to
542 Whether to require that inbound pushes be transported over SSL to
541 prevent password sniffing. Default is true.
543 prevent password sniffing. Default is true.
542 staticurl;;
544 staticurl;;
543 Base URL to use for static files. If unset, static files (e.g.
545 Base URL to use for static files. If unset, static files (e.g.
544 the hgicon.png favicon) will be served by the CGI script itself.
546 the hgicon.png favicon) will be served by the CGI script itself.
545 Use this setting to serve them directly with the HTTP server.
547 Use this setting to serve them directly with the HTTP server.
546 Example: "http://hgserver/static/"
548 Example: "http://hgserver/static/"
547 stripes;;
549 stripes;;
548 How many lines a "zebra stripe" should span in multiline output.
550 How many lines a "zebra stripe" should span in multiline output.
549 Default is 1; set to 0 to disable.
551 Default is 1; set to 0 to disable.
550 style;;
552 style;;
551 Which template map style to use.
553 Which template map style to use.
552 templates;;
554 templates;;
553 Where to find the HTML templates. Default is install path.
555 Where to find the HTML templates. Default is install path.
554 encoding;;
556 encoding;;
555 Character encoding name.
557 Character encoding name.
556 Example: "UTF-8"
558 Example: "UTF-8"
557
559
558
560
559 AUTHOR
561 AUTHOR
560 ------
562 ------
561 Bryan O'Sullivan <bos@serpentine.com>.
563 Bryan O'Sullivan <bos@serpentine.com>.
562
564
563 Mercurial was written by Matt Mackall <mpm@selenic.com>.
565 Mercurial was written by Matt Mackall <mpm@selenic.com>.
564
566
565 SEE ALSO
567 SEE ALSO
566 --------
568 --------
567 hg(1), hgignore(5)
569 hg(1), hgignore(5)
568
570
569 COPYING
571 COPYING
570 -------
572 -------
571 This manual page is copyright 2005 Bryan O'Sullivan.
573 This manual page is copyright 2005 Bryan O'Sullivan.
572 Mercurial is copyright 2005-2007 Matt Mackall.
574 Mercurial is copyright 2005-2007 Matt Mackall.
573 Free use of this software is granted under the terms of the GNU General
575 Free use of this software is granted under the terms of the GNU General
574 Public License (GPL).
576 Public License (GPL).
@@ -1,2303 +1,2188 b''
1 # queue.py - patch queues for mercurial
1 # queue.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.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 '''patch management and development
8 '''patch management and development
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details):
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
33 from mercurial import commands, cmdutil, hg, patch, revlog, util
34 from mercurial import repair
34 import os, sys, re, errno
35 import os, sys, re, errno
35
36
36 commands.norepo += " qclone qversion"
37 commands.norepo += " qclone qversion"
37
38
38 # Patch names looks like unix-file names.
39 # Patch names looks like unix-file names.
39 # They must be joinable with queue directory and result in the patch path.
40 # They must be joinable with queue directory and result in the patch path.
40 normname = util.normpath
41 normname = util.normpath
41
42
42 class statusentry:
43 class statusentry:
43 def __init__(self, rev, name=None):
44 def __init__(self, rev, name=None):
44 if not name:
45 if not name:
45 fields = rev.split(':', 1)
46 fields = rev.split(':', 1)
46 if len(fields) == 2:
47 if len(fields) == 2:
47 self.rev, self.name = fields
48 self.rev, self.name = fields
48 else:
49 else:
49 self.rev, self.name = None, None
50 self.rev, self.name = None, None
50 else:
51 else:
51 self.rev, self.name = rev, name
52 self.rev, self.name = rev, name
52
53
53 def __str__(self):
54 def __str__(self):
54 return self.rev + ':' + self.name
55 return self.rev + ':' + self.name
55
56
56 class queue:
57 class queue:
57 def __init__(self, ui, path, patchdir=None):
58 def __init__(self, ui, path, patchdir=None):
58 self.basepath = path
59 self.basepath = path
59 self.path = patchdir or os.path.join(path, "patches")
60 self.path = patchdir or os.path.join(path, "patches")
60 self.opener = util.opener(self.path)
61 self.opener = util.opener(self.path)
61 self.ui = ui
62 self.ui = ui
62 self.applied = []
63 self.applied = []
63 self.full_series = []
64 self.full_series = []
64 self.applied_dirty = 0
65 self.applied_dirty = 0
65 self.series_dirty = 0
66 self.series_dirty = 0
66 self.series_path = "series"
67 self.series_path = "series"
67 self.status_path = "status"
68 self.status_path = "status"
68 self.guards_path = "guards"
69 self.guards_path = "guards"
69 self.active_guards = None
70 self.active_guards = None
70 self.guards_dirty = False
71 self.guards_dirty = False
71 self._diffopts = None
72 self._diffopts = None
72
73
73 if os.path.exists(self.join(self.series_path)):
74 if os.path.exists(self.join(self.series_path)):
74 self.full_series = self.opener(self.series_path).read().splitlines()
75 self.full_series = self.opener(self.series_path).read().splitlines()
75 self.parse_series()
76 self.parse_series()
76
77
77 if os.path.exists(self.join(self.status_path)):
78 if os.path.exists(self.join(self.status_path)):
78 lines = self.opener(self.status_path).read().splitlines()
79 lines = self.opener(self.status_path).read().splitlines()
79 self.applied = [statusentry(l) for l in lines]
80 self.applied = [statusentry(l) for l in lines]
80
81
81 def diffopts(self):
82 def diffopts(self):
82 if self._diffopts is None:
83 if self._diffopts is None:
83 self._diffopts = patch.diffopts(self.ui)
84 self._diffopts = patch.diffopts(self.ui)
84 return self._diffopts
85 return self._diffopts
85
86
86 def join(self, *p):
87 def join(self, *p):
87 return os.path.join(self.path, *p)
88 return os.path.join(self.path, *p)
88
89
89 def find_series(self, patch):
90 def find_series(self, patch):
90 pre = re.compile("(\s*)([^#]+)")
91 pre = re.compile("(\s*)([^#]+)")
91 index = 0
92 index = 0
92 for l in self.full_series:
93 for l in self.full_series:
93 m = pre.match(l)
94 m = pre.match(l)
94 if m:
95 if m:
95 s = m.group(2)
96 s = m.group(2)
96 s = s.rstrip()
97 s = s.rstrip()
97 if s == patch:
98 if s == patch:
98 return index
99 return index
99 index += 1
100 index += 1
100 return None
101 return None
101
102
102 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
103
104
104 def parse_series(self):
105 def parse_series(self):
105 self.series = []
106 self.series = []
106 self.series_guards = []
107 self.series_guards = []
107 for l in self.full_series:
108 for l in self.full_series:
108 h = l.find('#')
109 h = l.find('#')
109 if h == -1:
110 if h == -1:
110 patch = l
111 patch = l
111 comment = ''
112 comment = ''
112 elif h == 0:
113 elif h == 0:
113 continue
114 continue
114 else:
115 else:
115 patch = l[:h]
116 patch = l[:h]
116 comment = l[h:]
117 comment = l[h:]
117 patch = patch.strip()
118 patch = patch.strip()
118 if patch:
119 if patch:
119 if patch in self.series:
120 if patch in self.series:
120 raise util.Abort(_('%s appears more than once in %s') %
121 raise util.Abort(_('%s appears more than once in %s') %
121 (patch, self.join(self.series_path)))
122 (patch, self.join(self.series_path)))
122 self.series.append(patch)
123 self.series.append(patch)
123 self.series_guards.append(self.guard_re.findall(comment))
124 self.series_guards.append(self.guard_re.findall(comment))
124
125
125 def check_guard(self, guard):
126 def check_guard(self, guard):
126 bad_chars = '# \t\r\n\f'
127 bad_chars = '# \t\r\n\f'
127 first = guard[0]
128 first = guard[0]
128 for c in '-+':
129 for c in '-+':
129 if first == c:
130 if first == c:
130 return (_('guard %r starts with invalid character: %r') %
131 return (_('guard %r starts with invalid character: %r') %
131 (guard, c))
132 (guard, c))
132 for c in bad_chars:
133 for c in bad_chars:
133 if c in guard:
134 if c in guard:
134 return _('invalid character in guard %r: %r') % (guard, c)
135 return _('invalid character in guard %r: %r') % (guard, c)
135
136
136 def set_active(self, guards):
137 def set_active(self, guards):
137 for guard in guards:
138 for guard in guards:
138 bad = self.check_guard(guard)
139 bad = self.check_guard(guard)
139 if bad:
140 if bad:
140 raise util.Abort(bad)
141 raise util.Abort(bad)
141 guards = dict.fromkeys(guards).keys()
142 guards = dict.fromkeys(guards).keys()
142 guards.sort()
143 guards.sort()
143 self.ui.debug('active guards: %s\n' % ' '.join(guards))
144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
144 self.active_guards = guards
145 self.active_guards = guards
145 self.guards_dirty = True
146 self.guards_dirty = True
146
147
147 def active(self):
148 def active(self):
148 if self.active_guards is None:
149 if self.active_guards is None:
149 self.active_guards = []
150 self.active_guards = []
150 try:
151 try:
151 guards = self.opener(self.guards_path).read().split()
152 guards = self.opener(self.guards_path).read().split()
152 except IOError, err:
153 except IOError, err:
153 if err.errno != errno.ENOENT: raise
154 if err.errno != errno.ENOENT: raise
154 guards = []
155 guards = []
155 for i, guard in enumerate(guards):
156 for i, guard in enumerate(guards):
156 bad = self.check_guard(guard)
157 bad = self.check_guard(guard)
157 if bad:
158 if bad:
158 self.ui.warn('%s:%d: %s\n' %
159 self.ui.warn('%s:%d: %s\n' %
159 (self.join(self.guards_path), i + 1, bad))
160 (self.join(self.guards_path), i + 1, bad))
160 else:
161 else:
161 self.active_guards.append(guard)
162 self.active_guards.append(guard)
162 return self.active_guards
163 return self.active_guards
163
164
164 def set_guards(self, idx, guards):
165 def set_guards(self, idx, guards):
165 for g in guards:
166 for g in guards:
166 if len(g) < 2:
167 if len(g) < 2:
167 raise util.Abort(_('guard %r too short') % g)
168 raise util.Abort(_('guard %r too short') % g)
168 if g[0] not in '-+':
169 if g[0] not in '-+':
169 raise util.Abort(_('guard %r starts with invalid char') % g)
170 raise util.Abort(_('guard %r starts with invalid char') % g)
170 bad = self.check_guard(g[1:])
171 bad = self.check_guard(g[1:])
171 if bad:
172 if bad:
172 raise util.Abort(bad)
173 raise util.Abort(bad)
173 drop = self.guard_re.sub('', self.full_series[idx])
174 drop = self.guard_re.sub('', self.full_series[idx])
174 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
175 self.parse_series()
176 self.parse_series()
176 self.series_dirty = True
177 self.series_dirty = True
177
178
178 def pushable(self, idx):
179 def pushable(self, idx):
179 if isinstance(idx, str):
180 if isinstance(idx, str):
180 idx = self.series.index(idx)
181 idx = self.series.index(idx)
181 patchguards = self.series_guards[idx]
182 patchguards = self.series_guards[idx]
182 if not patchguards:
183 if not patchguards:
183 return True, None
184 return True, None
184 default = False
185 default = False
185 guards = self.active()
186 guards = self.active()
186 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
187 if exactneg:
188 if exactneg:
188 return False, exactneg[0]
189 return False, exactneg[0]
189 pos = [g for g in patchguards if g[0] == '+']
190 pos = [g for g in patchguards if g[0] == '+']
190 exactpos = [g for g in pos if g[1:] in guards]
191 exactpos = [g for g in pos if g[1:] in guards]
191 if pos:
192 if pos:
192 if exactpos:
193 if exactpos:
193 return True, exactpos[0]
194 return True, exactpos[0]
194 return False, pos
195 return False, pos
195 return True, ''
196 return True, ''
196
197
197 def explain_pushable(self, idx, all_patches=False):
198 def explain_pushable(self, idx, all_patches=False):
198 write = all_patches and self.ui.write or self.ui.warn
199 write = all_patches and self.ui.write or self.ui.warn
199 if all_patches or self.ui.verbose:
200 if all_patches or self.ui.verbose:
200 if isinstance(idx, str):
201 if isinstance(idx, str):
201 idx = self.series.index(idx)
202 idx = self.series.index(idx)
202 pushable, why = self.pushable(idx)
203 pushable, why = self.pushable(idx)
203 if all_patches and pushable:
204 if all_patches and pushable:
204 if why is None:
205 if why is None:
205 write(_('allowing %s - no guards in effect\n') %
206 write(_('allowing %s - no guards in effect\n') %
206 self.series[idx])
207 self.series[idx])
207 else:
208 else:
208 if not why:
209 if not why:
209 write(_('allowing %s - no matching negative guards\n') %
210 write(_('allowing %s - no matching negative guards\n') %
210 self.series[idx])
211 self.series[idx])
211 else:
212 else:
212 write(_('allowing %s - guarded by %r\n') %
213 write(_('allowing %s - guarded by %r\n') %
213 (self.series[idx], why))
214 (self.series[idx], why))
214 if not pushable:
215 if not pushable:
215 if why:
216 if why:
216 write(_('skipping %s - guarded by %r\n') %
217 write(_('skipping %s - guarded by %r\n') %
217 (self.series[idx], why))
218 (self.series[idx], why))
218 else:
219 else:
219 write(_('skipping %s - no matching guards\n') %
220 write(_('skipping %s - no matching guards\n') %
220 self.series[idx])
221 self.series[idx])
221
222
222 def save_dirty(self):
223 def save_dirty(self):
223 def write_list(items, path):
224 def write_list(items, path):
224 fp = self.opener(path, 'w')
225 fp = self.opener(path, 'w')
225 for i in items:
226 for i in items:
226 print >> fp, i
227 print >> fp, i
227 fp.close()
228 fp.close()
228 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
229 if self.series_dirty: write_list(self.full_series, self.series_path)
230 if self.series_dirty: write_list(self.full_series, self.series_path)
230 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
231
232
232 def readheaders(self, patch):
233 def readheaders(self, patch):
233 def eatdiff(lines):
234 def eatdiff(lines):
234 while lines:
235 while lines:
235 l = lines[-1]
236 l = lines[-1]
236 if (l.startswith("diff -") or
237 if (l.startswith("diff -") or
237 l.startswith("Index:") or
238 l.startswith("Index:") or
238 l.startswith("===========")):
239 l.startswith("===========")):
239 del lines[-1]
240 del lines[-1]
240 else:
241 else:
241 break
242 break
242 def eatempty(lines):
243 def eatempty(lines):
243 while lines:
244 while lines:
244 l = lines[-1]
245 l = lines[-1]
245 if re.match('\s*$', l):
246 if re.match('\s*$', l):
246 del lines[-1]
247 del lines[-1]
247 else:
248 else:
248 break
249 break
249
250
250 pf = self.join(patch)
251 pf = self.join(patch)
251 message = []
252 message = []
252 comments = []
253 comments = []
253 user = None
254 user = None
254 date = None
255 date = None
255 format = None
256 format = None
256 subject = None
257 subject = None
257 diffstart = 0
258 diffstart = 0
258
259
259 for line in file(pf):
260 for line in file(pf):
260 line = line.rstrip()
261 line = line.rstrip()
261 if line.startswith('diff --git'):
262 if line.startswith('diff --git'):
262 diffstart = 2
263 diffstart = 2
263 break
264 break
264 if diffstart:
265 if diffstart:
265 if line.startswith('+++ '):
266 if line.startswith('+++ '):
266 diffstart = 2
267 diffstart = 2
267 break
268 break
268 if line.startswith("--- "):
269 if line.startswith("--- "):
269 diffstart = 1
270 diffstart = 1
270 continue
271 continue
271 elif format == "hgpatch":
272 elif format == "hgpatch":
272 # parse values when importing the result of an hg export
273 # parse values when importing the result of an hg export
273 if line.startswith("# User "):
274 if line.startswith("# User "):
274 user = line[7:]
275 user = line[7:]
275 elif line.startswith("# Date "):
276 elif line.startswith("# Date "):
276 date = line[7:]
277 date = line[7:]
277 elif not line.startswith("# ") and line:
278 elif not line.startswith("# ") and line:
278 message.append(line)
279 message.append(line)
279 format = None
280 format = None
280 elif line == '# HG changeset patch':
281 elif line == '# HG changeset patch':
281 format = "hgpatch"
282 format = "hgpatch"
282 elif (format != "tagdone" and (line.startswith("Subject: ") or
283 elif (format != "tagdone" and (line.startswith("Subject: ") or
283 line.startswith("subject: "))):
284 line.startswith("subject: "))):
284 subject = line[9:]
285 subject = line[9:]
285 format = "tag"
286 format = "tag"
286 elif (format != "tagdone" and (line.startswith("From: ") or
287 elif (format != "tagdone" and (line.startswith("From: ") or
287 line.startswith("from: "))):
288 line.startswith("from: "))):
288 user = line[6:]
289 user = line[6:]
289 format = "tag"
290 format = "tag"
290 elif format == "tag" and line == "":
291 elif format == "tag" and line == "":
291 # when looking for tags (subject: from: etc) they
292 # when looking for tags (subject: from: etc) they
292 # end once you find a blank line in the source
293 # end once you find a blank line in the source
293 format = "tagdone"
294 format = "tagdone"
294 elif message or line:
295 elif message or line:
295 message.append(line)
296 message.append(line)
296 comments.append(line)
297 comments.append(line)
297
298
298 eatdiff(message)
299 eatdiff(message)
299 eatdiff(comments)
300 eatdiff(comments)
300 eatempty(message)
301 eatempty(message)
301 eatempty(comments)
302 eatempty(comments)
302
303
303 # make sure message isn't empty
304 # make sure message isn't empty
304 if format and format.startswith("tag") and subject:
305 if format and format.startswith("tag") and subject:
305 message.insert(0, "")
306 message.insert(0, "")
306 message.insert(0, subject)
307 message.insert(0, subject)
307 return (message, comments, user, date, diffstart > 1)
308 return (message, comments, user, date, diffstart > 1)
308
309
309 def removeundo(self, repo):
310 def removeundo(self, repo):
310 undo = repo.sjoin('undo')
311 undo = repo.sjoin('undo')
311 if not os.path.exists(undo):
312 if not os.path.exists(undo):
312 return
313 return
313 try:
314 try:
314 os.unlink(undo)
315 os.unlink(undo)
315 except OSError, inst:
316 except OSError, inst:
316 self.ui.warn('error removing undo: %s\n' % str(inst))
317 self.ui.warn('error removing undo: %s\n' % str(inst))
317
318
318 def printdiff(self, repo, node1, node2=None, files=None,
319 def printdiff(self, repo, node1, node2=None, files=None,
319 fp=None, changes=None, opts={}):
320 fp=None, changes=None, opts={}):
320 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
321
322
322 patch.diff(repo, node1, node2, fns, match=matchfn,
323 patch.diff(repo, node1, node2, fns, match=matchfn,
323 fp=fp, changes=changes, opts=self.diffopts())
324 fp=fp, changes=changes, opts=self.diffopts())
324
325
325 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
326 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
326 # first try just applying the patch
327 # first try just applying the patch
327 (err, n) = self.apply(repo, [ patch ], update_status=False,
328 (err, n) = self.apply(repo, [ patch ], update_status=False,
328 strict=True, merge=rev, wlock=wlock)
329 strict=True, merge=rev, wlock=wlock)
329
330
330 if err == 0:
331 if err == 0:
331 return (err, n)
332 return (err, n)
332
333
333 if n is None:
334 if n is None:
334 raise util.Abort(_("apply failed for patch %s") % patch)
335 raise util.Abort(_("apply failed for patch %s") % patch)
335
336
336 self.ui.warn("patch didn't work out, merging %s\n" % patch)
337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
337
338
338 # apply failed, strip away that rev and merge.
339 # apply failed, strip away that rev and merge.
339 hg.clean(repo, head, wlock=wlock)
340 hg.clean(repo, head, wlock=wlock)
340 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
341 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
341
342
342 ctx = repo.changectx(rev)
343 ctx = repo.changectx(rev)
343 ret = hg.merge(repo, rev, wlock=wlock)
344 ret = hg.merge(repo, rev, wlock=wlock)
344 if ret:
345 if ret:
345 raise util.Abort(_("update returned %d") % ret)
346 raise util.Abort(_("update returned %d") % ret)
346 n = repo.commit(None, ctx.description(), ctx.user(),
347 n = repo.commit(None, ctx.description(), ctx.user(),
347 force=1, wlock=wlock)
348 force=1, wlock=wlock)
348 if n == None:
349 if n == None:
349 raise util.Abort(_("repo commit failed"))
350 raise util.Abort(_("repo commit failed"))
350 try:
351 try:
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 except:
353 except:
353 raise util.Abort(_("unable to read %s") % patch)
354 raise util.Abort(_("unable to read %s") % patch)
354
355
355 patchf = self.opener(patch, "w")
356 patchf = self.opener(patch, "w")
356 if comments:
357 if comments:
357 comments = "\n".join(comments) + '\n\n'
358 comments = "\n".join(comments) + '\n\n'
358 patchf.write(comments)
359 patchf.write(comments)
359 self.printdiff(repo, head, n, fp=patchf)
360 self.printdiff(repo, head, n, fp=patchf)
360 patchf.close()
361 patchf.close()
361 self.removeundo(repo)
362 self.removeundo(repo)
362 return (0, n)
363 return (0, n)
363
364
364 def qparents(self, repo, rev=None):
365 def qparents(self, repo, rev=None):
365 if rev is None:
366 if rev is None:
366 (p1, p2) = repo.dirstate.parents()
367 (p1, p2) = repo.dirstate.parents()
367 if p2 == revlog.nullid:
368 if p2 == revlog.nullid:
368 return p1
369 return p1
369 if len(self.applied) == 0:
370 if len(self.applied) == 0:
370 return None
371 return None
371 return revlog.bin(self.applied[-1].rev)
372 return revlog.bin(self.applied[-1].rev)
372 pp = repo.changelog.parents(rev)
373 pp = repo.changelog.parents(rev)
373 if pp[1] != revlog.nullid:
374 if pp[1] != revlog.nullid:
374 arevs = [ x.rev for x in self.applied ]
375 arevs = [ x.rev for x in self.applied ]
375 p0 = revlog.hex(pp[0])
376 p0 = revlog.hex(pp[0])
376 p1 = revlog.hex(pp[1])
377 p1 = revlog.hex(pp[1])
377 if p0 in arevs:
378 if p0 in arevs:
378 return pp[0]
379 return pp[0]
379 if p1 in arevs:
380 if p1 in arevs:
380 return pp[1]
381 return pp[1]
381 return pp[0]
382 return pp[0]
382
383
383 def mergepatch(self, repo, mergeq, series, wlock):
384 def mergepatch(self, repo, mergeq, series, wlock):
384 if len(self.applied) == 0:
385 if len(self.applied) == 0:
385 # each of the patches merged in will have two parents. This
386 # each of the patches merged in will have two parents. This
386 # can confuse the qrefresh, qdiff, and strip code because it
387 # can confuse the qrefresh, qdiff, and strip code because it
387 # needs to know which parent is actually in the patch queue.
388 # needs to know which parent is actually in the patch queue.
388 # so, we insert a merge marker with only one parent. This way
389 # so, we insert a merge marker with only one parent. This way
389 # the first patch in the queue is never a merge patch
390 # the first patch in the queue is never a merge patch
390 #
391 #
391 pname = ".hg.patches.merge.marker"
392 pname = ".hg.patches.merge.marker"
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
393 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
393 wlock=wlock)
394 wlock=wlock)
394 self.removeundo(repo)
395 self.removeundo(repo)
395 self.applied.append(statusentry(revlog.hex(n), pname))
396 self.applied.append(statusentry(revlog.hex(n), pname))
396 self.applied_dirty = 1
397 self.applied_dirty = 1
397
398
398 head = self.qparents(repo)
399 head = self.qparents(repo)
399
400
400 for patch in series:
401 for patch in series:
401 patch = mergeq.lookup(patch, strict=True)
402 patch = mergeq.lookup(patch, strict=True)
402 if not patch:
403 if not patch:
403 self.ui.warn("patch %s does not exist\n" % patch)
404 self.ui.warn("patch %s does not exist\n" % patch)
404 return (1, None)
405 return (1, None)
405 pushable, reason = self.pushable(patch)
406 pushable, reason = self.pushable(patch)
406 if not pushable:
407 if not pushable:
407 self.explain_pushable(patch, all_patches=True)
408 self.explain_pushable(patch, all_patches=True)
408 continue
409 continue
409 info = mergeq.isapplied(patch)
410 info = mergeq.isapplied(patch)
410 if not info:
411 if not info:
411 self.ui.warn("patch %s is not applied\n" % patch)
412 self.ui.warn("patch %s is not applied\n" % patch)
412 return (1, None)
413 return (1, None)
413 rev = revlog.bin(info[1])
414 rev = revlog.bin(info[1])
414 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
415 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
415 if head:
416 if head:
416 self.applied.append(statusentry(revlog.hex(head), patch))
417 self.applied.append(statusentry(revlog.hex(head), patch))
417 self.applied_dirty = 1
418 self.applied_dirty = 1
418 if err:
419 if err:
419 return (err, head)
420 return (err, head)
420 self.save_dirty()
421 self.save_dirty()
421 return (0, head)
422 return (0, head)
422
423
423 def patch(self, repo, patchfile):
424 def patch(self, repo, patchfile):
424 '''Apply patchfile to the working directory.
425 '''Apply patchfile to the working directory.
425 patchfile: file name of patch'''
426 patchfile: file name of patch'''
426 files = {}
427 files = {}
427 try:
428 try:
428 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
429 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
429 files=files)
430 files=files)
430 except Exception, inst:
431 except Exception, inst:
431 self.ui.note(str(inst) + '\n')
432 self.ui.note(str(inst) + '\n')
432 if not self.ui.verbose:
433 if not self.ui.verbose:
433 self.ui.warn("patch failed, unable to continue (try -v)\n")
434 self.ui.warn("patch failed, unable to continue (try -v)\n")
434 return (False, files, False)
435 return (False, files, False)
435
436
436 return (True, files, fuzz)
437 return (True, files, fuzz)
437
438
438 def apply(self, repo, series, list=False, update_status=True,
439 def apply(self, repo, series, list=False, update_status=True,
439 strict=False, patchdir=None, merge=None, wlock=None,
440 strict=False, patchdir=None, merge=None, wlock=None,
440 all_files={}):
441 all_files={}):
441 if not wlock:
442 if not wlock:
442 wlock = repo.wlock()
443 wlock = repo.wlock()
443 lock = repo.lock()
444 lock = repo.lock()
444 tr = repo.transaction()
445 tr = repo.transaction()
445 try:
446 try:
446 ret = self._apply(tr, repo, series, list, update_status,
447 ret = self._apply(tr, repo, series, list, update_status,
447 strict, patchdir, merge, wlock,
448 strict, patchdir, merge, wlock,
448 lock=lock, all_files=all_files)
449 lock=lock, all_files=all_files)
449 tr.close()
450 tr.close()
450 self.save_dirty()
451 self.save_dirty()
451 return ret
452 return ret
452 except:
453 except:
453 try:
454 try:
454 tr.abort()
455 tr.abort()
455 finally:
456 finally:
456 repo.invalidate()
457 repo.invalidate()
457 repo.dirstate.invalidate()
458 repo.dirstate.invalidate()
458 raise
459 raise
459
460
460 def _apply(self, tr, repo, series, list=False, update_status=True,
461 def _apply(self, tr, repo, series, list=False, update_status=True,
461 strict=False, patchdir=None, merge=None, wlock=None,
462 strict=False, patchdir=None, merge=None, wlock=None,
462 lock=None, all_files={}):
463 lock=None, all_files={}):
463 # TODO unify with commands.py
464 # TODO unify with commands.py
464 if not patchdir:
465 if not patchdir:
465 patchdir = self.path
466 patchdir = self.path
466 err = 0
467 err = 0
467 n = None
468 n = None
468 for patchname in series:
469 for patchname in series:
469 pushable, reason = self.pushable(patchname)
470 pushable, reason = self.pushable(patchname)
470 if not pushable:
471 if not pushable:
471 self.explain_pushable(patchname, all_patches=True)
472 self.explain_pushable(patchname, all_patches=True)
472 continue
473 continue
473 self.ui.warn("applying %s\n" % patchname)
474 self.ui.warn("applying %s\n" % patchname)
474 pf = os.path.join(patchdir, patchname)
475 pf = os.path.join(patchdir, patchname)
475
476
476 try:
477 try:
477 message, comments, user, date, patchfound = self.readheaders(patchname)
478 message, comments, user, date, patchfound = self.readheaders(patchname)
478 except:
479 except:
479 self.ui.warn("Unable to read %s\n" % patchname)
480 self.ui.warn("Unable to read %s\n" % patchname)
480 err = 1
481 err = 1
481 break
482 break
482
483
483 if not message:
484 if not message:
484 message = "imported patch %s\n" % patchname
485 message = "imported patch %s\n" % patchname
485 else:
486 else:
486 if list:
487 if list:
487 message.append("\nimported patch %s" % patchname)
488 message.append("\nimported patch %s" % patchname)
488 message = '\n'.join(message)
489 message = '\n'.join(message)
489
490
490 (patcherr, files, fuzz) = self.patch(repo, pf)
491 (patcherr, files, fuzz) = self.patch(repo, pf)
491 all_files.update(files)
492 all_files.update(files)
492 patcherr = not patcherr
493 patcherr = not patcherr
493
494
494 if merge and files:
495 if merge and files:
495 # Mark as removed/merged and update dirstate parent info
496 # Mark as removed/merged and update dirstate parent info
496 removed = []
497 removed = []
497 merged = []
498 merged = []
498 for f in files:
499 for f in files:
499 if os.path.exists(repo.dirstate.wjoin(f)):
500 if os.path.exists(repo.dirstate.wjoin(f)):
500 merged.append(f)
501 merged.append(f)
501 else:
502 else:
502 removed.append(f)
503 removed.append(f)
503 repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r')
504 repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r')
504 repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm')
505 repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm')
505 p1, p2 = repo.dirstate.parents()
506 p1, p2 = repo.dirstate.parents()
506 repo.dirstate.setparents(p1, merge)
507 repo.dirstate.setparents(p1, merge)
507 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
508 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
508 n = repo.commit(files, message, user, date, force=1, lock=lock,
509 n = repo.commit(files, message, user, date, force=1, lock=lock,
509 wlock=wlock)
510 wlock=wlock)
510
511
511 if n == None:
512 if n == None:
512 raise util.Abort(_("repo commit failed"))
513 raise util.Abort(_("repo commit failed"))
513
514
514 if update_status:
515 if update_status:
515 self.applied.append(statusentry(revlog.hex(n), patchname))
516 self.applied.append(statusentry(revlog.hex(n), patchname))
516
517
517 if patcherr:
518 if patcherr:
518 if not patchfound:
519 if not patchfound:
519 self.ui.warn("patch %s is empty\n" % patchname)
520 self.ui.warn("patch %s is empty\n" % patchname)
520 err = 0
521 err = 0
521 else:
522 else:
522 self.ui.warn("patch failed, rejects left in working dir\n")
523 self.ui.warn("patch failed, rejects left in working dir\n")
523 err = 1
524 err = 1
524 break
525 break
525
526
526 if fuzz and strict:
527 if fuzz and strict:
527 self.ui.warn("fuzz found when applying patch, stopping\n")
528 self.ui.warn("fuzz found when applying patch, stopping\n")
528 err = 1
529 err = 1
529 break
530 break
530 self.removeundo(repo)
531 self.removeundo(repo)
531 return (err, n)
532 return (err, n)
532
533
533 def delete(self, repo, patches, opts):
534 def delete(self, repo, patches, opts):
534 realpatches = []
535 realpatches = []
535 for patch in patches:
536 for patch in patches:
536 patch = self.lookup(patch, strict=True)
537 patch = self.lookup(patch, strict=True)
537 info = self.isapplied(patch)
538 info = self.isapplied(patch)
538 if info:
539 if info:
539 raise util.Abort(_("cannot delete applied patch %s") % patch)
540 raise util.Abort(_("cannot delete applied patch %s") % patch)
540 if patch not in self.series:
541 if patch not in self.series:
541 raise util.Abort(_("patch %s not in series file") % patch)
542 raise util.Abort(_("patch %s not in series file") % patch)
542 realpatches.append(patch)
543 realpatches.append(patch)
543
544
544 appliedbase = 0
545 appliedbase = 0
545 if opts.get('rev'):
546 if opts.get('rev'):
546 if not self.applied:
547 if not self.applied:
547 raise util.Abort(_('no patches applied'))
548 raise util.Abort(_('no patches applied'))
548 revs = cmdutil.revrange(repo, opts['rev'])
549 revs = cmdutil.revrange(repo, opts['rev'])
549 if len(revs) > 1 and revs[0] > revs[1]:
550 if len(revs) > 1 and revs[0] > revs[1]:
550 revs.reverse()
551 revs.reverse()
551 for rev in revs:
552 for rev in revs:
552 if appliedbase >= len(self.applied):
553 if appliedbase >= len(self.applied):
553 raise util.Abort(_("revision %d is not managed") % rev)
554 raise util.Abort(_("revision %d is not managed") % rev)
554
555
555 base = revlog.bin(self.applied[appliedbase].rev)
556 base = revlog.bin(self.applied[appliedbase].rev)
556 node = repo.changelog.node(rev)
557 node = repo.changelog.node(rev)
557 if node != base:
558 if node != base:
558 raise util.Abort(_("cannot delete revision %d above "
559 raise util.Abort(_("cannot delete revision %d above "
559 "applied patches") % rev)
560 "applied patches") % rev)
560 realpatches.append(self.applied[appliedbase].name)
561 realpatches.append(self.applied[appliedbase].name)
561 appliedbase += 1
562 appliedbase += 1
562
563
563 if not opts.get('keep'):
564 if not opts.get('keep'):
564 r = self.qrepo()
565 r = self.qrepo()
565 if r:
566 if r:
566 r.remove(realpatches, True)
567 r.remove(realpatches, True)
567 else:
568 else:
568 for p in realpatches:
569 for p in realpatches:
569 os.unlink(self.join(p))
570 os.unlink(self.join(p))
570
571
571 if appliedbase:
572 if appliedbase:
572 del self.applied[:appliedbase]
573 del self.applied[:appliedbase]
573 self.applied_dirty = 1
574 self.applied_dirty = 1
574 indices = [self.find_series(p) for p in realpatches]
575 indices = [self.find_series(p) for p in realpatches]
575 indices.sort()
576 indices.sort()
576 for i in indices[-1::-1]:
577 for i in indices[-1::-1]:
577 del self.full_series[i]
578 del self.full_series[i]
578 self.parse_series()
579 self.parse_series()
579 self.series_dirty = 1
580 self.series_dirty = 1
580
581
581 def check_toppatch(self, repo):
582 def check_toppatch(self, repo):
582 if len(self.applied) > 0:
583 if len(self.applied) > 0:
583 top = revlog.bin(self.applied[-1].rev)
584 top = revlog.bin(self.applied[-1].rev)
584 pp = repo.dirstate.parents()
585 pp = repo.dirstate.parents()
585 if top not in pp:
586 if top not in pp:
586 raise util.Abort(_("queue top not at same revision as working directory"))
587 raise util.Abort(_("queue top not at same revision as working directory"))
587 return top
588 return top
588 return None
589 return None
589 def check_localchanges(self, repo, force=False, refresh=True):
590 def check_localchanges(self, repo, force=False, refresh=True):
590 m, a, r, d = repo.status()[:4]
591 m, a, r, d = repo.status()[:4]
591 if m or a or r or d:
592 if m or a or r or d:
592 if not force:
593 if not force:
593 if refresh:
594 if refresh:
594 raise util.Abort(_("local changes found, refresh first"))
595 raise util.Abort(_("local changes found, refresh first"))
595 else:
596 else:
596 raise util.Abort(_("local changes found"))
597 raise util.Abort(_("local changes found"))
597 return m, a, r, d
598 return m, a, r, d
598 def new(self, repo, patch, msg=None, force=None):
599 def new(self, repo, patch, msg=None, force=None):
599 if os.path.exists(self.join(patch)):
600 if os.path.exists(self.join(patch)):
600 raise util.Abort(_('patch "%s" already exists') % patch)
601 raise util.Abort(_('patch "%s" already exists') % patch)
601 m, a, r, d = self.check_localchanges(repo, force)
602 m, a, r, d = self.check_localchanges(repo, force)
602 commitfiles = m + a + r
603 commitfiles = m + a + r
603 self.check_toppatch(repo)
604 self.check_toppatch(repo)
604 wlock = repo.wlock()
605 wlock = repo.wlock()
605 insert = self.full_series_end()
606 insert = self.full_series_end()
606 if msg:
607 if msg:
607 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
608 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
608 wlock=wlock)
609 wlock=wlock)
609 else:
610 else:
610 n = repo.commit(commitfiles,
611 n = repo.commit(commitfiles,
611 "New patch: %s" % patch, force=True, wlock=wlock)
612 "New patch: %s" % patch, force=True, wlock=wlock)
612 if n == None:
613 if n == None:
613 raise util.Abort(_("repo commit failed"))
614 raise util.Abort(_("repo commit failed"))
614 self.full_series[insert:insert] = [patch]
615 self.full_series[insert:insert] = [patch]
615 self.applied.append(statusentry(revlog.hex(n), patch))
616 self.applied.append(statusentry(revlog.hex(n), patch))
616 self.parse_series()
617 self.parse_series()
617 self.series_dirty = 1
618 self.series_dirty = 1
618 self.applied_dirty = 1
619 self.applied_dirty = 1
619 p = self.opener(patch, "w")
620 p = self.opener(patch, "w")
620 if msg:
621 if msg:
621 msg = msg + "\n"
622 msg = msg + "\n"
622 p.write(msg)
623 p.write(msg)
623 p.close()
624 p.close()
624 wlock = None
625 wlock = None
625 r = self.qrepo()
626 r = self.qrepo()
626 if r: r.add([patch])
627 if r: r.add([patch])
627 if commitfiles:
628 if commitfiles:
628 self.refresh(repo, short=True)
629 self.refresh(repo, short=True)
629 self.removeundo(repo)
630 self.removeundo(repo)
630
631
631 def strip(self, repo, rev, update=True, backup="all", wlock=None):
632 def strip(self, repo, rev, update=True, backup="all", wlock=None):
632 def limitheads(chlog, stop):
633 """return the list of all nodes that have no children"""
634 p = {}
635 h = []
636 stoprev = 0
637 if stop in chlog.nodemap:
638 stoprev = chlog.rev(stop)
639
640 for r in xrange(chlog.count() - 1, -1, -1):
641 n = chlog.node(r)
642 if n not in p:
643 h.append(n)
644 if n == stop:
645 break
646 if r < stoprev:
647 break
648 for pn in chlog.parents(n):
649 p[pn] = 1
650 return h
651
652 def bundle(cg):
653 backupdir = repo.join("strip-backup")
654 if not os.path.isdir(backupdir):
655 os.mkdir(backupdir)
656 name = os.path.join(backupdir, "%s" % revlog.short(rev))
657 name = savename(name)
658 self.ui.warn("saving bundle to %s\n" % name)
659 return changegroup.writebundle(cg, name, "HG10BZ")
660
661 def stripall(revnum):
662 mm = repo.changectx(rev).manifest()
663 seen = {}
664
665 for x in xrange(revnum, repo.changelog.count()):
666 for f in repo.changectx(x).files():
667 if f in seen:
668 continue
669 seen[f] = 1
670 if f in mm:
671 filerev = mm[f]
672 else:
673 filerev = 0
674 seen[f] = filerev
675 # we go in two steps here so the strip loop happens in a
676 # sensible order. When stripping many files, this helps keep
677 # our disk access patterns under control.
678 seen_list = seen.keys()
679 seen_list.sort()
680 for f in seen_list:
681 ff = repo.file(f)
682 filerev = seen[f]
683 if filerev != 0:
684 if filerev in ff.nodemap:
685 filerev = ff.rev(filerev)
686 else:
687 filerev = 0
688 ff.strip(filerev, revnum)
689
690 if not wlock:
633 if not wlock:
691 wlock = repo.wlock()
634 wlock = repo.wlock()
692 lock = repo.lock()
635 lock = repo.lock()
693 chlog = repo.changelog
694 # TODO delete the undo files, and handle undo of merge sets
695 pp = chlog.parents(rev)
696 revnum = chlog.rev(rev)
697
636
698 if update:
637 if update:
699 self.check_localchanges(repo, refresh=False)
638 self.check_localchanges(repo, refresh=False)
700 urev = self.qparents(repo, rev)
639 urev = self.qparents(repo, rev)
701 hg.clean(repo, urev, wlock=wlock)
640 hg.clean(repo, urev, wlock=wlock)
702 repo.dirstate.write()
641 repo.dirstate.write()
703
642
704 # save is a list of all the branches we are truncating away
705 # that we actually want to keep. changegroup will be used
706 # to preserve them and add them back after the truncate
707 saveheads = []
708 savebases = {}
709
710 heads = limitheads(chlog, rev)
711 seen = {}
712
713 # search through all the heads, finding those where the revision
714 # we want to strip away is an ancestor. Also look for merges
715 # that might be turned into new heads by the strip.
716 while heads:
717 h = heads.pop()
718 n = h
719 while True:
720 seen[n] = 1
721 pp = chlog.parents(n)
722 if pp[1] != revlog.nullid:
723 for p in pp:
724 if chlog.rev(p) > revnum and p not in seen:
725 heads.append(p)
726 if pp[0] == revlog.nullid:
727 break
728 if chlog.rev(pp[0]) < revnum:
729 break
730 n = pp[0]
731 if n == rev:
732 break
733 r = chlog.reachable(h, rev)
734 if rev not in r:
735 saveheads.append(h)
736 for x in r:
737 if chlog.rev(x) > revnum:
738 savebases[x] = 1
739
740 # create a changegroup for all the branches we need to keep
741 if backup == "all":
742 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
743 bundle(backupch)
744 if saveheads:
745 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
746 chgrpfile = bundle(backupch)
747
748 stripall(revnum)
749
750 change = chlog.read(rev)
751 chlog.strip(revnum, revnum)
752 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
753 self.removeundo(repo)
643 self.removeundo(repo)
754 if saveheads:
644 repair.strip(self.ui, repo, rev, backup)
755 self.ui.status("adding branch\n")
756 commands.unbundle(self.ui, repo, "file:%s" % chgrpfile,
757 update=False)
758 if backup != "strip":
759 os.unlink(chgrpfile)
760
645
761 def isapplied(self, patch):
646 def isapplied(self, patch):
762 """returns (index, rev, patch)"""
647 """returns (index, rev, patch)"""
763 for i in xrange(len(self.applied)):
648 for i in xrange(len(self.applied)):
764 a = self.applied[i]
649 a = self.applied[i]
765 if a.name == patch:
650 if a.name == patch:
766 return (i, a.rev, a.name)
651 return (i, a.rev, a.name)
767 return None
652 return None
768
653
769 # if the exact patch name does not exist, we try a few
654 # if the exact patch name does not exist, we try a few
770 # variations. If strict is passed, we try only #1
655 # variations. If strict is passed, we try only #1
771 #
656 #
772 # 1) a number to indicate an offset in the series file
657 # 1) a number to indicate an offset in the series file
773 # 2) a unique substring of the patch name was given
658 # 2) a unique substring of the patch name was given
774 # 3) patchname[-+]num to indicate an offset in the series file
659 # 3) patchname[-+]num to indicate an offset in the series file
775 def lookup(self, patch, strict=False):
660 def lookup(self, patch, strict=False):
776 patch = patch and str(patch)
661 patch = patch and str(patch)
777
662
778 def partial_name(s):
663 def partial_name(s):
779 if s in self.series:
664 if s in self.series:
780 return s
665 return s
781 matches = [x for x in self.series if s in x]
666 matches = [x for x in self.series if s in x]
782 if len(matches) > 1:
667 if len(matches) > 1:
783 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
668 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
784 for m in matches:
669 for m in matches:
785 self.ui.warn(' %s\n' % m)
670 self.ui.warn(' %s\n' % m)
786 return None
671 return None
787 if matches:
672 if matches:
788 return matches[0]
673 return matches[0]
789 if len(self.series) > 0 and len(self.applied) > 0:
674 if len(self.series) > 0 and len(self.applied) > 0:
790 if s == 'qtip':
675 if s == 'qtip':
791 return self.series[self.series_end(True)-1]
676 return self.series[self.series_end(True)-1]
792 if s == 'qbase':
677 if s == 'qbase':
793 return self.series[0]
678 return self.series[0]
794 return None
679 return None
795 if patch == None:
680 if patch == None:
796 return None
681 return None
797
682
798 # we don't want to return a partial match until we make
683 # we don't want to return a partial match until we make
799 # sure the file name passed in does not exist (checked below)
684 # sure the file name passed in does not exist (checked below)
800 res = partial_name(patch)
685 res = partial_name(patch)
801 if res and res == patch:
686 if res and res == patch:
802 return res
687 return res
803
688
804 if not os.path.isfile(self.join(patch)):
689 if not os.path.isfile(self.join(patch)):
805 try:
690 try:
806 sno = int(patch)
691 sno = int(patch)
807 except(ValueError, OverflowError):
692 except(ValueError, OverflowError):
808 pass
693 pass
809 else:
694 else:
810 if sno < len(self.series):
695 if sno < len(self.series):
811 return self.series[sno]
696 return self.series[sno]
812 if not strict:
697 if not strict:
813 # return any partial match made above
698 # return any partial match made above
814 if res:
699 if res:
815 return res
700 return res
816 minus = patch.rfind('-')
701 minus = patch.rfind('-')
817 if minus >= 0:
702 if minus >= 0:
818 res = partial_name(patch[:minus])
703 res = partial_name(patch[:minus])
819 if res:
704 if res:
820 i = self.series.index(res)
705 i = self.series.index(res)
821 try:
706 try:
822 off = int(patch[minus+1:] or 1)
707 off = int(patch[minus+1:] or 1)
823 except(ValueError, OverflowError):
708 except(ValueError, OverflowError):
824 pass
709 pass
825 else:
710 else:
826 if i - off >= 0:
711 if i - off >= 0:
827 return self.series[i - off]
712 return self.series[i - off]
828 plus = patch.rfind('+')
713 plus = patch.rfind('+')
829 if plus >= 0:
714 if plus >= 0:
830 res = partial_name(patch[:plus])
715 res = partial_name(patch[:plus])
831 if res:
716 if res:
832 i = self.series.index(res)
717 i = self.series.index(res)
833 try:
718 try:
834 off = int(patch[plus+1:] or 1)
719 off = int(patch[plus+1:] or 1)
835 except(ValueError, OverflowError):
720 except(ValueError, OverflowError):
836 pass
721 pass
837 else:
722 else:
838 if i + off < len(self.series):
723 if i + off < len(self.series):
839 return self.series[i + off]
724 return self.series[i + off]
840 raise util.Abort(_("patch %s not in series") % patch)
725 raise util.Abort(_("patch %s not in series") % patch)
841
726
842 def push(self, repo, patch=None, force=False, list=False,
727 def push(self, repo, patch=None, force=False, list=False,
843 mergeq=None, wlock=None):
728 mergeq=None, wlock=None):
844 if not wlock:
729 if not wlock:
845 wlock = repo.wlock()
730 wlock = repo.wlock()
846 patch = self.lookup(patch)
731 patch = self.lookup(patch)
847 # Suppose our series file is: A B C and the current 'top' patch is B.
732 # Suppose our series file is: A B C and the current 'top' patch is B.
848 # qpush C should be performed (moving forward)
733 # qpush C should be performed (moving forward)
849 # qpush B is a NOP (no change)
734 # qpush B is a NOP (no change)
850 # qpush A is an error (can't go backwards with qpush)
735 # qpush A is an error (can't go backwards with qpush)
851 if patch:
736 if patch:
852 info = self.isapplied(patch)
737 info = self.isapplied(patch)
853 if info:
738 if info:
854 if info[0] < len(self.applied) - 1:
739 if info[0] < len(self.applied) - 1:
855 raise util.Abort(_("cannot push to a previous patch: %s") %
740 raise util.Abort(_("cannot push to a previous patch: %s") %
856 patch)
741 patch)
857 if info[0] < len(self.series) - 1:
742 if info[0] < len(self.series) - 1:
858 self.ui.warn(_('qpush: %s is already at the top\n') % patch)
743 self.ui.warn(_('qpush: %s is already at the top\n') % patch)
859 else:
744 else:
860 self.ui.warn(_('all patches are currently applied\n'))
745 self.ui.warn(_('all patches are currently applied\n'))
861 return
746 return
862
747
863 # Following the above example, starting at 'top' of B:
748 # Following the above example, starting at 'top' of B:
864 # qpush should be performed (pushes C), but a subsequent qpush without
749 # qpush should be performed (pushes C), but a subsequent qpush without
865 # an argument is an error (nothing to apply). This allows a loop
750 # an argument is an error (nothing to apply). This allows a loop
866 # of "...while hg qpush..." to work as it detects an error when done
751 # of "...while hg qpush..." to work as it detects an error when done
867 if self.series_end() == len(self.series):
752 if self.series_end() == len(self.series):
868 self.ui.warn(_('patch series already fully applied\n'))
753 self.ui.warn(_('patch series already fully applied\n'))
869 return 1
754 return 1
870 if not force:
755 if not force:
871 self.check_localchanges(repo)
756 self.check_localchanges(repo)
872
757
873 self.applied_dirty = 1;
758 self.applied_dirty = 1;
874 start = self.series_end()
759 start = self.series_end()
875 if start > 0:
760 if start > 0:
876 self.check_toppatch(repo)
761 self.check_toppatch(repo)
877 if not patch:
762 if not patch:
878 patch = self.series[start]
763 patch = self.series[start]
879 end = start + 1
764 end = start + 1
880 else:
765 else:
881 end = self.series.index(patch, start) + 1
766 end = self.series.index(patch, start) + 1
882 s = self.series[start:end]
767 s = self.series[start:end]
883 all_files = {}
768 all_files = {}
884 try:
769 try:
885 if mergeq:
770 if mergeq:
886 ret = self.mergepatch(repo, mergeq, s, wlock)
771 ret = self.mergepatch(repo, mergeq, s, wlock)
887 else:
772 else:
888 ret = self.apply(repo, s, list, wlock=wlock,
773 ret = self.apply(repo, s, list, wlock=wlock,
889 all_files=all_files)
774 all_files=all_files)
890 except:
775 except:
891 self.ui.warn(_('cleaning up working directory...'))
776 self.ui.warn(_('cleaning up working directory...'))
892 node = repo.dirstate.parents()[0]
777 node = repo.dirstate.parents()[0]
893 hg.revert(repo, node, None, wlock)
778 hg.revert(repo, node, None, wlock)
894 unknown = repo.status(wlock=wlock)[4]
779 unknown = repo.status(wlock=wlock)[4]
895 # only remove unknown files that we know we touched or
780 # only remove unknown files that we know we touched or
896 # created while patching
781 # created while patching
897 for f in unknown:
782 for f in unknown:
898 if f in all_files:
783 if f in all_files:
899 util.unlink(repo.wjoin(f))
784 util.unlink(repo.wjoin(f))
900 self.ui.warn(_('done\n'))
785 self.ui.warn(_('done\n'))
901 raise
786 raise
902 top = self.applied[-1].name
787 top = self.applied[-1].name
903 if ret[0]:
788 if ret[0]:
904 self.ui.write("Errors during apply, please fix and refresh %s\n" %
789 self.ui.write("Errors during apply, please fix and refresh %s\n" %
905 top)
790 top)
906 else:
791 else:
907 self.ui.write("Now at: %s\n" % top)
792 self.ui.write("Now at: %s\n" % top)
908 return ret[0]
793 return ret[0]
909
794
910 def pop(self, repo, patch=None, force=False, update=True, all=False,
795 def pop(self, repo, patch=None, force=False, update=True, all=False,
911 wlock=None):
796 wlock=None):
912 def getfile(f, rev):
797 def getfile(f, rev):
913 t = repo.file(f).read(rev)
798 t = repo.file(f).read(rev)
914 repo.wfile(f, "w").write(t)
799 repo.wfile(f, "w").write(t)
915
800
916 if not wlock:
801 if not wlock:
917 wlock = repo.wlock()
802 wlock = repo.wlock()
918 if patch:
803 if patch:
919 # index, rev, patch
804 # index, rev, patch
920 info = self.isapplied(patch)
805 info = self.isapplied(patch)
921 if not info:
806 if not info:
922 patch = self.lookup(patch)
807 patch = self.lookup(patch)
923 info = self.isapplied(patch)
808 info = self.isapplied(patch)
924 if not info:
809 if not info:
925 raise util.Abort(_("patch %s is not applied") % patch)
810 raise util.Abort(_("patch %s is not applied") % patch)
926
811
927 if len(self.applied) == 0:
812 if len(self.applied) == 0:
928 # Allow qpop -a to work repeatedly,
813 # Allow qpop -a to work repeatedly,
929 # but not qpop without an argument
814 # but not qpop without an argument
930 self.ui.warn(_("no patches applied\n"))
815 self.ui.warn(_("no patches applied\n"))
931 return not all
816 return not all
932
817
933 if not update:
818 if not update:
934 parents = repo.dirstate.parents()
819 parents = repo.dirstate.parents()
935 rr = [ revlog.bin(x.rev) for x in self.applied ]
820 rr = [ revlog.bin(x.rev) for x in self.applied ]
936 for p in parents:
821 for p in parents:
937 if p in rr:
822 if p in rr:
938 self.ui.warn("qpop: forcing dirstate update\n")
823 self.ui.warn("qpop: forcing dirstate update\n")
939 update = True
824 update = True
940
825
941 if not force and update:
826 if not force and update:
942 self.check_localchanges(repo)
827 self.check_localchanges(repo)
943
828
944 self.applied_dirty = 1;
829 self.applied_dirty = 1;
945 end = len(self.applied)
830 end = len(self.applied)
946 if not patch:
831 if not patch:
947 if all:
832 if all:
948 popi = 0
833 popi = 0
949 else:
834 else:
950 popi = len(self.applied) - 1
835 popi = len(self.applied) - 1
951 else:
836 else:
952 popi = info[0] + 1
837 popi = info[0] + 1
953 if popi >= end:
838 if popi >= end:
954 self.ui.warn("qpop: %s is already at the top\n" % patch)
839 self.ui.warn("qpop: %s is already at the top\n" % patch)
955 return
840 return
956 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
841 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
957
842
958 start = info[0]
843 start = info[0]
959 rev = revlog.bin(info[1])
844 rev = revlog.bin(info[1])
960
845
961 # we know there are no local changes, so we can make a simplified
846 # we know there are no local changes, so we can make a simplified
962 # form of hg.update.
847 # form of hg.update.
963 if update:
848 if update:
964 top = self.check_toppatch(repo)
849 top = self.check_toppatch(repo)
965 qp = self.qparents(repo, rev)
850 qp = self.qparents(repo, rev)
966 changes = repo.changelog.read(qp)
851 changes = repo.changelog.read(qp)
967 mmap = repo.manifest.read(changes[0])
852 mmap = repo.manifest.read(changes[0])
968 m, a, r, d, u = repo.status(qp, top)[:5]
853 m, a, r, d, u = repo.status(qp, top)[:5]
969 if d:
854 if d:
970 raise util.Abort("deletions found between repo revs")
855 raise util.Abort("deletions found between repo revs")
971 for f in m:
856 for f in m:
972 getfile(f, mmap[f])
857 getfile(f, mmap[f])
973 for f in r:
858 for f in r:
974 getfile(f, mmap[f])
859 getfile(f, mmap[f])
975 util.set_exec(repo.wjoin(f), mmap.execf(f))
860 util.set_exec(repo.wjoin(f), mmap.execf(f))
976 repo.dirstate.update(m + r, 'n')
861 repo.dirstate.update(m + r, 'n')
977 for f in a:
862 for f in a:
978 try:
863 try:
979 os.unlink(repo.wjoin(f))
864 os.unlink(repo.wjoin(f))
980 except OSError, e:
865 except OSError, e:
981 if e.errno != errno.ENOENT:
866 if e.errno != errno.ENOENT:
982 raise
867 raise
983 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
868 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
984 except: pass
869 except: pass
985 if a:
870 if a:
986 repo.dirstate.forget(a)
871 repo.dirstate.forget(a)
987 repo.dirstate.setparents(qp, revlog.nullid)
872 repo.dirstate.setparents(qp, revlog.nullid)
988 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
873 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
989 del self.applied[start:end]
874 del self.applied[start:end]
990 if len(self.applied):
875 if len(self.applied):
991 self.ui.write("Now at: %s\n" % self.applied[-1].name)
876 self.ui.write("Now at: %s\n" % self.applied[-1].name)
992 else:
877 else:
993 self.ui.write("Patch queue now empty\n")
878 self.ui.write("Patch queue now empty\n")
994
879
995 def diff(self, repo, pats, opts):
880 def diff(self, repo, pats, opts):
996 top = self.check_toppatch(repo)
881 top = self.check_toppatch(repo)
997 if not top:
882 if not top:
998 self.ui.write("No patches applied\n")
883 self.ui.write("No patches applied\n")
999 return
884 return
1000 qp = self.qparents(repo, top)
885 qp = self.qparents(repo, top)
1001 if opts.get('git'):
886 if opts.get('git'):
1002 self.diffopts().git = True
887 self.diffopts().git = True
1003 self.printdiff(repo, qp, files=pats, opts=opts)
888 self.printdiff(repo, qp, files=pats, opts=opts)
1004
889
1005 def refresh(self, repo, pats=None, **opts):
890 def refresh(self, repo, pats=None, **opts):
1006 if len(self.applied) == 0:
891 if len(self.applied) == 0:
1007 self.ui.write("No patches applied\n")
892 self.ui.write("No patches applied\n")
1008 return 1
893 return 1
1009 wlock = repo.wlock()
894 wlock = repo.wlock()
1010 self.check_toppatch(repo)
895 self.check_toppatch(repo)
1011 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
896 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1012 top = revlog.bin(top)
897 top = revlog.bin(top)
1013 cparents = repo.changelog.parents(top)
898 cparents = repo.changelog.parents(top)
1014 patchparent = self.qparents(repo, top)
899 patchparent = self.qparents(repo, top)
1015 message, comments, user, date, patchfound = self.readheaders(patchfn)
900 message, comments, user, date, patchfound = self.readheaders(patchfn)
1016
901
1017 patchf = self.opener(patchfn, "w")
902 patchf = self.opener(patchfn, "w")
1018 msg = opts.get('msg', '').rstrip()
903 msg = opts.get('msg', '').rstrip()
1019 if msg:
904 if msg:
1020 if comments:
905 if comments:
1021 # Remove existing message.
906 # Remove existing message.
1022 ci = 0
907 ci = 0
1023 subj = None
908 subj = None
1024 for mi in xrange(len(message)):
909 for mi in xrange(len(message)):
1025 if comments[ci].lower().startswith('subject: '):
910 if comments[ci].lower().startswith('subject: '):
1026 subj = comments[ci][9:]
911 subj = comments[ci][9:]
1027 while message[mi] != comments[ci] and message[mi] != subj:
912 while message[mi] != comments[ci] and message[mi] != subj:
1028 ci += 1
913 ci += 1
1029 del comments[ci]
914 del comments[ci]
1030 comments.append(msg)
915 comments.append(msg)
1031 if comments:
916 if comments:
1032 comments = "\n".join(comments) + '\n\n'
917 comments = "\n".join(comments) + '\n\n'
1033 patchf.write(comments)
918 patchf.write(comments)
1034
919
1035 if opts.get('git'):
920 if opts.get('git'):
1036 self.diffopts().git = True
921 self.diffopts().git = True
1037 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
922 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1038 tip = repo.changelog.tip()
923 tip = repo.changelog.tip()
1039 if top == tip:
924 if top == tip:
1040 # if the top of our patch queue is also the tip, there is an
925 # if the top of our patch queue is also the tip, there is an
1041 # optimization here. We update the dirstate in place and strip
926 # optimization here. We update the dirstate in place and strip
1042 # off the tip commit. Then just commit the current directory
927 # off the tip commit. Then just commit the current directory
1043 # tree. We can also send repo.commit the list of files
928 # tree. We can also send repo.commit the list of files
1044 # changed to speed up the diff
929 # changed to speed up the diff
1045 #
930 #
1046 # in short mode, we only diff the files included in the
931 # in short mode, we only diff the files included in the
1047 # patch already
932 # patch already
1048 #
933 #
1049 # this should really read:
934 # this should really read:
1050 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
935 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1051 # but we do it backwards to take advantage of manifest/chlog
936 # but we do it backwards to take advantage of manifest/chlog
1052 # caching against the next repo.status call
937 # caching against the next repo.status call
1053 #
938 #
1054 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
939 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1055 changes = repo.changelog.read(tip)
940 changes = repo.changelog.read(tip)
1056 man = repo.manifest.read(changes[0])
941 man = repo.manifest.read(changes[0])
1057 aaa = aa[:]
942 aaa = aa[:]
1058 if opts.get('short'):
943 if opts.get('short'):
1059 filelist = mm + aa + dd
944 filelist = mm + aa + dd
1060 match = dict.fromkeys(filelist).__contains__
945 match = dict.fromkeys(filelist).__contains__
1061 else:
946 else:
1062 filelist = None
947 filelist = None
1063 match = util.always
948 match = util.always
1064 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
949 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1065
950
1066 # we might end up with files that were added between tip and
951 # we might end up with files that were added between tip and
1067 # the dirstate parent, but then changed in the local dirstate.
952 # the dirstate parent, but then changed in the local dirstate.
1068 # in this case, we want them to only show up in the added section
953 # in this case, we want them to only show up in the added section
1069 for x in m:
954 for x in m:
1070 if x not in aa:
955 if x not in aa:
1071 mm.append(x)
956 mm.append(x)
1072 # we might end up with files added by the local dirstate that
957 # we might end up with files added by the local dirstate that
1073 # were deleted by the patch. In this case, they should only
958 # were deleted by the patch. In this case, they should only
1074 # show up in the changed section.
959 # show up in the changed section.
1075 for x in a:
960 for x in a:
1076 if x in dd:
961 if x in dd:
1077 del dd[dd.index(x)]
962 del dd[dd.index(x)]
1078 mm.append(x)
963 mm.append(x)
1079 else:
964 else:
1080 aa.append(x)
965 aa.append(x)
1081 # make sure any files deleted in the local dirstate
966 # make sure any files deleted in the local dirstate
1082 # are not in the add or change column of the patch
967 # are not in the add or change column of the patch
1083 forget = []
968 forget = []
1084 for x in d + r:
969 for x in d + r:
1085 if x in aa:
970 if x in aa:
1086 del aa[aa.index(x)]
971 del aa[aa.index(x)]
1087 forget.append(x)
972 forget.append(x)
1088 continue
973 continue
1089 elif x in mm:
974 elif x in mm:
1090 del mm[mm.index(x)]
975 del mm[mm.index(x)]
1091 dd.append(x)
976 dd.append(x)
1092
977
1093 m = util.unique(mm)
978 m = util.unique(mm)
1094 r = util.unique(dd)
979 r = util.unique(dd)
1095 a = util.unique(aa)
980 a = util.unique(aa)
1096 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
981 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1097 filelist = util.unique(c[0] + c[1] + c[2])
982 filelist = util.unique(c[0] + c[1] + c[2])
1098 patch.diff(repo, patchparent, files=filelist, match=matchfn,
983 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1099 fp=patchf, changes=c, opts=self.diffopts())
984 fp=patchf, changes=c, opts=self.diffopts())
1100 patchf.close()
985 patchf.close()
1101
986
1102 repo.dirstate.setparents(*cparents)
987 repo.dirstate.setparents(*cparents)
1103 copies = {}
988 copies = {}
1104 for dst in a:
989 for dst in a:
1105 src = repo.dirstate.copied(dst)
990 src = repo.dirstate.copied(dst)
1106 if src is None:
991 if src is None:
1107 continue
992 continue
1108 copies.setdefault(src, []).append(dst)
993 copies.setdefault(src, []).append(dst)
1109 repo.dirstate.update(a, 'a')
994 repo.dirstate.update(a, 'a')
1110 # remember the copies between patchparent and tip
995 # remember the copies between patchparent and tip
1111 # this may be slow, so don't do it if we're not tracking copies
996 # this may be slow, so don't do it if we're not tracking copies
1112 if self.diffopts().git:
997 if self.diffopts().git:
1113 for dst in aaa:
998 for dst in aaa:
1114 f = repo.file(dst)
999 f = repo.file(dst)
1115 src = f.renamed(man[dst])
1000 src = f.renamed(man[dst])
1116 if src:
1001 if src:
1117 copies[src[0]] = copies.get(dst, [])
1002 copies[src[0]] = copies.get(dst, [])
1118 if dst in a:
1003 if dst in a:
1119 copies[src[0]].append(dst)
1004 copies[src[0]].append(dst)
1120 # we can't copy a file created by the patch itself
1005 # we can't copy a file created by the patch itself
1121 if dst in copies:
1006 if dst in copies:
1122 del copies[dst]
1007 del copies[dst]
1123 for src, dsts in copies.iteritems():
1008 for src, dsts in copies.iteritems():
1124 for dst in dsts:
1009 for dst in dsts:
1125 repo.dirstate.copy(src, dst)
1010 repo.dirstate.copy(src, dst)
1126 repo.dirstate.update(r, 'r')
1011 repo.dirstate.update(r, 'r')
1127 # if the patch excludes a modified file, mark that file with mtime=0
1012 # if the patch excludes a modified file, mark that file with mtime=0
1128 # so status can see it.
1013 # so status can see it.
1129 mm = []
1014 mm = []
1130 for i in xrange(len(m)-1, -1, -1):
1015 for i in xrange(len(m)-1, -1, -1):
1131 if not matchfn(m[i]):
1016 if not matchfn(m[i]):
1132 mm.append(m[i])
1017 mm.append(m[i])
1133 del m[i]
1018 del m[i]
1134 repo.dirstate.update(m, 'n')
1019 repo.dirstate.update(m, 'n')
1135 repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
1020 repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
1136 repo.dirstate.forget(forget)
1021 repo.dirstate.forget(forget)
1137
1022
1138 if not msg:
1023 if not msg:
1139 if not message:
1024 if not message:
1140 message = "patch queue: %s\n" % patchfn
1025 message = "patch queue: %s\n" % patchfn
1141 else:
1026 else:
1142 message = "\n".join(message)
1027 message = "\n".join(message)
1143 else:
1028 else:
1144 message = msg
1029 message = msg
1145
1030
1146 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1031 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1147 n = repo.commit(filelist, message, changes[1], match=matchfn,
1032 n = repo.commit(filelist, message, changes[1], match=matchfn,
1148 force=1, wlock=wlock)
1033 force=1, wlock=wlock)
1149 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1034 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1150 self.applied_dirty = 1
1035 self.applied_dirty = 1
1151 self.removeundo(repo)
1036 self.removeundo(repo)
1152 else:
1037 else:
1153 self.printdiff(repo, patchparent, fp=patchf)
1038 self.printdiff(repo, patchparent, fp=patchf)
1154 patchf.close()
1039 patchf.close()
1155 added = repo.status()[1]
1040 added = repo.status()[1]
1156 for a in added:
1041 for a in added:
1157 f = repo.wjoin(a)
1042 f = repo.wjoin(a)
1158 try:
1043 try:
1159 os.unlink(f)
1044 os.unlink(f)
1160 except OSError, e:
1045 except OSError, e:
1161 if e.errno != errno.ENOENT:
1046 if e.errno != errno.ENOENT:
1162 raise
1047 raise
1163 try: os.removedirs(os.path.dirname(f))
1048 try: os.removedirs(os.path.dirname(f))
1164 except: pass
1049 except: pass
1165 # forget the file copies in the dirstate
1050 # forget the file copies in the dirstate
1166 # push should readd the files later on
1051 # push should readd the files later on
1167 repo.dirstate.forget(added)
1052 repo.dirstate.forget(added)
1168 self.pop(repo, force=True, wlock=wlock)
1053 self.pop(repo, force=True, wlock=wlock)
1169 self.push(repo, force=True, wlock=wlock)
1054 self.push(repo, force=True, wlock=wlock)
1170
1055
1171 def init(self, repo, create=False):
1056 def init(self, repo, create=False):
1172 if not create and os.path.isdir(self.path):
1057 if not create and os.path.isdir(self.path):
1173 raise util.Abort(_("patch queue directory already exists"))
1058 raise util.Abort(_("patch queue directory already exists"))
1174 try:
1059 try:
1175 os.mkdir(self.path)
1060 os.mkdir(self.path)
1176 except OSError, inst:
1061 except OSError, inst:
1177 if inst.errno != errno.EEXIST or not create:
1062 if inst.errno != errno.EEXIST or not create:
1178 raise
1063 raise
1179 if create:
1064 if create:
1180 return self.qrepo(create=True)
1065 return self.qrepo(create=True)
1181
1066
1182 def unapplied(self, repo, patch=None):
1067 def unapplied(self, repo, patch=None):
1183 if patch and patch not in self.series:
1068 if patch and patch not in self.series:
1184 raise util.Abort(_("patch %s is not in series file") % patch)
1069 raise util.Abort(_("patch %s is not in series file") % patch)
1185 if not patch:
1070 if not patch:
1186 start = self.series_end()
1071 start = self.series_end()
1187 else:
1072 else:
1188 start = self.series.index(patch) + 1
1073 start = self.series.index(patch) + 1
1189 unapplied = []
1074 unapplied = []
1190 for i in xrange(start, len(self.series)):
1075 for i in xrange(start, len(self.series)):
1191 pushable, reason = self.pushable(i)
1076 pushable, reason = self.pushable(i)
1192 if pushable:
1077 if pushable:
1193 unapplied.append((i, self.series[i]))
1078 unapplied.append((i, self.series[i]))
1194 self.explain_pushable(i)
1079 self.explain_pushable(i)
1195 return unapplied
1080 return unapplied
1196
1081
1197 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1082 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1198 summary=False):
1083 summary=False):
1199 def displayname(patchname):
1084 def displayname(patchname):
1200 if summary:
1085 if summary:
1201 msg = self.readheaders(patchname)[0]
1086 msg = self.readheaders(patchname)[0]
1202 msg = msg and ': ' + msg[0] or ': '
1087 msg = msg and ': ' + msg[0] or ': '
1203 else:
1088 else:
1204 msg = ''
1089 msg = ''
1205 return '%s%s' % (patchname, msg)
1090 return '%s%s' % (patchname, msg)
1206
1091
1207 applied = dict.fromkeys([p.name for p in self.applied])
1092 applied = dict.fromkeys([p.name for p in self.applied])
1208 if length is None:
1093 if length is None:
1209 length = len(self.series) - start
1094 length = len(self.series) - start
1210 if not missing:
1095 if not missing:
1211 for i in xrange(start, start+length):
1096 for i in xrange(start, start+length):
1212 patch = self.series[i]
1097 patch = self.series[i]
1213 if patch in applied:
1098 if patch in applied:
1214 stat = 'A'
1099 stat = 'A'
1215 elif self.pushable(i)[0]:
1100 elif self.pushable(i)[0]:
1216 stat = 'U'
1101 stat = 'U'
1217 else:
1102 else:
1218 stat = 'G'
1103 stat = 'G'
1219 pfx = ''
1104 pfx = ''
1220 if self.ui.verbose:
1105 if self.ui.verbose:
1221 pfx = '%d %s ' % (i, stat)
1106 pfx = '%d %s ' % (i, stat)
1222 elif status and status != stat:
1107 elif status and status != stat:
1223 continue
1108 continue
1224 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1109 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1225 else:
1110 else:
1226 msng_list = []
1111 msng_list = []
1227 for root, dirs, files in os.walk(self.path):
1112 for root, dirs, files in os.walk(self.path):
1228 d = root[len(self.path) + 1:]
1113 d = root[len(self.path) + 1:]
1229 for f in files:
1114 for f in files:
1230 fl = os.path.join(d, f)
1115 fl = os.path.join(d, f)
1231 if (fl not in self.series and
1116 if (fl not in self.series and
1232 fl not in (self.status_path, self.series_path,
1117 fl not in (self.status_path, self.series_path,
1233 self.guards_path)
1118 self.guards_path)
1234 and not fl.startswith('.')):
1119 and not fl.startswith('.')):
1235 msng_list.append(fl)
1120 msng_list.append(fl)
1236 msng_list.sort()
1121 msng_list.sort()
1237 for x in msng_list:
1122 for x in msng_list:
1238 pfx = self.ui.verbose and ('D ') or ''
1123 pfx = self.ui.verbose and ('D ') or ''
1239 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1124 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1240
1125
1241 def issaveline(self, l):
1126 def issaveline(self, l):
1242 if l.name == '.hg.patches.save.line':
1127 if l.name == '.hg.patches.save.line':
1243 return True
1128 return True
1244
1129
1245 def qrepo(self, create=False):
1130 def qrepo(self, create=False):
1246 if create or os.path.isdir(self.join(".hg")):
1131 if create or os.path.isdir(self.join(".hg")):
1247 return hg.repository(self.ui, path=self.path, create=create)
1132 return hg.repository(self.ui, path=self.path, create=create)
1248
1133
1249 def restore(self, repo, rev, delete=None, qupdate=None):
1134 def restore(self, repo, rev, delete=None, qupdate=None):
1250 c = repo.changelog.read(rev)
1135 c = repo.changelog.read(rev)
1251 desc = c[4].strip()
1136 desc = c[4].strip()
1252 lines = desc.splitlines()
1137 lines = desc.splitlines()
1253 i = 0
1138 i = 0
1254 datastart = None
1139 datastart = None
1255 series = []
1140 series = []
1256 applied = []
1141 applied = []
1257 qpp = None
1142 qpp = None
1258 for i in xrange(0, len(lines)):
1143 for i in xrange(0, len(lines)):
1259 if lines[i] == 'Patch Data:':
1144 if lines[i] == 'Patch Data:':
1260 datastart = i + 1
1145 datastart = i + 1
1261 elif lines[i].startswith('Dirstate:'):
1146 elif lines[i].startswith('Dirstate:'):
1262 l = lines[i].rstrip()
1147 l = lines[i].rstrip()
1263 l = l[10:].split(' ')
1148 l = l[10:].split(' ')
1264 qpp = [ hg.bin(x) for x in l ]
1149 qpp = [ hg.bin(x) for x in l ]
1265 elif datastart != None:
1150 elif datastart != None:
1266 l = lines[i].rstrip()
1151 l = lines[i].rstrip()
1267 se = statusentry(l)
1152 se = statusentry(l)
1268 file_ = se.name
1153 file_ = se.name
1269 if se.rev:
1154 if se.rev:
1270 applied.append(se)
1155 applied.append(se)
1271 else:
1156 else:
1272 series.append(file_)
1157 series.append(file_)
1273 if datastart == None:
1158 if datastart == None:
1274 self.ui.warn("No saved patch data found\n")
1159 self.ui.warn("No saved patch data found\n")
1275 return 1
1160 return 1
1276 self.ui.warn("restoring status: %s\n" % lines[0])
1161 self.ui.warn("restoring status: %s\n" % lines[0])
1277 self.full_series = series
1162 self.full_series = series
1278 self.applied = applied
1163 self.applied = applied
1279 self.parse_series()
1164 self.parse_series()
1280 self.series_dirty = 1
1165 self.series_dirty = 1
1281 self.applied_dirty = 1
1166 self.applied_dirty = 1
1282 heads = repo.changelog.heads()
1167 heads = repo.changelog.heads()
1283 if delete:
1168 if delete:
1284 if rev not in heads:
1169 if rev not in heads:
1285 self.ui.warn("save entry has children, leaving it alone\n")
1170 self.ui.warn("save entry has children, leaving it alone\n")
1286 else:
1171 else:
1287 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1172 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1288 pp = repo.dirstate.parents()
1173 pp = repo.dirstate.parents()
1289 if rev in pp:
1174 if rev in pp:
1290 update = True
1175 update = True
1291 else:
1176 else:
1292 update = False
1177 update = False
1293 self.strip(repo, rev, update=update, backup='strip')
1178 self.strip(repo, rev, update=update, backup='strip')
1294 if qpp:
1179 if qpp:
1295 self.ui.warn("saved queue repository parents: %s %s\n" %
1180 self.ui.warn("saved queue repository parents: %s %s\n" %
1296 (hg.short(qpp[0]), hg.short(qpp[1])))
1181 (hg.short(qpp[0]), hg.short(qpp[1])))
1297 if qupdate:
1182 if qupdate:
1298 print "queue directory updating"
1183 print "queue directory updating"
1299 r = self.qrepo()
1184 r = self.qrepo()
1300 if not r:
1185 if not r:
1301 self.ui.warn("Unable to load queue repository\n")
1186 self.ui.warn("Unable to load queue repository\n")
1302 return 1
1187 return 1
1303 hg.clean(r, qpp[0])
1188 hg.clean(r, qpp[0])
1304
1189
1305 def save(self, repo, msg=None):
1190 def save(self, repo, msg=None):
1306 if len(self.applied) == 0:
1191 if len(self.applied) == 0:
1307 self.ui.warn("save: no patches applied, exiting\n")
1192 self.ui.warn("save: no patches applied, exiting\n")
1308 return 1
1193 return 1
1309 if self.issaveline(self.applied[-1]):
1194 if self.issaveline(self.applied[-1]):
1310 self.ui.warn("status is already saved\n")
1195 self.ui.warn("status is already saved\n")
1311 return 1
1196 return 1
1312
1197
1313 ar = [ ':' + x for x in self.full_series ]
1198 ar = [ ':' + x for x in self.full_series ]
1314 if not msg:
1199 if not msg:
1315 msg = "hg patches saved state"
1200 msg = "hg patches saved state"
1316 else:
1201 else:
1317 msg = "hg patches: " + msg.rstrip('\r\n')
1202 msg = "hg patches: " + msg.rstrip('\r\n')
1318 r = self.qrepo()
1203 r = self.qrepo()
1319 if r:
1204 if r:
1320 pp = r.dirstate.parents()
1205 pp = r.dirstate.parents()
1321 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1206 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1322 msg += "\n\nPatch Data:\n"
1207 msg += "\n\nPatch Data:\n"
1323 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1208 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1324 "\n".join(ar) + '\n' or "")
1209 "\n".join(ar) + '\n' or "")
1325 n = repo.commit(None, text, user=None, force=1)
1210 n = repo.commit(None, text, user=None, force=1)
1326 if not n:
1211 if not n:
1327 self.ui.warn("repo commit failed\n")
1212 self.ui.warn("repo commit failed\n")
1328 return 1
1213 return 1
1329 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1214 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1330 self.applied_dirty = 1
1215 self.applied_dirty = 1
1331 self.removeundo(repo)
1216 self.removeundo(repo)
1332
1217
1333 def full_series_end(self):
1218 def full_series_end(self):
1334 if len(self.applied) > 0:
1219 if len(self.applied) > 0:
1335 p = self.applied[-1].name
1220 p = self.applied[-1].name
1336 end = self.find_series(p)
1221 end = self.find_series(p)
1337 if end == None:
1222 if end == None:
1338 return len(self.full_series)
1223 return len(self.full_series)
1339 return end + 1
1224 return end + 1
1340 return 0
1225 return 0
1341
1226
1342 def series_end(self, all_patches=False):
1227 def series_end(self, all_patches=False):
1343 """If all_patches is False, return the index of the next pushable patch
1228 """If all_patches is False, return the index of the next pushable patch
1344 in the series, or the series length. If all_patches is True, return the
1229 in the series, or the series length. If all_patches is True, return the
1345 index of the first patch past the last applied one.
1230 index of the first patch past the last applied one.
1346 """
1231 """
1347 end = 0
1232 end = 0
1348 def next(start):
1233 def next(start):
1349 if all_patches:
1234 if all_patches:
1350 return start
1235 return start
1351 i = start
1236 i = start
1352 while i < len(self.series):
1237 while i < len(self.series):
1353 p, reason = self.pushable(i)
1238 p, reason = self.pushable(i)
1354 if p:
1239 if p:
1355 break
1240 break
1356 self.explain_pushable(i)
1241 self.explain_pushable(i)
1357 i += 1
1242 i += 1
1358 return i
1243 return i
1359 if len(self.applied) > 0:
1244 if len(self.applied) > 0:
1360 p = self.applied[-1].name
1245 p = self.applied[-1].name
1361 try:
1246 try:
1362 end = self.series.index(p)
1247 end = self.series.index(p)
1363 except ValueError:
1248 except ValueError:
1364 return 0
1249 return 0
1365 return next(end + 1)
1250 return next(end + 1)
1366 return next(end)
1251 return next(end)
1367
1252
1368 def appliedname(self, index):
1253 def appliedname(self, index):
1369 pname = self.applied[index].name
1254 pname = self.applied[index].name
1370 if not self.ui.verbose:
1255 if not self.ui.verbose:
1371 p = pname
1256 p = pname
1372 else:
1257 else:
1373 p = str(self.series.index(pname)) + " " + pname
1258 p = str(self.series.index(pname)) + " " + pname
1374 return p
1259 return p
1375
1260
1376 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1261 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1377 force=None, git=False):
1262 force=None, git=False):
1378 def checkseries(patchname):
1263 def checkseries(patchname):
1379 if patchname in self.series:
1264 if patchname in self.series:
1380 raise util.Abort(_('patch %s is already in the series file')
1265 raise util.Abort(_('patch %s is already in the series file')
1381 % patchname)
1266 % patchname)
1382 def checkfile(patchname):
1267 def checkfile(patchname):
1383 if not force and os.path.exists(self.join(patchname)):
1268 if not force and os.path.exists(self.join(patchname)):
1384 raise util.Abort(_('patch "%s" already exists')
1269 raise util.Abort(_('patch "%s" already exists')
1385 % patchname)
1270 % patchname)
1386
1271
1387 if rev:
1272 if rev:
1388 if files:
1273 if files:
1389 raise util.Abort(_('option "-r" not valid when importing '
1274 raise util.Abort(_('option "-r" not valid when importing '
1390 'files'))
1275 'files'))
1391 rev = cmdutil.revrange(repo, rev)
1276 rev = cmdutil.revrange(repo, rev)
1392 rev.sort(lambda x, y: cmp(y, x))
1277 rev.sort(lambda x, y: cmp(y, x))
1393 if (len(files) > 1 or len(rev) > 1) and patchname:
1278 if (len(files) > 1 or len(rev) > 1) and patchname:
1394 raise util.Abort(_('option "-n" not valid when importing multiple '
1279 raise util.Abort(_('option "-n" not valid when importing multiple '
1395 'patches'))
1280 'patches'))
1396 i = 0
1281 i = 0
1397 added = []
1282 added = []
1398 if rev:
1283 if rev:
1399 # If mq patches are applied, we can only import revisions
1284 # If mq patches are applied, we can only import revisions
1400 # that form a linear path to qbase.
1285 # that form a linear path to qbase.
1401 # Otherwise, they should form a linear path to a head.
1286 # Otherwise, they should form a linear path to a head.
1402 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1287 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1403 if len(heads) > 1:
1288 if len(heads) > 1:
1404 raise util.Abort(_('revision %d is the root of more than one '
1289 raise util.Abort(_('revision %d is the root of more than one '
1405 'branch') % rev[-1])
1290 'branch') % rev[-1])
1406 if self.applied:
1291 if self.applied:
1407 base = revlog.hex(repo.changelog.node(rev[0]))
1292 base = revlog.hex(repo.changelog.node(rev[0]))
1408 if base in [n.rev for n in self.applied]:
1293 if base in [n.rev for n in self.applied]:
1409 raise util.Abort(_('revision %d is already managed')
1294 raise util.Abort(_('revision %d is already managed')
1410 % rev[0])
1295 % rev[0])
1411 if heads != [revlog.bin(self.applied[-1].rev)]:
1296 if heads != [revlog.bin(self.applied[-1].rev)]:
1412 raise util.Abort(_('revision %d is not the parent of '
1297 raise util.Abort(_('revision %d is not the parent of '
1413 'the queue') % rev[0])
1298 'the queue') % rev[0])
1414 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1299 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1415 lastparent = repo.changelog.parentrevs(base)[0]
1300 lastparent = repo.changelog.parentrevs(base)[0]
1416 else:
1301 else:
1417 if heads != [repo.changelog.node(rev[0])]:
1302 if heads != [repo.changelog.node(rev[0])]:
1418 raise util.Abort(_('revision %d has unmanaged children')
1303 raise util.Abort(_('revision %d has unmanaged children')
1419 % rev[0])
1304 % rev[0])
1420 lastparent = None
1305 lastparent = None
1421
1306
1422 if git:
1307 if git:
1423 self.diffopts().git = True
1308 self.diffopts().git = True
1424
1309
1425 for r in rev:
1310 for r in rev:
1426 p1, p2 = repo.changelog.parentrevs(r)
1311 p1, p2 = repo.changelog.parentrevs(r)
1427 n = repo.changelog.node(r)
1312 n = repo.changelog.node(r)
1428 if p2 != revlog.nullrev:
1313 if p2 != revlog.nullrev:
1429 raise util.Abort(_('cannot import merge revision %d') % r)
1314 raise util.Abort(_('cannot import merge revision %d') % r)
1430 if lastparent and lastparent != r:
1315 if lastparent and lastparent != r:
1431 raise util.Abort(_('revision %d is not the parent of %d')
1316 raise util.Abort(_('revision %d is not the parent of %d')
1432 % (r, lastparent))
1317 % (r, lastparent))
1433 lastparent = p1
1318 lastparent = p1
1434
1319
1435 if not patchname:
1320 if not patchname:
1436 patchname = normname('%d.diff' % r)
1321 patchname = normname('%d.diff' % r)
1437 checkseries(patchname)
1322 checkseries(patchname)
1438 checkfile(patchname)
1323 checkfile(patchname)
1439 self.full_series.insert(0, patchname)
1324 self.full_series.insert(0, patchname)
1440
1325
1441 patchf = self.opener(patchname, "w")
1326 patchf = self.opener(patchname, "w")
1442 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1327 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1443 patchf.close()
1328 patchf.close()
1444
1329
1445 se = statusentry(revlog.hex(n), patchname)
1330 se = statusentry(revlog.hex(n), patchname)
1446 self.applied.insert(0, se)
1331 self.applied.insert(0, se)
1447
1332
1448 added.append(patchname)
1333 added.append(patchname)
1449 patchname = None
1334 patchname = None
1450 self.parse_series()
1335 self.parse_series()
1451 self.applied_dirty = 1
1336 self.applied_dirty = 1
1452
1337
1453 for filename in files:
1338 for filename in files:
1454 if existing:
1339 if existing:
1455 if filename == '-':
1340 if filename == '-':
1456 raise util.Abort(_('-e is incompatible with import from -'))
1341 raise util.Abort(_('-e is incompatible with import from -'))
1457 if not patchname:
1342 if not patchname:
1458 patchname = normname(filename)
1343 patchname = normname(filename)
1459 if not os.path.isfile(self.join(patchname)):
1344 if not os.path.isfile(self.join(patchname)):
1460 raise util.Abort(_("patch %s does not exist") % patchname)
1345 raise util.Abort(_("patch %s does not exist") % patchname)
1461 else:
1346 else:
1462 try:
1347 try:
1463 if filename == '-':
1348 if filename == '-':
1464 if not patchname:
1349 if not patchname:
1465 raise util.Abort(_('need --name to import a patch from -'))
1350 raise util.Abort(_('need --name to import a patch from -'))
1466 text = sys.stdin.read()
1351 text = sys.stdin.read()
1467 else:
1352 else:
1468 text = file(filename).read()
1353 text = file(filename).read()
1469 except IOError:
1354 except IOError:
1470 raise util.Abort(_("unable to read %s") % patchname)
1355 raise util.Abort(_("unable to read %s") % patchname)
1471 if not patchname:
1356 if not patchname:
1472 patchname = normname(os.path.basename(filename))
1357 patchname = normname(os.path.basename(filename))
1473 checkfile(patchname)
1358 checkfile(patchname)
1474 patchf = self.opener(patchname, "w")
1359 patchf = self.opener(patchname, "w")
1475 patchf.write(text)
1360 patchf.write(text)
1476 checkseries(patchname)
1361 checkseries(patchname)
1477 index = self.full_series_end() + i
1362 index = self.full_series_end() + i
1478 self.full_series[index:index] = [patchname]
1363 self.full_series[index:index] = [patchname]
1479 self.parse_series()
1364 self.parse_series()
1480 self.ui.warn("adding %s to series file\n" % patchname)
1365 self.ui.warn("adding %s to series file\n" % patchname)
1481 i += 1
1366 i += 1
1482 added.append(patchname)
1367 added.append(patchname)
1483 patchname = None
1368 patchname = None
1484 self.series_dirty = 1
1369 self.series_dirty = 1
1485 qrepo = self.qrepo()
1370 qrepo = self.qrepo()
1486 if qrepo:
1371 if qrepo:
1487 qrepo.add(added)
1372 qrepo.add(added)
1488
1373
1489 def delete(ui, repo, *patches, **opts):
1374 def delete(ui, repo, *patches, **opts):
1490 """remove patches from queue
1375 """remove patches from queue
1491
1376
1492 With --rev, mq will stop managing the named revisions. The
1377 With --rev, mq will stop managing the named revisions. The
1493 patches must be applied and at the base of the stack. This option
1378 patches must be applied and at the base of the stack. This option
1494 is useful when the patches have been applied upstream.
1379 is useful when the patches have been applied upstream.
1495
1380
1496 Otherwise, the patches must not be applied.
1381 Otherwise, the patches must not be applied.
1497
1382
1498 With --keep, the patch files are preserved in the patch directory."""
1383 With --keep, the patch files are preserved in the patch directory."""
1499 q = repo.mq
1384 q = repo.mq
1500 q.delete(repo, patches, opts)
1385 q.delete(repo, patches, opts)
1501 q.save_dirty()
1386 q.save_dirty()
1502 return 0
1387 return 0
1503
1388
1504 def applied(ui, repo, patch=None, **opts):
1389 def applied(ui, repo, patch=None, **opts):
1505 """print the patches already applied"""
1390 """print the patches already applied"""
1506 q = repo.mq
1391 q = repo.mq
1507 if patch:
1392 if patch:
1508 if patch not in q.series:
1393 if patch not in q.series:
1509 raise util.Abort(_("patch %s is not in series file") % patch)
1394 raise util.Abort(_("patch %s is not in series file") % patch)
1510 end = q.series.index(patch) + 1
1395 end = q.series.index(patch) + 1
1511 else:
1396 else:
1512 end = q.series_end(True)
1397 end = q.series_end(True)
1513 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1398 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1514
1399
1515 def unapplied(ui, repo, patch=None, **opts):
1400 def unapplied(ui, repo, patch=None, **opts):
1516 """print the patches not yet applied"""
1401 """print the patches not yet applied"""
1517 q = repo.mq
1402 q = repo.mq
1518 if patch:
1403 if patch:
1519 if patch not in q.series:
1404 if patch not in q.series:
1520 raise util.Abort(_("patch %s is not in series file") % patch)
1405 raise util.Abort(_("patch %s is not in series file") % patch)
1521 start = q.series.index(patch) + 1
1406 start = q.series.index(patch) + 1
1522 else:
1407 else:
1523 start = q.series_end(True)
1408 start = q.series_end(True)
1524 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1409 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1525
1410
1526 def qimport(ui, repo, *filename, **opts):
1411 def qimport(ui, repo, *filename, **opts):
1527 """import a patch
1412 """import a patch
1528
1413
1529 The patch will have the same name as its source file unless you
1414 The patch will have the same name as its source file unless you
1530 give it a new one with --name.
1415 give it a new one with --name.
1531
1416
1532 You can register an existing patch inside the patch directory
1417 You can register an existing patch inside the patch directory
1533 with the --existing flag.
1418 with the --existing flag.
1534
1419
1535 With --force, an existing patch of the same name will be overwritten.
1420 With --force, an existing patch of the same name will be overwritten.
1536
1421
1537 An existing changeset may be placed under mq control with --rev
1422 An existing changeset may be placed under mq control with --rev
1538 (e.g. qimport --rev tip -n patch will place tip under mq control).
1423 (e.g. qimport --rev tip -n patch will place tip under mq control).
1539 With --git, patches imported with --rev will use the git diff
1424 With --git, patches imported with --rev will use the git diff
1540 format.
1425 format.
1541 """
1426 """
1542 q = repo.mq
1427 q = repo.mq
1543 q.qimport(repo, filename, patchname=opts['name'],
1428 q.qimport(repo, filename, patchname=opts['name'],
1544 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1429 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1545 git=opts['git'])
1430 git=opts['git'])
1546 q.save_dirty()
1431 q.save_dirty()
1547 return 0
1432 return 0
1548
1433
1549 def init(ui, repo, **opts):
1434 def init(ui, repo, **opts):
1550 """init a new queue repository
1435 """init a new queue repository
1551
1436
1552 The queue repository is unversioned by default. If -c is
1437 The queue repository is unversioned by default. If -c is
1553 specified, qinit will create a separate nested repository
1438 specified, qinit will create a separate nested repository
1554 for patches (qinit -c may also be run later to convert
1439 for patches (qinit -c may also be run later to convert
1555 an unversioned patch repository into a versioned one).
1440 an unversioned patch repository into a versioned one).
1556 You can use qcommit to commit changes to this queue repository."""
1441 You can use qcommit to commit changes to this queue repository."""
1557 q = repo.mq
1442 q = repo.mq
1558 r = q.init(repo, create=opts['create_repo'])
1443 r = q.init(repo, create=opts['create_repo'])
1559 q.save_dirty()
1444 q.save_dirty()
1560 if r:
1445 if r:
1561 if not os.path.exists(r.wjoin('.hgignore')):
1446 if not os.path.exists(r.wjoin('.hgignore')):
1562 fp = r.wopener('.hgignore', 'w')
1447 fp = r.wopener('.hgignore', 'w')
1563 fp.write('syntax: glob\n')
1448 fp.write('syntax: glob\n')
1564 fp.write('status\n')
1449 fp.write('status\n')
1565 fp.write('guards\n')
1450 fp.write('guards\n')
1566 fp.close()
1451 fp.close()
1567 if not os.path.exists(r.wjoin('series')):
1452 if not os.path.exists(r.wjoin('series')):
1568 r.wopener('series', 'w').close()
1453 r.wopener('series', 'w').close()
1569 r.add(['.hgignore', 'series'])
1454 r.add(['.hgignore', 'series'])
1570 commands.add(ui, r)
1455 commands.add(ui, r)
1571 return 0
1456 return 0
1572
1457
1573 def clone(ui, source, dest=None, **opts):
1458 def clone(ui, source, dest=None, **opts):
1574 '''clone main and patch repository at same time
1459 '''clone main and patch repository at same time
1575
1460
1576 If source is local, destination will have no patches applied. If
1461 If source is local, destination will have no patches applied. If
1577 source is remote, this command can not check if patches are
1462 source is remote, this command can not check if patches are
1578 applied in source, so cannot guarantee that patches are not
1463 applied in source, so cannot guarantee that patches are not
1579 applied in destination. If you clone remote repository, be sure
1464 applied in destination. If you clone remote repository, be sure
1580 before that it has no patches applied.
1465 before that it has no patches applied.
1581
1466
1582 Source patch repository is looked for in <src>/.hg/patches by
1467 Source patch repository is looked for in <src>/.hg/patches by
1583 default. Use -p <url> to change.
1468 default. Use -p <url> to change.
1584 '''
1469 '''
1585 cmdutil.setremoteconfig(ui, opts)
1470 cmdutil.setremoteconfig(ui, opts)
1586 if dest is None:
1471 if dest is None:
1587 dest = hg.defaultdest(source)
1472 dest = hg.defaultdest(source)
1588 sr = hg.repository(ui, ui.expandpath(source))
1473 sr = hg.repository(ui, ui.expandpath(source))
1589 qbase, destrev = None, None
1474 qbase, destrev = None, None
1590 if sr.local():
1475 if sr.local():
1591 if sr.mq.applied:
1476 if sr.mq.applied:
1592 qbase = revlog.bin(sr.mq.applied[0].rev)
1477 qbase = revlog.bin(sr.mq.applied[0].rev)
1593 if not hg.islocal(dest):
1478 if not hg.islocal(dest):
1594 heads = dict.fromkeys(sr.heads())
1479 heads = dict.fromkeys(sr.heads())
1595 for h in sr.heads(qbase):
1480 for h in sr.heads(qbase):
1596 del heads[h]
1481 del heads[h]
1597 destrev = heads.keys()
1482 destrev = heads.keys()
1598 destrev.append(sr.changelog.parents(qbase)[0])
1483 destrev.append(sr.changelog.parents(qbase)[0])
1599 ui.note(_('cloning main repo\n'))
1484 ui.note(_('cloning main repo\n'))
1600 sr, dr = hg.clone(ui, sr.url(), dest,
1485 sr, dr = hg.clone(ui, sr.url(), dest,
1601 pull=opts['pull'],
1486 pull=opts['pull'],
1602 rev=destrev,
1487 rev=destrev,
1603 update=False,
1488 update=False,
1604 stream=opts['uncompressed'])
1489 stream=opts['uncompressed'])
1605 ui.note(_('cloning patch repo\n'))
1490 ui.note(_('cloning patch repo\n'))
1606 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1491 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1607 dr.url() + '/.hg/patches',
1492 dr.url() + '/.hg/patches',
1608 pull=opts['pull'],
1493 pull=opts['pull'],
1609 update=not opts['noupdate'],
1494 update=not opts['noupdate'],
1610 stream=opts['uncompressed'])
1495 stream=opts['uncompressed'])
1611 if dr.local():
1496 if dr.local():
1612 if qbase:
1497 if qbase:
1613 ui.note(_('stripping applied patches from destination repo\n'))
1498 ui.note(_('stripping applied patches from destination repo\n'))
1614 dr.mq.strip(dr, qbase, update=False, backup=None)
1499 dr.mq.strip(dr, qbase, update=False, backup=None)
1615 if not opts['noupdate']:
1500 if not opts['noupdate']:
1616 ui.note(_('updating destination repo\n'))
1501 ui.note(_('updating destination repo\n'))
1617 hg.update(dr, dr.changelog.tip())
1502 hg.update(dr, dr.changelog.tip())
1618
1503
1619 def commit(ui, repo, *pats, **opts):
1504 def commit(ui, repo, *pats, **opts):
1620 """commit changes in the queue repository"""
1505 """commit changes in the queue repository"""
1621 q = repo.mq
1506 q = repo.mq
1622 r = q.qrepo()
1507 r = q.qrepo()
1623 if not r: raise util.Abort('no queue repository')
1508 if not r: raise util.Abort('no queue repository')
1624 commands.commit(r.ui, r, *pats, **opts)
1509 commands.commit(r.ui, r, *pats, **opts)
1625
1510
1626 def series(ui, repo, **opts):
1511 def series(ui, repo, **opts):
1627 """print the entire series file"""
1512 """print the entire series file"""
1628 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1513 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1629 return 0
1514 return 0
1630
1515
1631 def top(ui, repo, **opts):
1516 def top(ui, repo, **opts):
1632 """print the name of the current patch"""
1517 """print the name of the current patch"""
1633 q = repo.mq
1518 q = repo.mq
1634 t = q.applied and q.series_end(True) or 0
1519 t = q.applied and q.series_end(True) or 0
1635 if t:
1520 if t:
1636 return q.qseries(repo, start=t-1, length=1, status='A',
1521 return q.qseries(repo, start=t-1, length=1, status='A',
1637 summary=opts.get('summary'))
1522 summary=opts.get('summary'))
1638 else:
1523 else:
1639 ui.write("No patches applied\n")
1524 ui.write("No patches applied\n")
1640 return 1
1525 return 1
1641
1526
1642 def next(ui, repo, **opts):
1527 def next(ui, repo, **opts):
1643 """print the name of the next patch"""
1528 """print the name of the next patch"""
1644 q = repo.mq
1529 q = repo.mq
1645 end = q.series_end()
1530 end = q.series_end()
1646 if end == len(q.series):
1531 if end == len(q.series):
1647 ui.write("All patches applied\n")
1532 ui.write("All patches applied\n")
1648 return 1
1533 return 1
1649 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1534 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1650
1535
1651 def prev(ui, repo, **opts):
1536 def prev(ui, repo, **opts):
1652 """print the name of the previous patch"""
1537 """print the name of the previous patch"""
1653 q = repo.mq
1538 q = repo.mq
1654 l = len(q.applied)
1539 l = len(q.applied)
1655 if l == 1:
1540 if l == 1:
1656 ui.write("Only one patch applied\n")
1541 ui.write("Only one patch applied\n")
1657 return 1
1542 return 1
1658 if not l:
1543 if not l:
1659 ui.write("No patches applied\n")
1544 ui.write("No patches applied\n")
1660 return 1
1545 return 1
1661 return q.qseries(repo, start=l-2, length=1, status='A',
1546 return q.qseries(repo, start=l-2, length=1, status='A',
1662 summary=opts.get('summary'))
1547 summary=opts.get('summary'))
1663
1548
1664 def new(ui, repo, patch, **opts):
1549 def new(ui, repo, patch, **opts):
1665 """create a new patch
1550 """create a new patch
1666
1551
1667 qnew creates a new patch on top of the currently-applied patch
1552 qnew creates a new patch on top of the currently-applied patch
1668 (if any). It will refuse to run if there are any outstanding
1553 (if any). It will refuse to run if there are any outstanding
1669 changes unless -f is specified, in which case the patch will
1554 changes unless -f is specified, in which case the patch will
1670 be initialised with them.
1555 be initialised with them.
1671
1556
1672 -e, -m or -l set the patch header as well as the commit message.
1557 -e, -m or -l set the patch header as well as the commit message.
1673 If none is specified, the patch header is empty and the
1558 If none is specified, the patch header is empty and the
1674 commit message is 'New patch: PATCH'"""
1559 commit message is 'New patch: PATCH'"""
1675 q = repo.mq
1560 q = repo.mq
1676 message = cmdutil.logmessage(opts)
1561 message = cmdutil.logmessage(opts)
1677 if opts['edit']:
1562 if opts['edit']:
1678 message = ui.edit(message, ui.username())
1563 message = ui.edit(message, ui.username())
1679 q.new(repo, patch, msg=message, force=opts['force'])
1564 q.new(repo, patch, msg=message, force=opts['force'])
1680 q.save_dirty()
1565 q.save_dirty()
1681 return 0
1566 return 0
1682
1567
1683 def refresh(ui, repo, *pats, **opts):
1568 def refresh(ui, repo, *pats, **opts):
1684 """update the current patch
1569 """update the current patch
1685
1570
1686 If any file patterns are provided, the refreshed patch will contain only
1571 If any file patterns are provided, the refreshed patch will contain only
1687 the modifications that match those patterns; the remaining modifications
1572 the modifications that match those patterns; the remaining modifications
1688 will remain in the working directory.
1573 will remain in the working directory.
1689
1574
1690 hg add/remove/copy/rename work as usual, though you might want to use
1575 hg add/remove/copy/rename work as usual, though you might want to use
1691 git-style patches (--git or [diff] git=1) to track copies and renames.
1576 git-style patches (--git or [diff] git=1) to track copies and renames.
1692 """
1577 """
1693 q = repo.mq
1578 q = repo.mq
1694 message = cmdutil.logmessage(opts)
1579 message = cmdutil.logmessage(opts)
1695 if opts['edit']:
1580 if opts['edit']:
1696 if message:
1581 if message:
1697 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1582 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1698 patch = q.applied[-1].name
1583 patch = q.applied[-1].name
1699 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1584 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1700 message = ui.edit('\n'.join(message), user or ui.username())
1585 message = ui.edit('\n'.join(message), user or ui.username())
1701 ret = q.refresh(repo, pats, msg=message, **opts)
1586 ret = q.refresh(repo, pats, msg=message, **opts)
1702 q.save_dirty()
1587 q.save_dirty()
1703 return ret
1588 return ret
1704
1589
1705 def diff(ui, repo, *pats, **opts):
1590 def diff(ui, repo, *pats, **opts):
1706 """diff of the current patch"""
1591 """diff of the current patch"""
1707 repo.mq.diff(repo, pats, opts)
1592 repo.mq.diff(repo, pats, opts)
1708 return 0
1593 return 0
1709
1594
1710 def fold(ui, repo, *files, **opts):
1595 def fold(ui, repo, *files, **opts):
1711 """fold the named patches into the current patch
1596 """fold the named patches into the current patch
1712
1597
1713 Patches must not yet be applied. Each patch will be successively
1598 Patches must not yet be applied. Each patch will be successively
1714 applied to the current patch in the order given. If all the
1599 applied to the current patch in the order given. If all the
1715 patches apply successfully, the current patch will be refreshed
1600 patches apply successfully, the current patch will be refreshed
1716 with the new cumulative patch, and the folded patches will
1601 with the new cumulative patch, and the folded patches will
1717 be deleted. With -k/--keep, the folded patch files will not
1602 be deleted. With -k/--keep, the folded patch files will not
1718 be removed afterwards.
1603 be removed afterwards.
1719
1604
1720 The header for each folded patch will be concatenated with
1605 The header for each folded patch will be concatenated with
1721 the current patch header, separated by a line of '* * *'."""
1606 the current patch header, separated by a line of '* * *'."""
1722
1607
1723 q = repo.mq
1608 q = repo.mq
1724
1609
1725 if not files:
1610 if not files:
1726 raise util.Abort(_('qfold requires at least one patch name'))
1611 raise util.Abort(_('qfold requires at least one patch name'))
1727 if not q.check_toppatch(repo):
1612 if not q.check_toppatch(repo):
1728 raise util.Abort(_('No patches applied'))
1613 raise util.Abort(_('No patches applied'))
1729
1614
1730 message = cmdutil.logmessage(opts)
1615 message = cmdutil.logmessage(opts)
1731 if opts['edit']:
1616 if opts['edit']:
1732 if message:
1617 if message:
1733 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1618 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1734
1619
1735 parent = q.lookup('qtip')
1620 parent = q.lookup('qtip')
1736 patches = []
1621 patches = []
1737 messages = []
1622 messages = []
1738 for f in files:
1623 for f in files:
1739 p = q.lookup(f)
1624 p = q.lookup(f)
1740 if p in patches or p == parent:
1625 if p in patches or p == parent:
1741 ui.warn(_('Skipping already folded patch %s') % p)
1626 ui.warn(_('Skipping already folded patch %s') % p)
1742 if q.isapplied(p):
1627 if q.isapplied(p):
1743 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1628 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1744 patches.append(p)
1629 patches.append(p)
1745
1630
1746 for p in patches:
1631 for p in patches:
1747 if not message:
1632 if not message:
1748 messages.append(q.readheaders(p)[0])
1633 messages.append(q.readheaders(p)[0])
1749 pf = q.join(p)
1634 pf = q.join(p)
1750 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1635 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1751 if not patchsuccess:
1636 if not patchsuccess:
1752 raise util.Abort(_('Error folding patch %s') % p)
1637 raise util.Abort(_('Error folding patch %s') % p)
1753 patch.updatedir(ui, repo, files)
1638 patch.updatedir(ui, repo, files)
1754
1639
1755 if not message:
1640 if not message:
1756 message, comments, user = q.readheaders(parent)[0:3]
1641 message, comments, user = q.readheaders(parent)[0:3]
1757 for msg in messages:
1642 for msg in messages:
1758 message.append('* * *')
1643 message.append('* * *')
1759 message.extend(msg)
1644 message.extend(msg)
1760 message = '\n'.join(message)
1645 message = '\n'.join(message)
1761
1646
1762 if opts['edit']:
1647 if opts['edit']:
1763 message = ui.edit(message, user or ui.username())
1648 message = ui.edit(message, user or ui.username())
1764
1649
1765 q.refresh(repo, msg=message)
1650 q.refresh(repo, msg=message)
1766 q.delete(repo, patches, opts)
1651 q.delete(repo, patches, opts)
1767 q.save_dirty()
1652 q.save_dirty()
1768
1653
1769 def goto(ui, repo, patch, **opts):
1654 def goto(ui, repo, patch, **opts):
1770 '''push or pop patches until named patch is at top of stack'''
1655 '''push or pop patches until named patch is at top of stack'''
1771 q = repo.mq
1656 q = repo.mq
1772 patch = q.lookup(patch)
1657 patch = q.lookup(patch)
1773 if q.isapplied(patch):
1658 if q.isapplied(patch):
1774 ret = q.pop(repo, patch, force=opts['force'])
1659 ret = q.pop(repo, patch, force=opts['force'])
1775 else:
1660 else:
1776 ret = q.push(repo, patch, force=opts['force'])
1661 ret = q.push(repo, patch, force=opts['force'])
1777 q.save_dirty()
1662 q.save_dirty()
1778 return ret
1663 return ret
1779
1664
1780 def guard(ui, repo, *args, **opts):
1665 def guard(ui, repo, *args, **opts):
1781 '''set or print guards for a patch
1666 '''set or print guards for a patch
1782
1667
1783 Guards control whether a patch can be pushed. A patch with no
1668 Guards control whether a patch can be pushed. A patch with no
1784 guards is always pushed. A patch with a positive guard ("+foo") is
1669 guards is always pushed. A patch with a positive guard ("+foo") is
1785 pushed only if the qselect command has activated it. A patch with
1670 pushed only if the qselect command has activated it. A patch with
1786 a negative guard ("-foo") is never pushed if the qselect command
1671 a negative guard ("-foo") is never pushed if the qselect command
1787 has activated it.
1672 has activated it.
1788
1673
1789 With no arguments, print the currently active guards.
1674 With no arguments, print the currently active guards.
1790 With arguments, set guards for the named patch.
1675 With arguments, set guards for the named patch.
1791
1676
1792 To set a negative guard "-foo" on topmost patch ("--" is needed so
1677 To set a negative guard "-foo" on topmost patch ("--" is needed so
1793 hg will not interpret "-foo" as an option):
1678 hg will not interpret "-foo" as an option):
1794 hg qguard -- -foo
1679 hg qguard -- -foo
1795
1680
1796 To set guards on another patch:
1681 To set guards on another patch:
1797 hg qguard other.patch +2.6.17 -stable
1682 hg qguard other.patch +2.6.17 -stable
1798 '''
1683 '''
1799 def status(idx):
1684 def status(idx):
1800 guards = q.series_guards[idx] or ['unguarded']
1685 guards = q.series_guards[idx] or ['unguarded']
1801 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1686 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1802 q = repo.mq
1687 q = repo.mq
1803 patch = None
1688 patch = None
1804 args = list(args)
1689 args = list(args)
1805 if opts['list']:
1690 if opts['list']:
1806 if args or opts['none']:
1691 if args or opts['none']:
1807 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1692 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1808 for i in xrange(len(q.series)):
1693 for i in xrange(len(q.series)):
1809 status(i)
1694 status(i)
1810 return
1695 return
1811 if not args or args[0][0:1] in '-+':
1696 if not args or args[0][0:1] in '-+':
1812 if not q.applied:
1697 if not q.applied:
1813 raise util.Abort(_('no patches applied'))
1698 raise util.Abort(_('no patches applied'))
1814 patch = q.applied[-1].name
1699 patch = q.applied[-1].name
1815 if patch is None and args[0][0:1] not in '-+':
1700 if patch is None and args[0][0:1] not in '-+':
1816 patch = args.pop(0)
1701 patch = args.pop(0)
1817 if patch is None:
1702 if patch is None:
1818 raise util.Abort(_('no patch to work with'))
1703 raise util.Abort(_('no patch to work with'))
1819 if args or opts['none']:
1704 if args or opts['none']:
1820 idx = q.find_series(patch)
1705 idx = q.find_series(patch)
1821 if idx is None:
1706 if idx is None:
1822 raise util.Abort(_('no patch named %s') % patch)
1707 raise util.Abort(_('no patch named %s') % patch)
1823 q.set_guards(idx, args)
1708 q.set_guards(idx, args)
1824 q.save_dirty()
1709 q.save_dirty()
1825 else:
1710 else:
1826 status(q.series.index(q.lookup(patch)))
1711 status(q.series.index(q.lookup(patch)))
1827
1712
1828 def header(ui, repo, patch=None):
1713 def header(ui, repo, patch=None):
1829 """Print the header of the topmost or specified patch"""
1714 """Print the header of the topmost or specified patch"""
1830 q = repo.mq
1715 q = repo.mq
1831
1716
1832 if patch:
1717 if patch:
1833 patch = q.lookup(patch)
1718 patch = q.lookup(patch)
1834 else:
1719 else:
1835 if not q.applied:
1720 if not q.applied:
1836 ui.write('No patches applied\n')
1721 ui.write('No patches applied\n')
1837 return 1
1722 return 1
1838 patch = q.lookup('qtip')
1723 patch = q.lookup('qtip')
1839 message = repo.mq.readheaders(patch)[0]
1724 message = repo.mq.readheaders(patch)[0]
1840
1725
1841 ui.write('\n'.join(message) + '\n')
1726 ui.write('\n'.join(message) + '\n')
1842
1727
1843 def lastsavename(path):
1728 def lastsavename(path):
1844 (directory, base) = os.path.split(path)
1729 (directory, base) = os.path.split(path)
1845 names = os.listdir(directory)
1730 names = os.listdir(directory)
1846 namere = re.compile("%s.([0-9]+)" % base)
1731 namere = re.compile("%s.([0-9]+)" % base)
1847 maxindex = None
1732 maxindex = None
1848 maxname = None
1733 maxname = None
1849 for f in names:
1734 for f in names:
1850 m = namere.match(f)
1735 m = namere.match(f)
1851 if m:
1736 if m:
1852 index = int(m.group(1))
1737 index = int(m.group(1))
1853 if maxindex == None or index > maxindex:
1738 if maxindex == None or index > maxindex:
1854 maxindex = index
1739 maxindex = index
1855 maxname = f
1740 maxname = f
1856 if maxname:
1741 if maxname:
1857 return (os.path.join(directory, maxname), maxindex)
1742 return (os.path.join(directory, maxname), maxindex)
1858 return (None, None)
1743 return (None, None)
1859
1744
1860 def savename(path):
1745 def savename(path):
1861 (last, index) = lastsavename(path)
1746 (last, index) = lastsavename(path)
1862 if last is None:
1747 if last is None:
1863 index = 0
1748 index = 0
1864 newpath = path + ".%d" % (index + 1)
1749 newpath = path + ".%d" % (index + 1)
1865 return newpath
1750 return newpath
1866
1751
1867 def push(ui, repo, patch=None, **opts):
1752 def push(ui, repo, patch=None, **opts):
1868 """push the next patch onto the stack"""
1753 """push the next patch onto the stack"""
1869 q = repo.mq
1754 q = repo.mq
1870 mergeq = None
1755 mergeq = None
1871
1756
1872 if opts['all']:
1757 if opts['all']:
1873 if not q.series:
1758 if not q.series:
1874 ui.warn(_('no patches in series\n'))
1759 ui.warn(_('no patches in series\n'))
1875 return 0
1760 return 0
1876 patch = q.series[-1]
1761 patch = q.series[-1]
1877 if opts['merge']:
1762 if opts['merge']:
1878 if opts['name']:
1763 if opts['name']:
1879 newpath = opts['name']
1764 newpath = opts['name']
1880 else:
1765 else:
1881 newpath, i = lastsavename(q.path)
1766 newpath, i = lastsavename(q.path)
1882 if not newpath:
1767 if not newpath:
1883 ui.warn("no saved queues found, please use -n\n")
1768 ui.warn("no saved queues found, please use -n\n")
1884 return 1
1769 return 1
1885 mergeq = queue(ui, repo.join(""), newpath)
1770 mergeq = queue(ui, repo.join(""), newpath)
1886 ui.warn("merging with queue at: %s\n" % mergeq.path)
1771 ui.warn("merging with queue at: %s\n" % mergeq.path)
1887 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1772 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1888 mergeq=mergeq)
1773 mergeq=mergeq)
1889 return ret
1774 return ret
1890
1775
1891 def pop(ui, repo, patch=None, **opts):
1776 def pop(ui, repo, patch=None, **opts):
1892 """pop the current patch off the stack"""
1777 """pop the current patch off the stack"""
1893 localupdate = True
1778 localupdate = True
1894 if opts['name']:
1779 if opts['name']:
1895 q = queue(ui, repo.join(""), repo.join(opts['name']))
1780 q = queue(ui, repo.join(""), repo.join(opts['name']))
1896 ui.warn('using patch queue: %s\n' % q.path)
1781 ui.warn('using patch queue: %s\n' % q.path)
1897 localupdate = False
1782 localupdate = False
1898 else:
1783 else:
1899 q = repo.mq
1784 q = repo.mq
1900 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1785 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1901 all=opts['all'])
1786 all=opts['all'])
1902 q.save_dirty()
1787 q.save_dirty()
1903 return ret
1788 return ret
1904
1789
1905 def rename(ui, repo, patch, name=None, **opts):
1790 def rename(ui, repo, patch, name=None, **opts):
1906 """rename a patch
1791 """rename a patch
1907
1792
1908 With one argument, renames the current patch to PATCH1.
1793 With one argument, renames the current patch to PATCH1.
1909 With two arguments, renames PATCH1 to PATCH2."""
1794 With two arguments, renames PATCH1 to PATCH2."""
1910
1795
1911 q = repo.mq
1796 q = repo.mq
1912
1797
1913 if not name:
1798 if not name:
1914 name = patch
1799 name = patch
1915 patch = None
1800 patch = None
1916
1801
1917 if patch:
1802 if patch:
1918 patch = q.lookup(patch)
1803 patch = q.lookup(patch)
1919 else:
1804 else:
1920 if not q.applied:
1805 if not q.applied:
1921 ui.write(_('No patches applied\n'))
1806 ui.write(_('No patches applied\n'))
1922 return
1807 return
1923 patch = q.lookup('qtip')
1808 patch = q.lookup('qtip')
1924 absdest = q.join(name)
1809 absdest = q.join(name)
1925 if os.path.isdir(absdest):
1810 if os.path.isdir(absdest):
1926 name = normname(os.path.join(name, os.path.basename(patch)))
1811 name = normname(os.path.join(name, os.path.basename(patch)))
1927 absdest = q.join(name)
1812 absdest = q.join(name)
1928 if os.path.exists(absdest):
1813 if os.path.exists(absdest):
1929 raise util.Abort(_('%s already exists') % absdest)
1814 raise util.Abort(_('%s already exists') % absdest)
1930
1815
1931 if name in q.series:
1816 if name in q.series:
1932 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1817 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1933
1818
1934 if ui.verbose:
1819 if ui.verbose:
1935 ui.write('Renaming %s to %s\n' % (patch, name))
1820 ui.write('Renaming %s to %s\n' % (patch, name))
1936 i = q.find_series(patch)
1821 i = q.find_series(patch)
1937 guards = q.guard_re.findall(q.full_series[i])
1822 guards = q.guard_re.findall(q.full_series[i])
1938 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1823 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1939 q.parse_series()
1824 q.parse_series()
1940 q.series_dirty = 1
1825 q.series_dirty = 1
1941
1826
1942 info = q.isapplied(patch)
1827 info = q.isapplied(patch)
1943 if info:
1828 if info:
1944 q.applied[info[0]] = statusentry(info[1], name)
1829 q.applied[info[0]] = statusentry(info[1], name)
1945 q.applied_dirty = 1
1830 q.applied_dirty = 1
1946
1831
1947 util.rename(q.join(patch), absdest)
1832 util.rename(q.join(patch), absdest)
1948 r = q.qrepo()
1833 r = q.qrepo()
1949 if r:
1834 if r:
1950 wlock = r.wlock()
1835 wlock = r.wlock()
1951 if r.dirstate.state(name) == 'r':
1836 if r.dirstate.state(name) == 'r':
1952 r.undelete([name], wlock)
1837 r.undelete([name], wlock)
1953 r.copy(patch, name, wlock)
1838 r.copy(patch, name, wlock)
1954 r.remove([patch], False, wlock)
1839 r.remove([patch], False, wlock)
1955
1840
1956 q.save_dirty()
1841 q.save_dirty()
1957
1842
1958 def restore(ui, repo, rev, **opts):
1843 def restore(ui, repo, rev, **opts):
1959 """restore the queue state saved by a rev"""
1844 """restore the queue state saved by a rev"""
1960 rev = repo.lookup(rev)
1845 rev = repo.lookup(rev)
1961 q = repo.mq
1846 q = repo.mq
1962 q.restore(repo, rev, delete=opts['delete'],
1847 q.restore(repo, rev, delete=opts['delete'],
1963 qupdate=opts['update'])
1848 qupdate=opts['update'])
1964 q.save_dirty()
1849 q.save_dirty()
1965 return 0
1850 return 0
1966
1851
1967 def save(ui, repo, **opts):
1852 def save(ui, repo, **opts):
1968 """save current queue state"""
1853 """save current queue state"""
1969 q = repo.mq
1854 q = repo.mq
1970 message = cmdutil.logmessage(opts)
1855 message = cmdutil.logmessage(opts)
1971 ret = q.save(repo, msg=message)
1856 ret = q.save(repo, msg=message)
1972 if ret:
1857 if ret:
1973 return ret
1858 return ret
1974 q.save_dirty()
1859 q.save_dirty()
1975 if opts['copy']:
1860 if opts['copy']:
1976 path = q.path
1861 path = q.path
1977 if opts['name']:
1862 if opts['name']:
1978 newpath = os.path.join(q.basepath, opts['name'])
1863 newpath = os.path.join(q.basepath, opts['name'])
1979 if os.path.exists(newpath):
1864 if os.path.exists(newpath):
1980 if not os.path.isdir(newpath):
1865 if not os.path.isdir(newpath):
1981 raise util.Abort(_('destination %s exists and is not '
1866 raise util.Abort(_('destination %s exists and is not '
1982 'a directory') % newpath)
1867 'a directory') % newpath)
1983 if not opts['force']:
1868 if not opts['force']:
1984 raise util.Abort(_('destination %s exists, '
1869 raise util.Abort(_('destination %s exists, '
1985 'use -f to force') % newpath)
1870 'use -f to force') % newpath)
1986 else:
1871 else:
1987 newpath = savename(path)
1872 newpath = savename(path)
1988 ui.warn("copy %s to %s\n" % (path, newpath))
1873 ui.warn("copy %s to %s\n" % (path, newpath))
1989 util.copyfiles(path, newpath)
1874 util.copyfiles(path, newpath)
1990 if opts['empty']:
1875 if opts['empty']:
1991 try:
1876 try:
1992 os.unlink(q.join(q.status_path))
1877 os.unlink(q.join(q.status_path))
1993 except:
1878 except:
1994 pass
1879 pass
1995 return 0
1880 return 0
1996
1881
1997 def strip(ui, repo, rev, **opts):
1882 def strip(ui, repo, rev, **opts):
1998 """strip a revision and all later revs on the same branch"""
1883 """strip a revision and all later revs on the same branch"""
1999 rev = repo.lookup(rev)
1884 rev = repo.lookup(rev)
2000 backup = 'all'
1885 backup = 'all'
2001 if opts['backup']:
1886 if opts['backup']:
2002 backup = 'strip'
1887 backup = 'strip'
2003 elif opts['nobackup']:
1888 elif opts['nobackup']:
2004 backup = 'none'
1889 backup = 'none'
2005 update = repo.dirstate.parents()[0] != revlog.nullid
1890 update = repo.dirstate.parents()[0] != revlog.nullid
2006 repo.mq.strip(repo, rev, backup=backup, update=update)
1891 repo.mq.strip(repo, rev, backup=backup, update=update)
2007 return 0
1892 return 0
2008
1893
2009 def select(ui, repo, *args, **opts):
1894 def select(ui, repo, *args, **opts):
2010 '''set or print guarded patches to push
1895 '''set or print guarded patches to push
2011
1896
2012 Use the qguard command to set or print guards on patch, then use
1897 Use the qguard command to set or print guards on patch, then use
2013 qselect to tell mq which guards to use. A patch will be pushed if it
1898 qselect to tell mq which guards to use. A patch will be pushed if it
2014 has no guards or any positive guards match the currently selected guard,
1899 has no guards or any positive guards match the currently selected guard,
2015 but will not be pushed if any negative guards match the current guard.
1900 but will not be pushed if any negative guards match the current guard.
2016 For example:
1901 For example:
2017
1902
2018 qguard foo.patch -stable (negative guard)
1903 qguard foo.patch -stable (negative guard)
2019 qguard bar.patch +stable (positive guard)
1904 qguard bar.patch +stable (positive guard)
2020 qselect stable
1905 qselect stable
2021
1906
2022 This activates the "stable" guard. mq will skip foo.patch (because
1907 This activates the "stable" guard. mq will skip foo.patch (because
2023 it has a negative match) but push bar.patch (because it
1908 it has a negative match) but push bar.patch (because it
2024 has a positive match).
1909 has a positive match).
2025
1910
2026 With no arguments, prints the currently active guards.
1911 With no arguments, prints the currently active guards.
2027 With one argument, sets the active guard.
1912 With one argument, sets the active guard.
2028
1913
2029 Use -n/--none to deactivate guards (no other arguments needed).
1914 Use -n/--none to deactivate guards (no other arguments needed).
2030 When no guards are active, patches with positive guards are skipped
1915 When no guards are active, patches with positive guards are skipped
2031 and patches with negative guards are pushed.
1916 and patches with negative guards are pushed.
2032
1917
2033 qselect can change the guards on applied patches. It does not pop
1918 qselect can change the guards on applied patches. It does not pop
2034 guarded patches by default. Use --pop to pop back to the last applied
1919 guarded patches by default. Use --pop to pop back to the last applied
2035 patch that is not guarded. Use --reapply (which implies --pop) to push
1920 patch that is not guarded. Use --reapply (which implies --pop) to push
2036 back to the current patch afterwards, but skip guarded patches.
1921 back to the current patch afterwards, but skip guarded patches.
2037
1922
2038 Use -s/--series to print a list of all guards in the series file (no
1923 Use -s/--series to print a list of all guards in the series file (no
2039 other arguments needed). Use -v for more information.'''
1924 other arguments needed). Use -v for more information.'''
2040
1925
2041 q = repo.mq
1926 q = repo.mq
2042 guards = q.active()
1927 guards = q.active()
2043 if args or opts['none']:
1928 if args or opts['none']:
2044 old_unapplied = q.unapplied(repo)
1929 old_unapplied = q.unapplied(repo)
2045 old_guarded = [i for i in xrange(len(q.applied)) if
1930 old_guarded = [i for i in xrange(len(q.applied)) if
2046 not q.pushable(i)[0]]
1931 not q.pushable(i)[0]]
2047 q.set_active(args)
1932 q.set_active(args)
2048 q.save_dirty()
1933 q.save_dirty()
2049 if not args:
1934 if not args:
2050 ui.status(_('guards deactivated\n'))
1935 ui.status(_('guards deactivated\n'))
2051 if not opts['pop'] and not opts['reapply']:
1936 if not opts['pop'] and not opts['reapply']:
2052 unapplied = q.unapplied(repo)
1937 unapplied = q.unapplied(repo)
2053 guarded = [i for i in xrange(len(q.applied))
1938 guarded = [i for i in xrange(len(q.applied))
2054 if not q.pushable(i)[0]]
1939 if not q.pushable(i)[0]]
2055 if len(unapplied) != len(old_unapplied):
1940 if len(unapplied) != len(old_unapplied):
2056 ui.status(_('number of unguarded, unapplied patches has '
1941 ui.status(_('number of unguarded, unapplied patches has '
2057 'changed from %d to %d\n') %
1942 'changed from %d to %d\n') %
2058 (len(old_unapplied), len(unapplied)))
1943 (len(old_unapplied), len(unapplied)))
2059 if len(guarded) != len(old_guarded):
1944 if len(guarded) != len(old_guarded):
2060 ui.status(_('number of guarded, applied patches has changed '
1945 ui.status(_('number of guarded, applied patches has changed '
2061 'from %d to %d\n') %
1946 'from %d to %d\n') %
2062 (len(old_guarded), len(guarded)))
1947 (len(old_guarded), len(guarded)))
2063 elif opts['series']:
1948 elif opts['series']:
2064 guards = {}
1949 guards = {}
2065 noguards = 0
1950 noguards = 0
2066 for gs in q.series_guards:
1951 for gs in q.series_guards:
2067 if not gs:
1952 if not gs:
2068 noguards += 1
1953 noguards += 1
2069 for g in gs:
1954 for g in gs:
2070 guards.setdefault(g, 0)
1955 guards.setdefault(g, 0)
2071 guards[g] += 1
1956 guards[g] += 1
2072 if ui.verbose:
1957 if ui.verbose:
2073 guards['NONE'] = noguards
1958 guards['NONE'] = noguards
2074 guards = guards.items()
1959 guards = guards.items()
2075 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1960 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2076 if guards:
1961 if guards:
2077 ui.note(_('guards in series file:\n'))
1962 ui.note(_('guards in series file:\n'))
2078 for guard, count in guards:
1963 for guard, count in guards:
2079 ui.note('%2d ' % count)
1964 ui.note('%2d ' % count)
2080 ui.write(guard, '\n')
1965 ui.write(guard, '\n')
2081 else:
1966 else:
2082 ui.note(_('no guards in series file\n'))
1967 ui.note(_('no guards in series file\n'))
2083 else:
1968 else:
2084 if guards:
1969 if guards:
2085 ui.note(_('active guards:\n'))
1970 ui.note(_('active guards:\n'))
2086 for g in guards:
1971 for g in guards:
2087 ui.write(g, '\n')
1972 ui.write(g, '\n')
2088 else:
1973 else:
2089 ui.write(_('no active guards\n'))
1974 ui.write(_('no active guards\n'))
2090 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1975 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2091 popped = False
1976 popped = False
2092 if opts['pop'] or opts['reapply']:
1977 if opts['pop'] or opts['reapply']:
2093 for i in xrange(len(q.applied)):
1978 for i in xrange(len(q.applied)):
2094 pushable, reason = q.pushable(i)
1979 pushable, reason = q.pushable(i)
2095 if not pushable:
1980 if not pushable:
2096 ui.status(_('popping guarded patches\n'))
1981 ui.status(_('popping guarded patches\n'))
2097 popped = True
1982 popped = True
2098 if i == 0:
1983 if i == 0:
2099 q.pop(repo, all=True)
1984 q.pop(repo, all=True)
2100 else:
1985 else:
2101 q.pop(repo, i-1)
1986 q.pop(repo, i-1)
2102 break
1987 break
2103 if popped:
1988 if popped:
2104 try:
1989 try:
2105 if reapply:
1990 if reapply:
2106 ui.status(_('reapplying unguarded patches\n'))
1991 ui.status(_('reapplying unguarded patches\n'))
2107 q.push(repo, reapply)
1992 q.push(repo, reapply)
2108 finally:
1993 finally:
2109 q.save_dirty()
1994 q.save_dirty()
2110
1995
2111 def reposetup(ui, repo):
1996 def reposetup(ui, repo):
2112 class mqrepo(repo.__class__):
1997 class mqrepo(repo.__class__):
2113 def abort_if_wdir_patched(self, errmsg, force=False):
1998 def abort_if_wdir_patched(self, errmsg, force=False):
2114 if self.mq.applied and not force:
1999 if self.mq.applied and not force:
2115 parent = revlog.hex(self.dirstate.parents()[0])
2000 parent = revlog.hex(self.dirstate.parents()[0])
2116 if parent in [s.rev for s in self.mq.applied]:
2001 if parent in [s.rev for s in self.mq.applied]:
2117 raise util.Abort(errmsg)
2002 raise util.Abort(errmsg)
2118
2003
2119 def commit(self, *args, **opts):
2004 def commit(self, *args, **opts):
2120 if len(args) >= 6:
2005 if len(args) >= 6:
2121 force = args[5]
2006 force = args[5]
2122 else:
2007 else:
2123 force = opts.get('force')
2008 force = opts.get('force')
2124 self.abort_if_wdir_patched(
2009 self.abort_if_wdir_patched(
2125 _('cannot commit over an applied mq patch'),
2010 _('cannot commit over an applied mq patch'),
2126 force)
2011 force)
2127
2012
2128 return super(mqrepo, self).commit(*args, **opts)
2013 return super(mqrepo, self).commit(*args, **opts)
2129
2014
2130 def push(self, remote, force=False, revs=None):
2015 def push(self, remote, force=False, revs=None):
2131 if self.mq.applied and not force and not revs:
2016 if self.mq.applied and not force and not revs:
2132 raise util.Abort(_('source has mq patches applied'))
2017 raise util.Abort(_('source has mq patches applied'))
2133 return super(mqrepo, self).push(remote, force, revs)
2018 return super(mqrepo, self).push(remote, force, revs)
2134
2019
2135 def tags(self):
2020 def tags(self):
2136 if self.tagscache:
2021 if self.tagscache:
2137 return self.tagscache
2022 return self.tagscache
2138
2023
2139 tagscache = super(mqrepo, self).tags()
2024 tagscache = super(mqrepo, self).tags()
2140
2025
2141 q = self.mq
2026 q = self.mq
2142 if not q.applied:
2027 if not q.applied:
2143 return tagscache
2028 return tagscache
2144
2029
2145 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2030 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2146 mqtags.append((mqtags[-1][0], 'qtip'))
2031 mqtags.append((mqtags[-1][0], 'qtip'))
2147 mqtags.append((mqtags[0][0], 'qbase'))
2032 mqtags.append((mqtags[0][0], 'qbase'))
2148 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2033 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2149 for patch in mqtags:
2034 for patch in mqtags:
2150 if patch[1] in tagscache:
2035 if patch[1] in tagscache:
2151 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2036 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2152 else:
2037 else:
2153 tagscache[patch[1]] = patch[0]
2038 tagscache[patch[1]] = patch[0]
2154
2039
2155 return tagscache
2040 return tagscache
2156
2041
2157 def _branchtags(self):
2042 def _branchtags(self):
2158 q = self.mq
2043 q = self.mq
2159 if not q.applied:
2044 if not q.applied:
2160 return super(mqrepo, self)._branchtags()
2045 return super(mqrepo, self)._branchtags()
2161
2046
2162 self.branchcache = {} # avoid recursion in changectx
2047 self.branchcache = {} # avoid recursion in changectx
2163 cl = self.changelog
2048 cl = self.changelog
2164 partial, last, lrev = self._readbranchcache()
2049 partial, last, lrev = self._readbranchcache()
2165
2050
2166 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2051 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2167 start = lrev + 1
2052 start = lrev + 1
2168 if start < qbase:
2053 if start < qbase:
2169 # update the cache (excluding the patches) and save it
2054 # update the cache (excluding the patches) and save it
2170 self._updatebranchcache(partial, lrev+1, qbase)
2055 self._updatebranchcache(partial, lrev+1, qbase)
2171 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2056 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2172 start = qbase
2057 start = qbase
2173 # if start = qbase, the cache is as updated as it should be.
2058 # if start = qbase, the cache is as updated as it should be.
2174 # if start > qbase, the cache includes (part of) the patches.
2059 # if start > qbase, the cache includes (part of) the patches.
2175 # we might as well use it, but we won't save it.
2060 # we might as well use it, but we won't save it.
2176
2061
2177 # update the cache up to the tip
2062 # update the cache up to the tip
2178 self._updatebranchcache(partial, start, cl.count())
2063 self._updatebranchcache(partial, start, cl.count())
2179
2064
2180 return partial
2065 return partial
2181
2066
2182 if repo.local():
2067 if repo.local():
2183 repo.__class__ = mqrepo
2068 repo.__class__ = mqrepo
2184 repo.mq = queue(ui, repo.join(""))
2069 repo.mq = queue(ui, repo.join(""))
2185
2070
2186 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2071 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2187
2072
2188 cmdtable = {
2073 cmdtable = {
2189 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2074 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2190 "qclone": (clone,
2075 "qclone": (clone,
2191 [('', 'pull', None, _('use pull protocol to copy metadata')),
2076 [('', 'pull', None, _('use pull protocol to copy metadata')),
2192 ('U', 'noupdate', None, _('do not update the new working directories')),
2077 ('U', 'noupdate', None, _('do not update the new working directories')),
2193 ('', 'uncompressed', None,
2078 ('', 'uncompressed', None,
2194 _('use uncompressed transfer (fast over LAN)')),
2079 _('use uncompressed transfer (fast over LAN)')),
2195 ('e', 'ssh', '', _('specify ssh command to use')),
2080 ('e', 'ssh', '', _('specify ssh command to use')),
2196 ('p', 'patches', '', _('location of source patch repo')),
2081 ('p', 'patches', '', _('location of source patch repo')),
2197 ('', 'remotecmd', '',
2082 ('', 'remotecmd', '',
2198 _('specify hg command to run on the remote side'))],
2083 _('specify hg command to run on the remote side'))],
2199 'hg qclone [OPTION]... SOURCE [DEST]'),
2084 'hg qclone [OPTION]... SOURCE [DEST]'),
2200 "qcommit|qci":
2085 "qcommit|qci":
2201 (commit,
2086 (commit,
2202 commands.table["^commit|ci"][1],
2087 commands.table["^commit|ci"][1],
2203 'hg qcommit [OPTION]... [FILE]...'),
2088 'hg qcommit [OPTION]... [FILE]...'),
2204 "^qdiff": (diff,
2089 "^qdiff": (diff,
2205 [('g', 'git', None, _('use git extended diff format')),
2090 [('g', 'git', None, _('use git extended diff format')),
2206 ('I', 'include', [], _('include names matching the given patterns')),
2091 ('I', 'include', [], _('include names matching the given patterns')),
2207 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2092 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2208 'hg qdiff [-I] [-X] [FILE]...'),
2093 'hg qdiff [-I] [-X] [FILE]...'),
2209 "qdelete|qremove|qrm":
2094 "qdelete|qremove|qrm":
2210 (delete,
2095 (delete,
2211 [('k', 'keep', None, _('keep patch file')),
2096 [('k', 'keep', None, _('keep patch file')),
2212 ('r', 'rev', [], _('stop managing a revision'))],
2097 ('r', 'rev', [], _('stop managing a revision'))],
2213 'hg qdelete [-k] [-r REV]... PATCH...'),
2098 'hg qdelete [-k] [-r REV]... PATCH...'),
2214 'qfold':
2099 'qfold':
2215 (fold,
2100 (fold,
2216 [('e', 'edit', None, _('edit patch header')),
2101 [('e', 'edit', None, _('edit patch header')),
2217 ('k', 'keep', None, _('keep folded patch files'))
2102 ('k', 'keep', None, _('keep folded patch files'))
2218 ] + commands.commitopts,
2103 ] + commands.commitopts,
2219 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2104 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2220 'qgoto': (goto, [('f', 'force', None, _('overwrite any local changes'))],
2105 'qgoto': (goto, [('f', 'force', None, _('overwrite any local changes'))],
2221 'hg qgoto [OPT]... PATCH'),
2106 'hg qgoto [OPT]... PATCH'),
2222 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2107 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2223 ('n', 'none', None, _('drop all guards'))],
2108 ('n', 'none', None, _('drop all guards'))],
2224 'hg qguard [PATCH] [+GUARD]... [-GUARD]...'),
2109 'hg qguard [PATCH] [+GUARD]... [-GUARD]...'),
2225 'qheader': (header, [],
2110 'qheader': (header, [],
2226 _('hg qheader [PATCH]')),
2111 _('hg qheader [PATCH]')),
2227 "^qimport":
2112 "^qimport":
2228 (qimport,
2113 (qimport,
2229 [('e', 'existing', None, 'import file in patch dir'),
2114 [('e', 'existing', None, 'import file in patch dir'),
2230 ('n', 'name', '', 'patch file name'),
2115 ('n', 'name', '', 'patch file name'),
2231 ('f', 'force', None, 'overwrite existing files'),
2116 ('f', 'force', None, 'overwrite existing files'),
2232 ('r', 'rev', [], 'place existing revisions under mq control'),
2117 ('r', 'rev', [], 'place existing revisions under mq control'),
2233 ('g', 'git', None, _('use git extended diff format'))],
2118 ('g', 'git', None, _('use git extended diff format'))],
2234 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2119 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2235 "^qinit":
2120 "^qinit":
2236 (init,
2121 (init,
2237 [('c', 'create-repo', None, 'create queue repository')],
2122 [('c', 'create-repo', None, 'create queue repository')],
2238 'hg qinit [-c]'),
2123 'hg qinit [-c]'),
2239 "qnew":
2124 "qnew":
2240 (new,
2125 (new,
2241 [('e', 'edit', None, _('edit commit message')),
2126 [('e', 'edit', None, _('edit commit message')),
2242 ('f', 'force', None, _('import uncommitted changes into patch'))
2127 ('f', 'force', None, _('import uncommitted changes into patch'))
2243 ] + commands.commitopts,
2128 ] + commands.commitopts,
2244 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2129 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2245 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2130 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2246 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2131 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2247 "^qpop":
2132 "^qpop":
2248 (pop,
2133 (pop,
2249 [('a', 'all', None, 'pop all patches'),
2134 [('a', 'all', None, 'pop all patches'),
2250 ('n', 'name', '', 'queue name to pop'),
2135 ('n', 'name', '', 'queue name to pop'),
2251 ('f', 'force', None, 'forget any local changes')],
2136 ('f', 'force', None, 'forget any local changes')],
2252 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2137 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2253 "^qpush":
2138 "^qpush":
2254 (push,
2139 (push,
2255 [('f', 'force', None, 'apply if the patch has rejects'),
2140 [('f', 'force', None, 'apply if the patch has rejects'),
2256 ('l', 'list', None, 'list patch name in commit text'),
2141 ('l', 'list', None, 'list patch name in commit text'),
2257 ('a', 'all', None, 'apply all patches'),
2142 ('a', 'all', None, 'apply all patches'),
2258 ('m', 'merge', None, 'merge from another queue'),
2143 ('m', 'merge', None, 'merge from another queue'),
2259 ('n', 'name', '', 'merge queue name')],
2144 ('n', 'name', '', 'merge queue name')],
2260 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2145 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2261 "^qrefresh":
2146 "^qrefresh":
2262 (refresh,
2147 (refresh,
2263 [('e', 'edit', None, _('edit commit message')),
2148 [('e', 'edit', None, _('edit commit message')),
2264 ('g', 'git', None, _('use git extended diff format')),
2149 ('g', 'git', None, _('use git extended diff format')),
2265 ('s', 'short', None, 'refresh only files already in the patch'),
2150 ('s', 'short', None, 'refresh only files already in the patch'),
2266 ('I', 'include', [], _('include names matching the given patterns')),
2151 ('I', 'include', [], _('include names matching the given patterns')),
2267 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2152 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2268 ] + commands.commitopts,
2153 ] + commands.commitopts,
2269 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2154 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2270 'qrename|qmv':
2155 'qrename|qmv':
2271 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2156 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2272 "qrestore":
2157 "qrestore":
2273 (restore,
2158 (restore,
2274 [('d', 'delete', None, 'delete save entry'),
2159 [('d', 'delete', None, 'delete save entry'),
2275 ('u', 'update', None, 'update queue working dir')],
2160 ('u', 'update', None, 'update queue working dir')],
2276 'hg qrestore [-d] [-u] REV'),
2161 'hg qrestore [-d] [-u] REV'),
2277 "qsave":
2162 "qsave":
2278 (save,
2163 (save,
2279 [('c', 'copy', None, 'copy patch directory'),
2164 [('c', 'copy', None, 'copy patch directory'),
2280 ('n', 'name', '', 'copy directory name'),
2165 ('n', 'name', '', 'copy directory name'),
2281 ('e', 'empty', None, 'clear queue status file'),
2166 ('e', 'empty', None, 'clear queue status file'),
2282 ('f', 'force', None, 'force copy')] + commands.commitopts,
2167 ('f', 'force', None, 'force copy')] + commands.commitopts,
2283 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2168 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2284 "qselect": (select,
2169 "qselect": (select,
2285 [('n', 'none', None, _('disable all guards')),
2170 [('n', 'none', None, _('disable all guards')),
2286 ('s', 'series', None, _('list all guards in series file')),
2171 ('s', 'series', None, _('list all guards in series file')),
2287 ('', 'pop', None,
2172 ('', 'pop', None,
2288 _('pop to before first guarded applied patch')),
2173 _('pop to before first guarded applied patch')),
2289 ('', 'reapply', None, _('pop, then reapply patches'))],
2174 ('', 'reapply', None, _('pop, then reapply patches'))],
2290 'hg qselect [OPTION]... [GUARD]...'),
2175 'hg qselect [OPTION]... [GUARD]...'),
2291 "qseries":
2176 "qseries":
2292 (series,
2177 (series,
2293 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2178 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2294 'hg qseries [-ms]'),
2179 'hg qseries [-ms]'),
2295 "^strip":
2180 "^strip":
2296 (strip,
2181 (strip,
2297 [('f', 'force', None, 'force multi-head removal'),
2182 [('f', 'force', None, 'force multi-head removal'),
2298 ('b', 'backup', None, 'bundle unrelated changesets'),
2183 ('b', 'backup', None, 'bundle unrelated changesets'),
2299 ('n', 'nobackup', None, 'no backups')],
2184 ('n', 'nobackup', None, 'no backups')],
2300 'hg strip [-f] [-b] [-n] REV'),
2185 'hg strip [-f] [-b] [-n] REV'),
2301 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2186 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2302 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2187 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2303 }
2188 }
@@ -1,237 +1,240 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 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 from mercurial import demandimport; demandimport.enable()
9 from mercurial import demandimport; demandimport.enable()
10 import os, mimetools, cStringIO
10 import os, mimetools, cStringIO
11 from mercurial.i18n import gettext as _
11 from mercurial.i18n import gettext as _
12 from mercurial import ui, hg, util, templater
12 from mercurial import ui, hg, util, templater
13 from common import get_mtime, staticfile, style_map, paritygen
13 from common import get_mtime, staticfile, style_map, paritygen
14 from hgweb_mod import hgweb
14 from hgweb_mod import hgweb
15
15
16 # This is a stopgap
16 # This is a stopgap
17 class hgwebdir(object):
17 class hgwebdir(object):
18 def __init__(self, config, parentui=None):
18 def __init__(self, config, parentui=None):
19 def cleannames(items):
19 def cleannames(items):
20 return [(name.strip(os.sep), path) for name, path in items]
20 return [(name.strip(os.sep), path) for name, path in items]
21
21
22 self.parentui = parentui
22 self.parentui = parentui
23 self.motd = None
23 self.motd = None
24 self.style = None
24 self.style = None
25 self.stripecount = None
25 self.stripecount = None
26 self.repos_sorted = ('name', False)
26 self.repos_sorted = ('name', False)
27 if isinstance(config, (list, tuple)):
27 if isinstance(config, (list, tuple)):
28 self.repos = cleannames(config)
28 self.repos = cleannames(config)
29 self.repos_sorted = ('', False)
29 self.repos_sorted = ('', False)
30 elif isinstance(config, dict):
30 elif isinstance(config, dict):
31 self.repos = cleannames(config.items())
31 self.repos = cleannames(config.items())
32 self.repos.sort()
32 self.repos.sort()
33 else:
33 else:
34 if isinstance(config, util.configparser):
34 if isinstance(config, util.configparser):
35 cp = config
35 cp = config
36 else:
36 else:
37 cp = util.configparser()
37 cp = util.configparser()
38 cp.read(config)
38 cp.read(config)
39 self.repos = []
39 self.repos = []
40 if cp.has_section('web'):
40 if cp.has_section('web'):
41 if cp.has_option('web', 'motd'):
41 if cp.has_option('web', 'motd'):
42 self.motd = cp.get('web', 'motd')
42 self.motd = cp.get('web', 'motd')
43 if cp.has_option('web', 'style'):
43 if cp.has_option('web', 'style'):
44 self.style = cp.get('web', 'style')
44 self.style = cp.get('web', 'style')
45 if cp.has_option('web', 'stripes'):
45 if cp.has_option('web', 'stripes'):
46 self.stripecount = int(cp.get('web', 'stripes'))
46 self.stripecount = int(cp.get('web', 'stripes'))
47 if cp.has_section('paths'):
47 if cp.has_section('paths'):
48 self.repos.extend(cleannames(cp.items('paths')))
48 self.repos.extend(cleannames(cp.items('paths')))
49 if cp.has_section('collections'):
49 if cp.has_section('collections'):
50 for prefix, root in cp.items('collections'):
50 for prefix, root in cp.items('collections'):
51 for path in util.walkrepos(root):
51 for path in util.walkrepos(root):
52 repo = os.path.normpath(path)
52 repo = os.path.normpath(path)
53 name = repo
53 name = repo
54 if name.startswith(prefix):
54 if name.startswith(prefix):
55 name = name[len(prefix):]
55 name = name[len(prefix):]
56 self.repos.append((name.lstrip(os.sep), repo))
56 self.repos.append((name.lstrip(os.sep), repo))
57 self.repos.sort()
57 self.repos.sort()
58
58
59 def run(self):
59 def run(self):
60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
62 import mercurial.hgweb.wsgicgi as wsgicgi
62 import mercurial.hgweb.wsgicgi as wsgicgi
63 from request import wsgiapplication
63 from request import wsgiapplication
64 def make_web_app():
64 def make_web_app():
65 return self
65 return self
66 wsgicgi.launch(wsgiapplication(make_web_app))
66 wsgicgi.launch(wsgiapplication(make_web_app))
67
67
68 def run_wsgi(self, req):
68 def run_wsgi(self, req):
69 def header(**map):
69 def header(**map):
70 header_file = cStringIO.StringIO(
70 header_file = cStringIO.StringIO(
71 ''.join(tmpl("header", encoding=util._encoding, **map)))
71 ''.join(tmpl("header", encoding=util._encoding, **map)))
72 msg = mimetools.Message(header_file, 0)
72 msg = mimetools.Message(header_file, 0)
73 req.header(msg.items())
73 req.header(msg.items())
74 yield header_file.read()
74 yield header_file.read()
75
75
76 def footer(**map):
76 def footer(**map):
77 yield tmpl("footer", **map)
77 yield tmpl("footer", **map)
78
78
79 def motd(**map):
79 def motd(**map):
80 if self.motd is not None:
80 if self.motd is not None:
81 yield self.motd
81 yield self.motd
82 else:
82 else:
83 yield config('web', 'motd', '')
83 yield config('web', 'motd', '')
84
84
85 parentui = self.parentui or ui.ui(report_untrusted=False)
85 parentui = self.parentui or ui.ui(report_untrusted=False)
86
86
87 def config(section, name, default=None, untrusted=True):
87 def config(section, name, default=None, untrusted=True):
88 return parentui.config(section, name, default, untrusted)
88 return parentui.config(section, name, default, untrusted)
89
89
90 url = req.env['REQUEST_URI'].split('?')[0]
90 url = req.env['REQUEST_URI'].split('?')[0]
91 if not url.endswith('/'):
91 if not url.endswith('/'):
92 url += '/'
92 url += '/'
93
93
94 staticurl = config('web', 'staticurl') or url + 'static/'
94 staticurl = config('web', 'staticurl') or url + 'static/'
95 if not staticurl.endswith('/'):
95 if not staticurl.endswith('/'):
96 staticurl += '/'
96 staticurl += '/'
97
97
98 style = self.style
98 style = self.style
99 if style is None:
99 if style is None:
100 style = config('web', 'style', '')
100 style = config('web', 'style', '')
101 if req.form.has_key('style'):
101 if req.form.has_key('style'):
102 style = req.form['style'][0]
102 style = req.form['style'][0]
103 if self.stripecount is None:
103 if self.stripecount is None:
104 self.stripecount = int(config('web', 'stripes', 1))
104 self.stripecount = int(config('web', 'stripes', 1))
105 mapfile = style_map(templater.templatepath(), style)
105 mapfile = style_map(templater.templatepath(), style)
106 tmpl = templater.templater(mapfile, templater.common_filters,
106 tmpl = templater.templater(mapfile, templater.common_filters,
107 defaults={"header": header,
107 defaults={"header": header,
108 "footer": footer,
108 "footer": footer,
109 "motd": motd,
109 "motd": motd,
110 "url": url,
110 "url": url,
111 "staticurl": staticurl})
111 "staticurl": staticurl})
112
112
113 def archivelist(ui, nodeid, url):
113 def archivelist(ui, nodeid, url):
114 allowed = ui.configlist("web", "allow_archive", untrusted=True)
114 allowed = ui.configlist("web", "allow_archive", untrusted=True)
115 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
115 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
116 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
116 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
117 untrusted=True):
117 untrusted=True):
118 yield {"type" : i[0], "extension": i[1],
118 yield {"type" : i[0], "extension": i[1],
119 "node": nodeid, "url": url}
119 "node": nodeid, "url": url}
120
120
121 def entries(sortcolumn="", descending=False, **map):
121 def entries(sortcolumn="", descending=False, **map):
122 def sessionvars(**map):
122 def sessionvars(**map):
123 fields = []
123 fields = []
124 if req.form.has_key('style'):
124 if req.form.has_key('style'):
125 style = req.form['style'][0]
125 style = req.form['style'][0]
126 if style != get('web', 'style', ''):
126 if style != get('web', 'style', ''):
127 fields.append(('style', style))
127 fields.append(('style', style))
128
128
129 separator = url[-1] == '?' and ';' or '?'
129 separator = url[-1] == '?' and ';' or '?'
130 for name, value in fields:
130 for name, value in fields:
131 yield dict(name=name, value=value, separator=separator)
131 yield dict(name=name, value=value, separator=separator)
132 separator = ';'
132 separator = ';'
133
133
134 rows = []
134 rows = []
135 parity = paritygen(self.stripecount)
135 parity = paritygen(self.stripecount)
136 for name, path in self.repos:
136 for name, path in self.repos:
137 u = ui.ui(parentui=parentui)
137 u = ui.ui(parentui=parentui)
138 try:
138 try:
139 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
139 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
140 except IOError:
140 except IOError:
141 pass
141 pass
142 def get(section, name, default=None):
142 def get(section, name, default=None):
143 return u.config(section, name, default, untrusted=True)
143 return u.config(section, name, default, untrusted=True)
144
144
145 if u.configbool("web", "hidden", untrusted=True):
146 continue
147
145 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
148 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
146 .replace("//", "/")) + '/'
149 .replace("//", "/")) + '/'
147
150
148 # update time with local timezone
151 # update time with local timezone
149 try:
152 try:
150 d = (get_mtime(path), util.makedate()[1])
153 d = (get_mtime(path), util.makedate()[1])
151 except OSError:
154 except OSError:
152 continue
155 continue
153
156
154 contact = (get("ui", "username") or # preferred
157 contact = (get("ui", "username") or # preferred
155 get("web", "contact") or # deprecated
158 get("web", "contact") or # deprecated
156 get("web", "author", "")) # also
159 get("web", "author", "")) # also
157 description = get("web", "description", "")
160 description = get("web", "description", "")
158 name = get("web", "name", name)
161 name = get("web", "name", name)
159 row = dict(contact=contact or "unknown",
162 row = dict(contact=contact or "unknown",
160 contact_sort=contact.upper() or "unknown",
163 contact_sort=contact.upper() or "unknown",
161 name=name,
164 name=name,
162 name_sort=name,
165 name_sort=name,
163 url=url,
166 url=url,
164 description=description or "unknown",
167 description=description or "unknown",
165 description_sort=description.upper() or "unknown",
168 description_sort=description.upper() or "unknown",
166 lastchange=d,
169 lastchange=d,
167 lastchange_sort=d[1]-d[0],
170 lastchange_sort=d[1]-d[0],
168 sessionvars=sessionvars,
171 sessionvars=sessionvars,
169 archives=archivelist(u, "tip", url))
172 archives=archivelist(u, "tip", url))
170 if (not sortcolumn
173 if (not sortcolumn
171 or (sortcolumn, descending) == self.repos_sorted):
174 or (sortcolumn, descending) == self.repos_sorted):
172 # fast path for unsorted output
175 # fast path for unsorted output
173 row['parity'] = parity.next()
176 row['parity'] = parity.next()
174 yield row
177 yield row
175 else:
178 else:
176 rows.append((row["%s_sort" % sortcolumn], row))
179 rows.append((row["%s_sort" % sortcolumn], row))
177 if rows:
180 if rows:
178 rows.sort()
181 rows.sort()
179 if descending:
182 if descending:
180 rows.reverse()
183 rows.reverse()
181 for key, row in rows:
184 for key, row in rows:
182 row['parity'] = parity.next()
185 row['parity'] = parity.next()
183 yield row
186 yield row
184
187
185 try:
188 try:
186 virtual = req.env.get("PATH_INFO", "").strip('/')
189 virtual = req.env.get("PATH_INFO", "").strip('/')
187 if virtual.startswith('static/'):
190 if virtual.startswith('static/'):
188 static = os.path.join(templater.templatepath(), 'static')
191 static = os.path.join(templater.templatepath(), 'static')
189 fname = virtual[7:]
192 fname = virtual[7:]
190 req.write(staticfile(static, fname, req) or
193 req.write(staticfile(static, fname, req) or
191 tmpl('error', error='%r not found' % fname))
194 tmpl('error', error='%r not found' % fname))
192 elif virtual:
195 elif virtual:
193 while virtual:
196 while virtual:
194 real = dict(self.repos).get(virtual)
197 real = dict(self.repos).get(virtual)
195 if real:
198 if real:
196 break
199 break
197 up = virtual.rfind('/')
200 up = virtual.rfind('/')
198 if up < 0:
201 if up < 0:
199 break
202 break
200 virtual = virtual[:up]
203 virtual = virtual[:up]
201 if real:
204 if real:
202 req.env['REPO_NAME'] = virtual
205 req.env['REPO_NAME'] = virtual
203 try:
206 try:
204 repo = hg.repository(parentui, real)
207 repo = hg.repository(parentui, real)
205 hgweb(repo).run_wsgi(req)
208 hgweb(repo).run_wsgi(req)
206 except IOError, inst:
209 except IOError, inst:
207 req.write(tmpl("error", error=inst.strerror))
210 req.write(tmpl("error", error=inst.strerror))
208 except hg.RepoError, inst:
211 except hg.RepoError, inst:
209 req.write(tmpl("error", error=str(inst)))
212 req.write(tmpl("error", error=str(inst)))
210 else:
213 else:
211 req.write(tmpl("notfound", repo=virtual))
214 req.write(tmpl("notfound", repo=virtual))
212 else:
215 else:
213 if req.form.has_key('static'):
216 if req.form.has_key('static'):
214 static = os.path.join(templater.templatepath(), "static")
217 static = os.path.join(templater.templatepath(), "static")
215 fname = req.form['static'][0]
218 fname = req.form['static'][0]
216 req.write(staticfile(static, fname, req)
219 req.write(staticfile(static, fname, req)
217 or tmpl("error", error="%r not found" % fname))
220 or tmpl("error", error="%r not found" % fname))
218 else:
221 else:
219 sortable = ["name", "description", "contact", "lastchange"]
222 sortable = ["name", "description", "contact", "lastchange"]
220 sortcolumn, descending = self.repos_sorted
223 sortcolumn, descending = self.repos_sorted
221 if req.form.has_key('sort'):
224 if req.form.has_key('sort'):
222 sortcolumn = req.form['sort'][0]
225 sortcolumn = req.form['sort'][0]
223 descending = sortcolumn.startswith('-')
226 descending = sortcolumn.startswith('-')
224 if descending:
227 if descending:
225 sortcolumn = sortcolumn[1:]
228 sortcolumn = sortcolumn[1:]
226 if sortcolumn not in sortable:
229 if sortcolumn not in sortable:
227 sortcolumn = ""
230 sortcolumn = ""
228
231
229 sort = [("sort_%s" % column,
232 sort = [("sort_%s" % column,
230 "%s%s" % ((not descending and column == sortcolumn)
233 "%s%s" % ((not descending and column == sortcolumn)
231 and "-" or "", column))
234 and "-" or "", column))
232 for column in sortable]
235 for column in sortable]
233 req.write(tmpl("index", entries=entries,
236 req.write(tmpl("index", entries=entries,
234 sortcolumn=sortcolumn, descending=descending,
237 sortcolumn=sortcolumn, descending=descending,
235 **dict(sort)))
238 **dict(sort)))
236 finally:
239 finally:
237 tmpl = None
240 tmpl = None
@@ -1,1550 +1,1551 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import _
15 from i18n import _
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
17 import os, threading, time, calendar, ConfigParser, locale, glob
17 import os, threading, time, calendar, ConfigParser, locale, glob
18
18
19 try:
19 try:
20 set = set
20 set = set
21 frozenset = frozenset
21 frozenset = frozenset
22 except NameError:
22 except NameError:
23 from sets import Set as set, ImmutableSet as frozenset
23 from sets import Set as set, ImmutableSet as frozenset
24
24
25 try:
25 try:
26 _encoding = os.environ.get("HGENCODING")
26 _encoding = os.environ.get("HGENCODING")
27 if sys.platform == 'darwin' and not _encoding:
27 if sys.platform == 'darwin' and not _encoding:
28 # On darwin, getpreferredencoding ignores the locale environment and
28 # On darwin, getpreferredencoding ignores the locale environment and
29 # always returns mac-roman. We override this if the environment is
29 # always returns mac-roman. We override this if the environment is
30 # not C (has been customized by the user).
30 # not C (has been customized by the user).
31 locale.setlocale(locale.LC_CTYPE, '')
31 locale.setlocale(locale.LC_CTYPE, '')
32 _encoding = locale.getlocale()[1]
32 _encoding = locale.getlocale()[1]
33 if not _encoding:
33 if not _encoding:
34 _encoding = locale.getpreferredencoding() or 'ascii'
34 _encoding = locale.getpreferredencoding() or 'ascii'
35 except locale.Error:
35 except locale.Error:
36 _encoding = 'ascii'
36 _encoding = 'ascii'
37 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
37 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
38 _fallbackencoding = 'ISO-8859-1'
38 _fallbackencoding = 'ISO-8859-1'
39
39
40 def tolocal(s):
40 def tolocal(s):
41 """
41 """
42 Convert a string from internal UTF-8 to local encoding
42 Convert a string from internal UTF-8 to local encoding
43
43
44 All internal strings should be UTF-8 but some repos before the
44 All internal strings should be UTF-8 but some repos before the
45 implementation of locale support may contain latin1 or possibly
45 implementation of locale support may contain latin1 or possibly
46 other character sets. We attempt to decode everything strictly
46 other character sets. We attempt to decode everything strictly
47 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
47 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
48 replace unknown characters.
48 replace unknown characters.
49 """
49 """
50 for e in ('UTF-8', _fallbackencoding):
50 for e in ('UTF-8', _fallbackencoding):
51 try:
51 try:
52 u = s.decode(e) # attempt strict decoding
52 u = s.decode(e) # attempt strict decoding
53 return u.encode(_encoding, "replace")
53 return u.encode(_encoding, "replace")
54 except LookupError, k:
54 except LookupError, k:
55 raise Abort(_("%s, please check your locale settings") % k)
55 raise Abort(_("%s, please check your locale settings") % k)
56 except UnicodeDecodeError:
56 except UnicodeDecodeError:
57 pass
57 pass
58 u = s.decode("utf-8", "replace") # last ditch
58 u = s.decode("utf-8", "replace") # last ditch
59 return u.encode(_encoding, "replace")
59 return u.encode(_encoding, "replace")
60
60
61 def fromlocal(s):
61 def fromlocal(s):
62 """
62 """
63 Convert a string from the local character encoding to UTF-8
63 Convert a string from the local character encoding to UTF-8
64
64
65 We attempt to decode strings using the encoding mode set by
65 We attempt to decode strings using the encoding mode set by
66 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
66 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
67 characters will cause an error message. Other modes include
67 characters will cause an error message. Other modes include
68 'replace', which replaces unknown characters with a special
68 'replace', which replaces unknown characters with a special
69 Unicode character, and 'ignore', which drops the character.
69 Unicode character, and 'ignore', which drops the character.
70 """
70 """
71 try:
71 try:
72 return s.decode(_encoding, _encodingmode).encode("utf-8")
72 return s.decode(_encoding, _encodingmode).encode("utf-8")
73 except UnicodeDecodeError, inst:
73 except UnicodeDecodeError, inst:
74 sub = s[max(0, inst.start-10):inst.start+10]
74 sub = s[max(0, inst.start-10):inst.start+10]
75 raise Abort("decoding near '%s': %s!" % (sub, inst))
75 raise Abort("decoding near '%s': %s!" % (sub, inst))
76 except LookupError, k:
76 except LookupError, k:
77 raise Abort(_("%s, please check your locale settings") % k)
77 raise Abort(_("%s, please check your locale settings") % k)
78
78
79 def locallen(s):
79 def locallen(s):
80 """Find the length in characters of a local string"""
80 """Find the length in characters of a local string"""
81 return len(s.decode(_encoding, "replace"))
81 return len(s.decode(_encoding, "replace"))
82
82
83 def localsub(s, a, b=None):
83 def localsub(s, a, b=None):
84 try:
84 try:
85 u = s.decode(_encoding, _encodingmode)
85 u = s.decode(_encoding, _encodingmode)
86 if b is not None:
86 if b is not None:
87 u = u[a:b]
87 u = u[a:b]
88 else:
88 else:
89 u = u[:a]
89 u = u[:a]
90 return u.encode(_encoding, _encodingmode)
90 return u.encode(_encoding, _encodingmode)
91 except UnicodeDecodeError, inst:
91 except UnicodeDecodeError, inst:
92 sub = s[max(0, inst.start-10), inst.start+10]
92 sub = s[max(0, inst.start-10), inst.start+10]
93 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
93 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
94
94
95 # used by parsedate
95 # used by parsedate
96 defaultdateformats = (
96 defaultdateformats = (
97 '%Y-%m-%d %H:%M:%S',
97 '%Y-%m-%d %H:%M:%S',
98 '%Y-%m-%d %I:%M:%S%p',
98 '%Y-%m-%d %I:%M:%S%p',
99 '%Y-%m-%d %H:%M',
99 '%Y-%m-%d %H:%M',
100 '%Y-%m-%d %I:%M%p',
100 '%Y-%m-%d %I:%M%p',
101 '%Y-%m-%d',
101 '%Y-%m-%d',
102 '%m-%d',
102 '%m-%d',
103 '%m/%d',
103 '%m/%d',
104 '%m/%d/%y',
104 '%m/%d/%y',
105 '%m/%d/%Y',
105 '%m/%d/%Y',
106 '%a %b %d %H:%M:%S %Y',
106 '%a %b %d %H:%M:%S %Y',
107 '%a %b %d %I:%M:%S%p %Y',
107 '%a %b %d %I:%M:%S%p %Y',
108 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
108 '%b %d %H:%M:%S %Y',
109 '%b %d %H:%M:%S %Y',
109 '%b %d %I:%M:%S%p %Y',
110 '%b %d %I:%M:%S%p %Y',
110 '%b %d %H:%M:%S',
111 '%b %d %H:%M:%S',
111 '%b %d %I:%M:%S%p',
112 '%b %d %I:%M:%S%p',
112 '%b %d %H:%M',
113 '%b %d %H:%M',
113 '%b %d %I:%M%p',
114 '%b %d %I:%M%p',
114 '%b %d %Y',
115 '%b %d %Y',
115 '%b %d',
116 '%b %d',
116 '%H:%M:%S',
117 '%H:%M:%S',
117 '%I:%M:%SP',
118 '%I:%M:%SP',
118 '%H:%M',
119 '%H:%M',
119 '%I:%M%p',
120 '%I:%M%p',
120 )
121 )
121
122
122 extendeddateformats = defaultdateformats + (
123 extendeddateformats = defaultdateformats + (
123 "%Y",
124 "%Y",
124 "%Y-%m",
125 "%Y-%m",
125 "%b",
126 "%b",
126 "%b %Y",
127 "%b %Y",
127 )
128 )
128
129
129 class SignalInterrupt(Exception):
130 class SignalInterrupt(Exception):
130 """Exception raised on SIGTERM and SIGHUP."""
131 """Exception raised on SIGTERM and SIGHUP."""
131
132
132 # differences from SafeConfigParser:
133 # differences from SafeConfigParser:
133 # - case-sensitive keys
134 # - case-sensitive keys
134 # - allows values that are not strings (this means that you may not
135 # - allows values that are not strings (this means that you may not
135 # be able to save the configuration to a file)
136 # be able to save the configuration to a file)
136 class configparser(ConfigParser.SafeConfigParser):
137 class configparser(ConfigParser.SafeConfigParser):
137 def optionxform(self, optionstr):
138 def optionxform(self, optionstr):
138 return optionstr
139 return optionstr
139
140
140 def set(self, section, option, value):
141 def set(self, section, option, value):
141 return ConfigParser.ConfigParser.set(self, section, option, value)
142 return ConfigParser.ConfigParser.set(self, section, option, value)
142
143
143 def _interpolate(self, section, option, rawval, vars):
144 def _interpolate(self, section, option, rawval, vars):
144 if not isinstance(rawval, basestring):
145 if not isinstance(rawval, basestring):
145 return rawval
146 return rawval
146 return ConfigParser.SafeConfigParser._interpolate(self, section,
147 return ConfigParser.SafeConfigParser._interpolate(self, section,
147 option, rawval, vars)
148 option, rawval, vars)
148
149
149 def cachefunc(func):
150 def cachefunc(func):
150 '''cache the result of function calls'''
151 '''cache the result of function calls'''
151 # XXX doesn't handle keywords args
152 # XXX doesn't handle keywords args
152 cache = {}
153 cache = {}
153 if func.func_code.co_argcount == 1:
154 if func.func_code.co_argcount == 1:
154 # we gain a small amount of time because
155 # we gain a small amount of time because
155 # we don't need to pack/unpack the list
156 # we don't need to pack/unpack the list
156 def f(arg):
157 def f(arg):
157 if arg not in cache:
158 if arg not in cache:
158 cache[arg] = func(arg)
159 cache[arg] = func(arg)
159 return cache[arg]
160 return cache[arg]
160 else:
161 else:
161 def f(*args):
162 def f(*args):
162 if args not in cache:
163 if args not in cache:
163 cache[args] = func(*args)
164 cache[args] = func(*args)
164 return cache[args]
165 return cache[args]
165
166
166 return f
167 return f
167
168
168 def pipefilter(s, cmd):
169 def pipefilter(s, cmd):
169 '''filter string S through command CMD, returning its output'''
170 '''filter string S through command CMD, returning its output'''
170 (pin, pout) = os.popen2(cmd, 'b')
171 (pin, pout) = os.popen2(cmd, 'b')
171 def writer():
172 def writer():
172 try:
173 try:
173 pin.write(s)
174 pin.write(s)
174 pin.close()
175 pin.close()
175 except IOError, inst:
176 except IOError, inst:
176 if inst.errno != errno.EPIPE:
177 if inst.errno != errno.EPIPE:
177 raise
178 raise
178
179
179 # we should use select instead on UNIX, but this will work on most
180 # we should use select instead on UNIX, but this will work on most
180 # systems, including Windows
181 # systems, including Windows
181 w = threading.Thread(target=writer)
182 w = threading.Thread(target=writer)
182 w.start()
183 w.start()
183 f = pout.read()
184 f = pout.read()
184 pout.close()
185 pout.close()
185 w.join()
186 w.join()
186 return f
187 return f
187
188
188 def tempfilter(s, cmd):
189 def tempfilter(s, cmd):
189 '''filter string S through a pair of temporary files with CMD.
190 '''filter string S through a pair of temporary files with CMD.
190 CMD is used as a template to create the real command to be run,
191 CMD is used as a template to create the real command to be run,
191 with the strings INFILE and OUTFILE replaced by the real names of
192 with the strings INFILE and OUTFILE replaced by the real names of
192 the temporary files generated.'''
193 the temporary files generated.'''
193 inname, outname = None, None
194 inname, outname = None, None
194 try:
195 try:
195 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
196 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
196 fp = os.fdopen(infd, 'wb')
197 fp = os.fdopen(infd, 'wb')
197 fp.write(s)
198 fp.write(s)
198 fp.close()
199 fp.close()
199 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
200 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
200 os.close(outfd)
201 os.close(outfd)
201 cmd = cmd.replace('INFILE', inname)
202 cmd = cmd.replace('INFILE', inname)
202 cmd = cmd.replace('OUTFILE', outname)
203 cmd = cmd.replace('OUTFILE', outname)
203 code = os.system(cmd)
204 code = os.system(cmd)
204 if code: raise Abort(_("command '%s' failed: %s") %
205 if code: raise Abort(_("command '%s' failed: %s") %
205 (cmd, explain_exit(code)))
206 (cmd, explain_exit(code)))
206 return open(outname, 'rb').read()
207 return open(outname, 'rb').read()
207 finally:
208 finally:
208 try:
209 try:
209 if inname: os.unlink(inname)
210 if inname: os.unlink(inname)
210 except: pass
211 except: pass
211 try:
212 try:
212 if outname: os.unlink(outname)
213 if outname: os.unlink(outname)
213 except: pass
214 except: pass
214
215
215 filtertable = {
216 filtertable = {
216 'tempfile:': tempfilter,
217 'tempfile:': tempfilter,
217 'pipe:': pipefilter,
218 'pipe:': pipefilter,
218 }
219 }
219
220
220 def filter(s, cmd):
221 def filter(s, cmd):
221 "filter a string through a command that transforms its input to its output"
222 "filter a string through a command that transforms its input to its output"
222 for name, fn in filtertable.iteritems():
223 for name, fn in filtertable.iteritems():
223 if cmd.startswith(name):
224 if cmd.startswith(name):
224 return fn(s, cmd[len(name):].lstrip())
225 return fn(s, cmd[len(name):].lstrip())
225 return pipefilter(s, cmd)
226 return pipefilter(s, cmd)
226
227
227 def binary(s):
228 def binary(s):
228 """return true if a string is binary data using diff's heuristic"""
229 """return true if a string is binary data using diff's heuristic"""
229 if s and '\0' in s[:4096]:
230 if s and '\0' in s[:4096]:
230 return True
231 return True
231 return False
232 return False
232
233
233 def unique(g):
234 def unique(g):
234 """return the uniq elements of iterable g"""
235 """return the uniq elements of iterable g"""
235 seen = {}
236 seen = {}
236 l = []
237 l = []
237 for f in g:
238 for f in g:
238 if f not in seen:
239 if f not in seen:
239 seen[f] = 1
240 seen[f] = 1
240 l.append(f)
241 l.append(f)
241 return l
242 return l
242
243
243 class Abort(Exception):
244 class Abort(Exception):
244 """Raised if a command needs to print an error and exit."""
245 """Raised if a command needs to print an error and exit."""
245
246
246 class UnexpectedOutput(Abort):
247 class UnexpectedOutput(Abort):
247 """Raised to print an error with part of output and exit."""
248 """Raised to print an error with part of output and exit."""
248
249
249 def always(fn): return True
250 def always(fn): return True
250 def never(fn): return False
251 def never(fn): return False
251
252
252 def expand_glob(pats):
253 def expand_glob(pats):
253 '''On Windows, expand the implicit globs in a list of patterns'''
254 '''On Windows, expand the implicit globs in a list of patterns'''
254 if os.name != 'nt':
255 if os.name != 'nt':
255 return list(pats)
256 return list(pats)
256 ret = []
257 ret = []
257 for p in pats:
258 for p in pats:
258 kind, name = patkind(p, None)
259 kind, name = patkind(p, None)
259 if kind is None:
260 if kind is None:
260 globbed = glob.glob(name)
261 globbed = glob.glob(name)
261 if globbed:
262 if globbed:
262 ret.extend(globbed)
263 ret.extend(globbed)
263 continue
264 continue
264 # if we couldn't expand the glob, just keep it around
265 # if we couldn't expand the glob, just keep it around
265 ret.append(p)
266 ret.append(p)
266 return ret
267 return ret
267
268
268 def patkind(name, dflt_pat='glob'):
269 def patkind(name, dflt_pat='glob'):
269 """Split a string into an optional pattern kind prefix and the
270 """Split a string into an optional pattern kind prefix and the
270 actual pattern."""
271 actual pattern."""
271 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
272 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
272 if name.startswith(prefix + ':'): return name.split(':', 1)
273 if name.startswith(prefix + ':'): return name.split(':', 1)
273 return dflt_pat, name
274 return dflt_pat, name
274
275
275 def globre(pat, head='^', tail='$'):
276 def globre(pat, head='^', tail='$'):
276 "convert a glob pattern into a regexp"
277 "convert a glob pattern into a regexp"
277 i, n = 0, len(pat)
278 i, n = 0, len(pat)
278 res = ''
279 res = ''
279 group = False
280 group = False
280 def peek(): return i < n and pat[i]
281 def peek(): return i < n and pat[i]
281 while i < n:
282 while i < n:
282 c = pat[i]
283 c = pat[i]
283 i = i+1
284 i = i+1
284 if c == '*':
285 if c == '*':
285 if peek() == '*':
286 if peek() == '*':
286 i += 1
287 i += 1
287 res += '.*'
288 res += '.*'
288 else:
289 else:
289 res += '[^/]*'
290 res += '[^/]*'
290 elif c == '?':
291 elif c == '?':
291 res += '.'
292 res += '.'
292 elif c == '[':
293 elif c == '[':
293 j = i
294 j = i
294 if j < n and pat[j] in '!]':
295 if j < n and pat[j] in '!]':
295 j += 1
296 j += 1
296 while j < n and pat[j] != ']':
297 while j < n and pat[j] != ']':
297 j += 1
298 j += 1
298 if j >= n:
299 if j >= n:
299 res += '\\['
300 res += '\\['
300 else:
301 else:
301 stuff = pat[i:j].replace('\\','\\\\')
302 stuff = pat[i:j].replace('\\','\\\\')
302 i = j + 1
303 i = j + 1
303 if stuff[0] == '!':
304 if stuff[0] == '!':
304 stuff = '^' + stuff[1:]
305 stuff = '^' + stuff[1:]
305 elif stuff[0] == '^':
306 elif stuff[0] == '^':
306 stuff = '\\' + stuff
307 stuff = '\\' + stuff
307 res = '%s[%s]' % (res, stuff)
308 res = '%s[%s]' % (res, stuff)
308 elif c == '{':
309 elif c == '{':
309 group = True
310 group = True
310 res += '(?:'
311 res += '(?:'
311 elif c == '}' and group:
312 elif c == '}' and group:
312 res += ')'
313 res += ')'
313 group = False
314 group = False
314 elif c == ',' and group:
315 elif c == ',' and group:
315 res += '|'
316 res += '|'
316 elif c == '\\':
317 elif c == '\\':
317 p = peek()
318 p = peek()
318 if p:
319 if p:
319 i += 1
320 i += 1
320 res += re.escape(p)
321 res += re.escape(p)
321 else:
322 else:
322 res += re.escape(c)
323 res += re.escape(c)
323 else:
324 else:
324 res += re.escape(c)
325 res += re.escape(c)
325 return head + res + tail
326 return head + res + tail
326
327
327 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
328 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
328
329
329 def pathto(root, n1, n2):
330 def pathto(root, n1, n2):
330 '''return the relative path from one place to another.
331 '''return the relative path from one place to another.
331 root should use os.sep to separate directories
332 root should use os.sep to separate directories
332 n1 should use os.sep to separate directories
333 n1 should use os.sep to separate directories
333 n2 should use "/" to separate directories
334 n2 should use "/" to separate directories
334 returns an os.sep-separated path.
335 returns an os.sep-separated path.
335
336
336 If n1 is a relative path, it's assumed it's
337 If n1 is a relative path, it's assumed it's
337 relative to root.
338 relative to root.
338 n2 should always be relative to root.
339 n2 should always be relative to root.
339 '''
340 '''
340 if not n1: return localpath(n2)
341 if not n1: return localpath(n2)
341 if os.path.isabs(n1):
342 if os.path.isabs(n1):
342 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
343 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
343 return os.path.join(root, localpath(n2))
344 return os.path.join(root, localpath(n2))
344 n2 = '/'.join((pconvert(root), n2))
345 n2 = '/'.join((pconvert(root), n2))
345 a, b = n1.split(os.sep), n2.split('/')
346 a, b = n1.split(os.sep), n2.split('/')
346 a.reverse()
347 a.reverse()
347 b.reverse()
348 b.reverse()
348 while a and b and a[-1] == b[-1]:
349 while a and b and a[-1] == b[-1]:
349 a.pop()
350 a.pop()
350 b.pop()
351 b.pop()
351 b.reverse()
352 b.reverse()
352 return os.sep.join((['..'] * len(a)) + b)
353 return os.sep.join((['..'] * len(a)) + b)
353
354
354 def canonpath(root, cwd, myname):
355 def canonpath(root, cwd, myname):
355 """return the canonical path of myname, given cwd and root"""
356 """return the canonical path of myname, given cwd and root"""
356 if root == os.sep:
357 if root == os.sep:
357 rootsep = os.sep
358 rootsep = os.sep
358 elif root.endswith(os.sep):
359 elif root.endswith(os.sep):
359 rootsep = root
360 rootsep = root
360 else:
361 else:
361 rootsep = root + os.sep
362 rootsep = root + os.sep
362 name = myname
363 name = myname
363 if not os.path.isabs(name):
364 if not os.path.isabs(name):
364 name = os.path.join(root, cwd, name)
365 name = os.path.join(root, cwd, name)
365 name = os.path.normpath(name)
366 name = os.path.normpath(name)
366 if name != rootsep and name.startswith(rootsep):
367 if name != rootsep and name.startswith(rootsep):
367 name = name[len(rootsep):]
368 name = name[len(rootsep):]
368 audit_path(name)
369 audit_path(name)
369 return pconvert(name)
370 return pconvert(name)
370 elif name == root:
371 elif name == root:
371 return ''
372 return ''
372 else:
373 else:
373 # Determine whether `name' is in the hierarchy at or beneath `root',
374 # Determine whether `name' is in the hierarchy at or beneath `root',
374 # by iterating name=dirname(name) until that causes no change (can't
375 # by iterating name=dirname(name) until that causes no change (can't
375 # check name == '/', because that doesn't work on windows). For each
376 # check name == '/', because that doesn't work on windows). For each
376 # `name', compare dev/inode numbers. If they match, the list `rel'
377 # `name', compare dev/inode numbers. If they match, the list `rel'
377 # holds the reversed list of components making up the relative file
378 # holds the reversed list of components making up the relative file
378 # name we want.
379 # name we want.
379 root_st = os.stat(root)
380 root_st = os.stat(root)
380 rel = []
381 rel = []
381 while True:
382 while True:
382 try:
383 try:
383 name_st = os.stat(name)
384 name_st = os.stat(name)
384 except OSError:
385 except OSError:
385 break
386 break
386 if samestat(name_st, root_st):
387 if samestat(name_st, root_st):
387 if not rel:
388 if not rel:
388 # name was actually the same as root (maybe a symlink)
389 # name was actually the same as root (maybe a symlink)
389 return ''
390 return ''
390 rel.reverse()
391 rel.reverse()
391 name = os.path.join(*rel)
392 name = os.path.join(*rel)
392 audit_path(name)
393 audit_path(name)
393 return pconvert(name)
394 return pconvert(name)
394 dirname, basename = os.path.split(name)
395 dirname, basename = os.path.split(name)
395 rel.append(basename)
396 rel.append(basename)
396 if dirname == name:
397 if dirname == name:
397 break
398 break
398 name = dirname
399 name = dirname
399
400
400 raise Abort('%s not under root' % myname)
401 raise Abort('%s not under root' % myname)
401
402
402 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
403 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
403 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
404 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
404
405
405 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
406 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
406 globbed=False, default=None):
407 globbed=False, default=None):
407 default = default or 'relpath'
408 default = default or 'relpath'
408 if default == 'relpath' and not globbed:
409 if default == 'relpath' and not globbed:
409 names = expand_glob(names)
410 names = expand_glob(names)
410 return _matcher(canonroot, cwd, names, inc, exc, default, src)
411 return _matcher(canonroot, cwd, names, inc, exc, default, src)
411
412
412 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
413 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
413 """build a function to match a set of file patterns
414 """build a function to match a set of file patterns
414
415
415 arguments:
416 arguments:
416 canonroot - the canonical root of the tree you're matching against
417 canonroot - the canonical root of the tree you're matching against
417 cwd - the current working directory, if relevant
418 cwd - the current working directory, if relevant
418 names - patterns to find
419 names - patterns to find
419 inc - patterns to include
420 inc - patterns to include
420 exc - patterns to exclude
421 exc - patterns to exclude
421 dflt_pat - if a pattern in names has no explicit type, assume this one
422 dflt_pat - if a pattern in names has no explicit type, assume this one
422 src - where these patterns came from (e.g. .hgignore)
423 src - where these patterns came from (e.g. .hgignore)
423
424
424 a pattern is one of:
425 a pattern is one of:
425 'glob:<glob>' - a glob relative to cwd
426 'glob:<glob>' - a glob relative to cwd
426 're:<regexp>' - a regular expression
427 're:<regexp>' - a regular expression
427 'path:<path>' - a path relative to canonroot
428 'path:<path>' - a path relative to canonroot
428 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
429 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
429 'relpath:<path>' - a path relative to cwd
430 'relpath:<path>' - a path relative to cwd
430 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
431 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
431 '<something>' - one of the cases above, selected by the dflt_pat argument
432 '<something>' - one of the cases above, selected by the dflt_pat argument
432
433
433 returns:
434 returns:
434 a 3-tuple containing
435 a 3-tuple containing
435 - list of roots (places where one should start a recursive walk of the fs);
436 - list of roots (places where one should start a recursive walk of the fs);
436 this often matches the explicit non-pattern names passed in, but also
437 this often matches the explicit non-pattern names passed in, but also
437 includes the initial part of glob: patterns that has no glob characters
438 includes the initial part of glob: patterns that has no glob characters
438 - a bool match(filename) function
439 - a bool match(filename) function
439 - a bool indicating if any patterns were passed in
440 - a bool indicating if any patterns were passed in
440 """
441 """
441
442
442 # a common case: no patterns at all
443 # a common case: no patterns at all
443 if not names and not inc and not exc:
444 if not names and not inc and not exc:
444 return [], always, False
445 return [], always, False
445
446
446 def contains_glob(name):
447 def contains_glob(name):
447 for c in name:
448 for c in name:
448 if c in _globchars: return True
449 if c in _globchars: return True
449 return False
450 return False
450
451
451 def regex(kind, name, tail):
452 def regex(kind, name, tail):
452 '''convert a pattern into a regular expression'''
453 '''convert a pattern into a regular expression'''
453 if not name:
454 if not name:
454 return ''
455 return ''
455 if kind == 're':
456 if kind == 're':
456 return name
457 return name
457 elif kind == 'path':
458 elif kind == 'path':
458 return '^' + re.escape(name) + '(?:/|$)'
459 return '^' + re.escape(name) + '(?:/|$)'
459 elif kind == 'relglob':
460 elif kind == 'relglob':
460 return globre(name, '(?:|.*/)', tail)
461 return globre(name, '(?:|.*/)', tail)
461 elif kind == 'relpath':
462 elif kind == 'relpath':
462 return re.escape(name) + '(?:/|$)'
463 return re.escape(name) + '(?:/|$)'
463 elif kind == 'relre':
464 elif kind == 'relre':
464 if name.startswith('^'):
465 if name.startswith('^'):
465 return name
466 return name
466 return '.*' + name
467 return '.*' + name
467 return globre(name, '', tail)
468 return globre(name, '', tail)
468
469
469 def matchfn(pats, tail):
470 def matchfn(pats, tail):
470 """build a matching function from a set of patterns"""
471 """build a matching function from a set of patterns"""
471 if not pats:
472 if not pats:
472 return
473 return
473 try:
474 try:
474 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
475 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
475 return re.compile(pat).match
476 return re.compile(pat).match
476 except re.error:
477 except re.error:
477 for k, p in pats:
478 for k, p in pats:
478 try:
479 try:
479 re.compile('(?:%s)' % regex(k, p, tail))
480 re.compile('(?:%s)' % regex(k, p, tail))
480 except re.error:
481 except re.error:
481 if src:
482 if src:
482 raise Abort("%s: invalid pattern (%s): %s" %
483 raise Abort("%s: invalid pattern (%s): %s" %
483 (src, k, p))
484 (src, k, p))
484 else:
485 else:
485 raise Abort("invalid pattern (%s): %s" % (k, p))
486 raise Abort("invalid pattern (%s): %s" % (k, p))
486 raise Abort("invalid pattern")
487 raise Abort("invalid pattern")
487
488
488 def globprefix(pat):
489 def globprefix(pat):
489 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
490 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
490 root = []
491 root = []
491 for p in pat.split('/'):
492 for p in pat.split('/'):
492 if contains_glob(p): break
493 if contains_glob(p): break
493 root.append(p)
494 root.append(p)
494 return '/'.join(root) or '.'
495 return '/'.join(root) or '.'
495
496
496 def normalizepats(names, default):
497 def normalizepats(names, default):
497 pats = []
498 pats = []
498 roots = []
499 roots = []
499 anypats = False
500 anypats = False
500 for kind, name in [patkind(p, default) for p in names]:
501 for kind, name in [patkind(p, default) for p in names]:
501 if kind in ('glob', 'relpath'):
502 if kind in ('glob', 'relpath'):
502 name = canonpath(canonroot, cwd, name)
503 name = canonpath(canonroot, cwd, name)
503 elif kind in ('relglob', 'path'):
504 elif kind in ('relglob', 'path'):
504 name = normpath(name)
505 name = normpath(name)
505
506
506 pats.append((kind, name))
507 pats.append((kind, name))
507
508
508 if kind in ('glob', 're', 'relglob', 'relre'):
509 if kind in ('glob', 're', 'relglob', 'relre'):
509 anypats = True
510 anypats = True
510
511
511 if kind == 'glob':
512 if kind == 'glob':
512 root = globprefix(name)
513 root = globprefix(name)
513 roots.append(root)
514 roots.append(root)
514 elif kind in ('relpath', 'path'):
515 elif kind in ('relpath', 'path'):
515 roots.append(name or '.')
516 roots.append(name or '.')
516 elif kind == 'relglob':
517 elif kind == 'relglob':
517 roots.append('.')
518 roots.append('.')
518 return roots, pats, anypats
519 return roots, pats, anypats
519
520
520 roots, pats, anypats = normalizepats(names, dflt_pat)
521 roots, pats, anypats = normalizepats(names, dflt_pat)
521
522
522 patmatch = matchfn(pats, '$') or always
523 patmatch = matchfn(pats, '$') or always
523 incmatch = always
524 incmatch = always
524 if inc:
525 if inc:
525 dummy, inckinds, dummy = normalizepats(inc, 'glob')
526 dummy, inckinds, dummy = normalizepats(inc, 'glob')
526 incmatch = matchfn(inckinds, '(?:/|$)')
527 incmatch = matchfn(inckinds, '(?:/|$)')
527 excmatch = lambda fn: False
528 excmatch = lambda fn: False
528 if exc:
529 if exc:
529 dummy, exckinds, dummy = normalizepats(exc, 'glob')
530 dummy, exckinds, dummy = normalizepats(exc, 'glob')
530 excmatch = matchfn(exckinds, '(?:/|$)')
531 excmatch = matchfn(exckinds, '(?:/|$)')
531
532
532 if not names and inc and not exc:
533 if not names and inc and not exc:
533 # common case: hgignore patterns
534 # common case: hgignore patterns
534 match = incmatch
535 match = incmatch
535 else:
536 else:
536 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
537 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
537
538
538 return (roots, match, (inc or exc or anypats) and True)
539 return (roots, match, (inc or exc or anypats) and True)
539
540
540 _hgexecutable = None
541 _hgexecutable = None
541
542
542 def set_hgexecutable(path):
543 def set_hgexecutable(path):
543 """remember location of the 'hg' executable if easily possible
544 """remember location of the 'hg' executable if easily possible
544
545
545 path might be None or empty if hg was loaded as a module,
546 path might be None or empty if hg was loaded as a module,
546 fall back to 'hg' in this case.
547 fall back to 'hg' in this case.
547 """
548 """
548 global _hgexecutable
549 global _hgexecutable
549 _hgexecutable = path and os.path.abspath(path) or 'hg'
550 _hgexecutable = path and os.path.abspath(path) or 'hg'
550
551
551 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
552 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
552 '''enhanced shell command execution.
553 '''enhanced shell command execution.
553 run with environment maybe modified, maybe in different dir.
554 run with environment maybe modified, maybe in different dir.
554
555
555 if command fails and onerr is None, return status. if ui object,
556 if command fails and onerr is None, return status. if ui object,
556 print error message and return status, else raise onerr object as
557 print error message and return status, else raise onerr object as
557 exception.'''
558 exception.'''
558 def py2shell(val):
559 def py2shell(val):
559 'convert python object into string that is useful to shell'
560 'convert python object into string that is useful to shell'
560 if val in (None, False):
561 if val in (None, False):
561 return '0'
562 return '0'
562 if val == True:
563 if val == True:
563 return '1'
564 return '1'
564 return str(val)
565 return str(val)
565 oldenv = {}
566 oldenv = {}
566 for k in environ:
567 for k in environ:
567 oldenv[k] = os.environ.get(k)
568 oldenv[k] = os.environ.get(k)
568 if cwd is not None:
569 if cwd is not None:
569 oldcwd = os.getcwd()
570 oldcwd = os.getcwd()
570 origcmd = cmd
571 origcmd = cmd
571 if os.name == 'nt':
572 if os.name == 'nt':
572 cmd = '"%s"' % cmd
573 cmd = '"%s"' % cmd
573 try:
574 try:
574 for k, v in environ.iteritems():
575 for k, v in environ.iteritems():
575 os.environ[k] = py2shell(v)
576 os.environ[k] = py2shell(v)
576 if 'HG' not in os.environ:
577 if 'HG' not in os.environ:
577 os.environ['HG'] = _hgexecutable
578 os.environ['HG'] = _hgexecutable
578 if cwd is not None and oldcwd != cwd:
579 if cwd is not None and oldcwd != cwd:
579 os.chdir(cwd)
580 os.chdir(cwd)
580 rc = os.system(cmd)
581 rc = os.system(cmd)
581 if rc and onerr:
582 if rc and onerr:
582 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
583 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
583 explain_exit(rc)[0])
584 explain_exit(rc)[0])
584 if errprefix:
585 if errprefix:
585 errmsg = '%s: %s' % (errprefix, errmsg)
586 errmsg = '%s: %s' % (errprefix, errmsg)
586 try:
587 try:
587 onerr.warn(errmsg + '\n')
588 onerr.warn(errmsg + '\n')
588 except AttributeError:
589 except AttributeError:
589 raise onerr(errmsg)
590 raise onerr(errmsg)
590 return rc
591 return rc
591 finally:
592 finally:
592 for k, v in oldenv.iteritems():
593 for k, v in oldenv.iteritems():
593 if v is None:
594 if v is None:
594 del os.environ[k]
595 del os.environ[k]
595 else:
596 else:
596 os.environ[k] = v
597 os.environ[k] = v
597 if cwd is not None and oldcwd != cwd:
598 if cwd is not None and oldcwd != cwd:
598 os.chdir(oldcwd)
599 os.chdir(oldcwd)
599
600
600 # os.path.lexists is not available on python2.3
601 # os.path.lexists is not available on python2.3
601 def lexists(filename):
602 def lexists(filename):
602 "test whether a file with this name exists. does not follow symlinks"
603 "test whether a file with this name exists. does not follow symlinks"
603 try:
604 try:
604 os.lstat(filename)
605 os.lstat(filename)
605 except:
606 except:
606 return False
607 return False
607 return True
608 return True
608
609
609 def rename(src, dst):
610 def rename(src, dst):
610 """forcibly rename a file"""
611 """forcibly rename a file"""
611 try:
612 try:
612 os.rename(src, dst)
613 os.rename(src, dst)
613 except OSError, err:
614 except OSError, err:
614 # on windows, rename to existing file is not allowed, so we
615 # on windows, rename to existing file is not allowed, so we
615 # must delete destination first. but if file is open, unlink
616 # must delete destination first. but if file is open, unlink
616 # schedules it for delete but does not delete it. rename
617 # schedules it for delete but does not delete it. rename
617 # happens immediately even for open files, so we create
618 # happens immediately even for open files, so we create
618 # temporary file, delete it, rename destination to that name,
619 # temporary file, delete it, rename destination to that name,
619 # then delete that. then rename is safe to do.
620 # then delete that. then rename is safe to do.
620 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
621 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
621 os.close(fd)
622 os.close(fd)
622 os.unlink(temp)
623 os.unlink(temp)
623 os.rename(dst, temp)
624 os.rename(dst, temp)
624 os.unlink(temp)
625 os.unlink(temp)
625 os.rename(src, dst)
626 os.rename(src, dst)
626
627
627 def unlink(f):
628 def unlink(f):
628 """unlink and remove the directory if it is empty"""
629 """unlink and remove the directory if it is empty"""
629 os.unlink(f)
630 os.unlink(f)
630 # try removing directories that might now be empty
631 # try removing directories that might now be empty
631 try:
632 try:
632 os.removedirs(os.path.dirname(f))
633 os.removedirs(os.path.dirname(f))
633 except OSError:
634 except OSError:
634 pass
635 pass
635
636
636 def copyfile(src, dest):
637 def copyfile(src, dest):
637 "copy a file, preserving mode"
638 "copy a file, preserving mode"
638 if os.path.islink(src):
639 if os.path.islink(src):
639 try:
640 try:
640 os.unlink(dest)
641 os.unlink(dest)
641 except:
642 except:
642 pass
643 pass
643 os.symlink(os.readlink(src), dest)
644 os.symlink(os.readlink(src), dest)
644 else:
645 else:
645 try:
646 try:
646 shutil.copyfile(src, dest)
647 shutil.copyfile(src, dest)
647 shutil.copymode(src, dest)
648 shutil.copymode(src, dest)
648 except shutil.Error, inst:
649 except shutil.Error, inst:
649 raise Abort(str(inst))
650 raise Abort(str(inst))
650
651
651 def copyfiles(src, dst, hardlink=None):
652 def copyfiles(src, dst, hardlink=None):
652 """Copy a directory tree using hardlinks if possible"""
653 """Copy a directory tree using hardlinks if possible"""
653
654
654 if hardlink is None:
655 if hardlink is None:
655 hardlink = (os.stat(src).st_dev ==
656 hardlink = (os.stat(src).st_dev ==
656 os.stat(os.path.dirname(dst)).st_dev)
657 os.stat(os.path.dirname(dst)).st_dev)
657
658
658 if os.path.isdir(src):
659 if os.path.isdir(src):
659 os.mkdir(dst)
660 os.mkdir(dst)
660 for name in os.listdir(src):
661 for name in os.listdir(src):
661 srcname = os.path.join(src, name)
662 srcname = os.path.join(src, name)
662 dstname = os.path.join(dst, name)
663 dstname = os.path.join(dst, name)
663 copyfiles(srcname, dstname, hardlink)
664 copyfiles(srcname, dstname, hardlink)
664 else:
665 else:
665 if hardlink:
666 if hardlink:
666 try:
667 try:
667 os_link(src, dst)
668 os_link(src, dst)
668 except (IOError, OSError):
669 except (IOError, OSError):
669 hardlink = False
670 hardlink = False
670 shutil.copy(src, dst)
671 shutil.copy(src, dst)
671 else:
672 else:
672 shutil.copy(src, dst)
673 shutil.copy(src, dst)
673
674
674 def audit_path(path):
675 def audit_path(path):
675 """Abort if path contains dangerous components"""
676 """Abort if path contains dangerous components"""
676 parts = os.path.normcase(path).split(os.sep)
677 parts = os.path.normcase(path).split(os.sep)
677 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
678 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
678 or os.pardir in parts):
679 or os.pardir in parts):
679 raise Abort(_("path contains illegal component: %s") % path)
680 raise Abort(_("path contains illegal component: %s") % path)
680
681
681 def _makelock_file(info, pathname):
682 def _makelock_file(info, pathname):
682 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
683 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
683 os.write(ld, info)
684 os.write(ld, info)
684 os.close(ld)
685 os.close(ld)
685
686
686 def _readlock_file(pathname):
687 def _readlock_file(pathname):
687 return posixfile(pathname).read()
688 return posixfile(pathname).read()
688
689
689 def nlinks(pathname):
690 def nlinks(pathname):
690 """Return number of hardlinks for the given file."""
691 """Return number of hardlinks for the given file."""
691 return os.lstat(pathname).st_nlink
692 return os.lstat(pathname).st_nlink
692
693
693 if hasattr(os, 'link'):
694 if hasattr(os, 'link'):
694 os_link = os.link
695 os_link = os.link
695 else:
696 else:
696 def os_link(src, dst):
697 def os_link(src, dst):
697 raise OSError(0, _("Hardlinks not supported"))
698 raise OSError(0, _("Hardlinks not supported"))
698
699
699 def fstat(fp):
700 def fstat(fp):
700 '''stat file object that may not have fileno method.'''
701 '''stat file object that may not have fileno method.'''
701 try:
702 try:
702 return os.fstat(fp.fileno())
703 return os.fstat(fp.fileno())
703 except AttributeError:
704 except AttributeError:
704 return os.stat(fp.name)
705 return os.stat(fp.name)
705
706
706 posixfile = file
707 posixfile = file
707
708
708 def is_win_9x():
709 def is_win_9x():
709 '''return true if run on windows 95, 98 or me.'''
710 '''return true if run on windows 95, 98 or me.'''
710 try:
711 try:
711 return sys.getwindowsversion()[3] == 1
712 return sys.getwindowsversion()[3] == 1
712 except AttributeError:
713 except AttributeError:
713 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
714 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
714
715
715 getuser_fallback = None
716 getuser_fallback = None
716
717
717 def getuser():
718 def getuser():
718 '''return name of current user'''
719 '''return name of current user'''
719 try:
720 try:
720 return getpass.getuser()
721 return getpass.getuser()
721 except ImportError:
722 except ImportError:
722 # import of pwd will fail on windows - try fallback
723 # import of pwd will fail on windows - try fallback
723 if getuser_fallback:
724 if getuser_fallback:
724 return getuser_fallback()
725 return getuser_fallback()
725 # raised if win32api not available
726 # raised if win32api not available
726 raise Abort(_('user name not available - set USERNAME '
727 raise Abort(_('user name not available - set USERNAME '
727 'environment variable'))
728 'environment variable'))
728
729
729 def username(uid=None):
730 def username(uid=None):
730 """Return the name of the user with the given uid.
731 """Return the name of the user with the given uid.
731
732
732 If uid is None, return the name of the current user."""
733 If uid is None, return the name of the current user."""
733 try:
734 try:
734 import pwd
735 import pwd
735 if uid is None:
736 if uid is None:
736 uid = os.getuid()
737 uid = os.getuid()
737 try:
738 try:
738 return pwd.getpwuid(uid)[0]
739 return pwd.getpwuid(uid)[0]
739 except KeyError:
740 except KeyError:
740 return str(uid)
741 return str(uid)
741 except ImportError:
742 except ImportError:
742 return None
743 return None
743
744
744 def groupname(gid=None):
745 def groupname(gid=None):
745 """Return the name of the group with the given gid.
746 """Return the name of the group with the given gid.
746
747
747 If gid is None, return the name of the current group."""
748 If gid is None, return the name of the current group."""
748 try:
749 try:
749 import grp
750 import grp
750 if gid is None:
751 if gid is None:
751 gid = os.getgid()
752 gid = os.getgid()
752 try:
753 try:
753 return grp.getgrgid(gid)[0]
754 return grp.getgrgid(gid)[0]
754 except KeyError:
755 except KeyError:
755 return str(gid)
756 return str(gid)
756 except ImportError:
757 except ImportError:
757 return None
758 return None
758
759
759 # File system features
760 # File system features
760
761
761 def checkfolding(path):
762 def checkfolding(path):
762 """
763 """
763 Check whether the given path is on a case-sensitive filesystem
764 Check whether the given path is on a case-sensitive filesystem
764
765
765 Requires a path (like /foo/.hg) ending with a foldable final
766 Requires a path (like /foo/.hg) ending with a foldable final
766 directory component.
767 directory component.
767 """
768 """
768 s1 = os.stat(path)
769 s1 = os.stat(path)
769 d, b = os.path.split(path)
770 d, b = os.path.split(path)
770 p2 = os.path.join(d, b.upper())
771 p2 = os.path.join(d, b.upper())
771 if path == p2:
772 if path == p2:
772 p2 = os.path.join(d, b.lower())
773 p2 = os.path.join(d, b.lower())
773 try:
774 try:
774 s2 = os.stat(p2)
775 s2 = os.stat(p2)
775 if s2 == s1:
776 if s2 == s1:
776 return False
777 return False
777 return True
778 return True
778 except:
779 except:
779 return True
780 return True
780
781
781 def checkexec(path):
782 def checkexec(path):
782 """
783 """
783 Check whether the given path is on a filesystem with UNIX-like exec flags
784 Check whether the given path is on a filesystem with UNIX-like exec flags
784
785
785 Requires a directory (like /foo/.hg)
786 Requires a directory (like /foo/.hg)
786 """
787 """
787 fh, fn = tempfile.mkstemp("", "", path)
788 fh, fn = tempfile.mkstemp("", "", path)
788 os.close(fh)
789 os.close(fh)
789 m = os.stat(fn).st_mode
790 m = os.stat(fn).st_mode
790 os.chmod(fn, m ^ 0111)
791 os.chmod(fn, m ^ 0111)
791 r = (os.stat(fn).st_mode != m)
792 r = (os.stat(fn).st_mode != m)
792 os.unlink(fn)
793 os.unlink(fn)
793 return r
794 return r
794
795
795 def execfunc(path, fallback):
796 def execfunc(path, fallback):
796 '''return an is_exec() function with default to fallback'''
797 '''return an is_exec() function with default to fallback'''
797 if checkexec(path):
798 if checkexec(path):
798 return lambda x: is_exec(os.path.join(path, x))
799 return lambda x: is_exec(os.path.join(path, x))
799 return fallback
800 return fallback
800
801
801 def checklink(path):
802 def checklink(path):
802 """check whether the given path is on a symlink-capable filesystem"""
803 """check whether the given path is on a symlink-capable filesystem"""
803 # mktemp is not racy because symlink creation will fail if the
804 # mktemp is not racy because symlink creation will fail if the
804 # file already exists
805 # file already exists
805 name = tempfile.mktemp(dir=path)
806 name = tempfile.mktemp(dir=path)
806 try:
807 try:
807 os.symlink(".", name)
808 os.symlink(".", name)
808 os.unlink(name)
809 os.unlink(name)
809 return True
810 return True
810 except (OSError, AttributeError):
811 except (OSError, AttributeError):
811 return False
812 return False
812
813
813 def linkfunc(path, fallback):
814 def linkfunc(path, fallback):
814 '''return an is_link() function with default to fallback'''
815 '''return an is_link() function with default to fallback'''
815 if checklink(path):
816 if checklink(path):
816 return lambda x: os.path.islink(os.path.join(path, x))
817 return lambda x: os.path.islink(os.path.join(path, x))
817 return fallback
818 return fallback
818
819
819 _umask = os.umask(0)
820 _umask = os.umask(0)
820 os.umask(_umask)
821 os.umask(_umask)
821
822
822 def needbinarypatch():
823 def needbinarypatch():
823 """return True if patches should be applied in binary mode by default."""
824 """return True if patches should be applied in binary mode by default."""
824 return os.name == 'nt'
825 return os.name == 'nt'
825
826
826 # Platform specific variants
827 # Platform specific variants
827 if os.name == 'nt':
828 if os.name == 'nt':
828 import msvcrt
829 import msvcrt
829 nulldev = 'NUL:'
830 nulldev = 'NUL:'
830
831
831 class winstdout:
832 class winstdout:
832 '''stdout on windows misbehaves if sent through a pipe'''
833 '''stdout on windows misbehaves if sent through a pipe'''
833
834
834 def __init__(self, fp):
835 def __init__(self, fp):
835 self.fp = fp
836 self.fp = fp
836
837
837 def __getattr__(self, key):
838 def __getattr__(self, key):
838 return getattr(self.fp, key)
839 return getattr(self.fp, key)
839
840
840 def close(self):
841 def close(self):
841 try:
842 try:
842 self.fp.close()
843 self.fp.close()
843 except: pass
844 except: pass
844
845
845 def write(self, s):
846 def write(self, s):
846 try:
847 try:
847 return self.fp.write(s)
848 return self.fp.write(s)
848 except IOError, inst:
849 except IOError, inst:
849 if inst.errno != 0: raise
850 if inst.errno != 0: raise
850 self.close()
851 self.close()
851 raise IOError(errno.EPIPE, 'Broken pipe')
852 raise IOError(errno.EPIPE, 'Broken pipe')
852
853
853 def flush(self):
854 def flush(self):
854 try:
855 try:
855 return self.fp.flush()
856 return self.fp.flush()
856 except IOError, inst:
857 except IOError, inst:
857 if inst.errno != errno.EINVAL: raise
858 if inst.errno != errno.EINVAL: raise
858 self.close()
859 self.close()
859 raise IOError(errno.EPIPE, 'Broken pipe')
860 raise IOError(errno.EPIPE, 'Broken pipe')
860
861
861 sys.stdout = winstdout(sys.stdout)
862 sys.stdout = winstdout(sys.stdout)
862
863
863 def system_rcpath():
864 def system_rcpath():
864 try:
865 try:
865 return system_rcpath_win32()
866 return system_rcpath_win32()
866 except:
867 except:
867 return [r'c:\mercurial\mercurial.ini']
868 return [r'c:\mercurial\mercurial.ini']
868
869
869 def user_rcpath():
870 def user_rcpath():
870 '''return os-specific hgrc search path to the user dir'''
871 '''return os-specific hgrc search path to the user dir'''
871 try:
872 try:
872 userrc = user_rcpath_win32()
873 userrc = user_rcpath_win32()
873 except:
874 except:
874 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
875 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
875 path = [userrc]
876 path = [userrc]
876 userprofile = os.environ.get('USERPROFILE')
877 userprofile = os.environ.get('USERPROFILE')
877 if userprofile:
878 if userprofile:
878 path.append(os.path.join(userprofile, 'mercurial.ini'))
879 path.append(os.path.join(userprofile, 'mercurial.ini'))
879 return path
880 return path
880
881
881 def parse_patch_output(output_line):
882 def parse_patch_output(output_line):
882 """parses the output produced by patch and returns the file name"""
883 """parses the output produced by patch and returns the file name"""
883 pf = output_line[14:]
884 pf = output_line[14:]
884 if pf[0] == '`':
885 if pf[0] == '`':
885 pf = pf[1:-1] # Remove the quotes
886 pf = pf[1:-1] # Remove the quotes
886 return pf
887 return pf
887
888
888 def testpid(pid):
889 def testpid(pid):
889 '''return False if pid dead, True if running or not known'''
890 '''return False if pid dead, True if running or not known'''
890 return True
891 return True
891
892
892 def set_exec(f, mode):
893 def set_exec(f, mode):
893 pass
894 pass
894
895
895 def set_link(f, mode):
896 def set_link(f, mode):
896 pass
897 pass
897
898
898 def set_binary(fd):
899 def set_binary(fd):
899 msvcrt.setmode(fd.fileno(), os.O_BINARY)
900 msvcrt.setmode(fd.fileno(), os.O_BINARY)
900
901
901 def pconvert(path):
902 def pconvert(path):
902 return path.replace("\\", "/")
903 return path.replace("\\", "/")
903
904
904 def localpath(path):
905 def localpath(path):
905 return path.replace('/', '\\')
906 return path.replace('/', '\\')
906
907
907 def normpath(path):
908 def normpath(path):
908 return pconvert(os.path.normpath(path))
909 return pconvert(os.path.normpath(path))
909
910
910 makelock = _makelock_file
911 makelock = _makelock_file
911 readlock = _readlock_file
912 readlock = _readlock_file
912
913
913 def samestat(s1, s2):
914 def samestat(s1, s2):
914 return False
915 return False
915
916
916 # A sequence of backslashes is special iff it precedes a double quote:
917 # A sequence of backslashes is special iff it precedes a double quote:
917 # - if there's an even number of backslashes, the double quote is not
918 # - if there's an even number of backslashes, the double quote is not
918 # quoted (i.e. it ends the quoted region)
919 # quoted (i.e. it ends the quoted region)
919 # - if there's an odd number of backslashes, the double quote is quoted
920 # - if there's an odd number of backslashes, the double quote is quoted
920 # - in both cases, every pair of backslashes is unquoted into a single
921 # - in both cases, every pair of backslashes is unquoted into a single
921 # backslash
922 # backslash
922 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
923 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
923 # So, to quote a string, we must surround it in double quotes, double
924 # So, to quote a string, we must surround it in double quotes, double
924 # the number of backslashes that preceed double quotes and add another
925 # the number of backslashes that preceed double quotes and add another
925 # backslash before every double quote (being careful with the double
926 # backslash before every double quote (being careful with the double
926 # quote we've appended to the end)
927 # quote we've appended to the end)
927 _quotere = None
928 _quotere = None
928 def shellquote(s):
929 def shellquote(s):
929 global _quotere
930 global _quotere
930 if _quotere is None:
931 if _quotere is None:
931 _quotere = re.compile(r'(\\*)("|\\$)')
932 _quotere = re.compile(r'(\\*)("|\\$)')
932 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
933 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
933
934
934 def explain_exit(code):
935 def explain_exit(code):
935 return _("exited with status %d") % code, code
936 return _("exited with status %d") % code, code
936
937
937 # if you change this stub into a real check, please try to implement the
938 # if you change this stub into a real check, please try to implement the
938 # username and groupname functions above, too.
939 # username and groupname functions above, too.
939 def isowner(fp, st=None):
940 def isowner(fp, st=None):
940 return True
941 return True
941
942
942 def find_in_path(name, path, default=None):
943 def find_in_path(name, path, default=None):
943 '''find name in search path. path can be string (will be split
944 '''find name in search path. path can be string (will be split
944 with os.pathsep), or iterable thing that returns strings. if name
945 with os.pathsep), or iterable thing that returns strings. if name
945 found, return path to name. else return default. name is looked up
946 found, return path to name. else return default. name is looked up
946 using cmd.exe rules, using PATHEXT.'''
947 using cmd.exe rules, using PATHEXT.'''
947 if isinstance(path, str):
948 if isinstance(path, str):
948 path = path.split(os.pathsep)
949 path = path.split(os.pathsep)
949
950
950 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
951 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
951 pathext = pathext.lower().split(os.pathsep)
952 pathext = pathext.lower().split(os.pathsep)
952 isexec = os.path.splitext(name)[1].lower() in pathext
953 isexec = os.path.splitext(name)[1].lower() in pathext
953
954
954 for p in path:
955 for p in path:
955 p_name = os.path.join(p, name)
956 p_name = os.path.join(p, name)
956
957
957 if isexec and os.path.exists(p_name):
958 if isexec and os.path.exists(p_name):
958 return p_name
959 return p_name
959
960
960 for ext in pathext:
961 for ext in pathext:
961 p_name_ext = p_name + ext
962 p_name_ext = p_name + ext
962 if os.path.exists(p_name_ext):
963 if os.path.exists(p_name_ext):
963 return p_name_ext
964 return p_name_ext
964 return default
965 return default
965
966
966 try:
967 try:
967 # override functions with win32 versions if possible
968 # override functions with win32 versions if possible
968 from util_win32 import *
969 from util_win32 import *
969 if not is_win_9x():
970 if not is_win_9x():
970 posixfile = posixfile_nt
971 posixfile = posixfile_nt
971 except ImportError:
972 except ImportError:
972 pass
973 pass
973
974
974 else:
975 else:
975 nulldev = '/dev/null'
976 nulldev = '/dev/null'
976
977
977 def rcfiles(path):
978 def rcfiles(path):
978 rcs = [os.path.join(path, 'hgrc')]
979 rcs = [os.path.join(path, 'hgrc')]
979 rcdir = os.path.join(path, 'hgrc.d')
980 rcdir = os.path.join(path, 'hgrc.d')
980 try:
981 try:
981 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
982 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
982 if f.endswith(".rc")])
983 if f.endswith(".rc")])
983 except OSError:
984 except OSError:
984 pass
985 pass
985 return rcs
986 return rcs
986
987
987 def system_rcpath():
988 def system_rcpath():
988 path = []
989 path = []
989 # old mod_python does not set sys.argv
990 # old mod_python does not set sys.argv
990 if len(getattr(sys, 'argv', [])) > 0:
991 if len(getattr(sys, 'argv', [])) > 0:
991 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
992 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
992 '/../etc/mercurial'))
993 '/../etc/mercurial'))
993 path.extend(rcfiles('/etc/mercurial'))
994 path.extend(rcfiles('/etc/mercurial'))
994 return path
995 return path
995
996
996 def user_rcpath():
997 def user_rcpath():
997 return [os.path.expanduser('~/.hgrc')]
998 return [os.path.expanduser('~/.hgrc')]
998
999
999 def parse_patch_output(output_line):
1000 def parse_patch_output(output_line):
1000 """parses the output produced by patch and returns the file name"""
1001 """parses the output produced by patch and returns the file name"""
1001 pf = output_line[14:]
1002 pf = output_line[14:]
1002 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1003 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1003 pf = pf[1:-1] # Remove the quotes
1004 pf = pf[1:-1] # Remove the quotes
1004 return pf
1005 return pf
1005
1006
1006 def is_exec(f):
1007 def is_exec(f):
1007 """check whether a file is executable"""
1008 """check whether a file is executable"""
1008 return (os.lstat(f).st_mode & 0100 != 0)
1009 return (os.lstat(f).st_mode & 0100 != 0)
1009
1010
1010 def set_exec(f, mode):
1011 def set_exec(f, mode):
1011 s = os.lstat(f).st_mode
1012 s = os.lstat(f).st_mode
1012 if (s & 0100 != 0) == mode:
1013 if (s & 0100 != 0) == mode:
1013 return
1014 return
1014 if mode:
1015 if mode:
1015 # Turn on +x for every +r bit when making a file executable
1016 # Turn on +x for every +r bit when making a file executable
1016 # and obey umask.
1017 # and obey umask.
1017 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1018 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1018 else:
1019 else:
1019 os.chmod(f, s & 0666)
1020 os.chmod(f, s & 0666)
1020
1021
1021 def set_link(f, mode):
1022 def set_link(f, mode):
1022 """make a file a symbolic link/regular file
1023 """make a file a symbolic link/regular file
1023
1024
1024 if a file is changed to a link, its contents become the link data
1025 if a file is changed to a link, its contents become the link data
1025 if a link is changed to a file, its link data become its contents
1026 if a link is changed to a file, its link data become its contents
1026 """
1027 """
1027
1028
1028 m = os.path.islink(f)
1029 m = os.path.islink(f)
1029 if m == bool(mode):
1030 if m == bool(mode):
1030 return
1031 return
1031
1032
1032 if mode: # switch file to link
1033 if mode: # switch file to link
1033 data = file(f).read()
1034 data = file(f).read()
1034 os.unlink(f)
1035 os.unlink(f)
1035 os.symlink(data, f)
1036 os.symlink(data, f)
1036 else:
1037 else:
1037 data = os.readlink(f)
1038 data = os.readlink(f)
1038 os.unlink(f)
1039 os.unlink(f)
1039 file(f, "w").write(data)
1040 file(f, "w").write(data)
1040
1041
1041 def set_binary(fd):
1042 def set_binary(fd):
1042 pass
1043 pass
1043
1044
1044 def pconvert(path):
1045 def pconvert(path):
1045 return path
1046 return path
1046
1047
1047 def localpath(path):
1048 def localpath(path):
1048 return path
1049 return path
1049
1050
1050 normpath = os.path.normpath
1051 normpath = os.path.normpath
1051 samestat = os.path.samestat
1052 samestat = os.path.samestat
1052
1053
1053 def makelock(info, pathname):
1054 def makelock(info, pathname):
1054 try:
1055 try:
1055 os.symlink(info, pathname)
1056 os.symlink(info, pathname)
1056 except OSError, why:
1057 except OSError, why:
1057 if why.errno == errno.EEXIST:
1058 if why.errno == errno.EEXIST:
1058 raise
1059 raise
1059 else:
1060 else:
1060 _makelock_file(info, pathname)
1061 _makelock_file(info, pathname)
1061
1062
1062 def readlock(pathname):
1063 def readlock(pathname):
1063 try:
1064 try:
1064 return os.readlink(pathname)
1065 return os.readlink(pathname)
1065 except OSError, why:
1066 except OSError, why:
1066 if why.errno == errno.EINVAL:
1067 if why.errno == errno.EINVAL:
1067 return _readlock_file(pathname)
1068 return _readlock_file(pathname)
1068 else:
1069 else:
1069 raise
1070 raise
1070
1071
1071 def shellquote(s):
1072 def shellquote(s):
1072 return "'%s'" % s.replace("'", "'\\''")
1073 return "'%s'" % s.replace("'", "'\\''")
1073
1074
1074 def testpid(pid):
1075 def testpid(pid):
1075 '''return False if pid dead, True if running or not sure'''
1076 '''return False if pid dead, True if running or not sure'''
1076 try:
1077 try:
1077 os.kill(pid, 0)
1078 os.kill(pid, 0)
1078 return True
1079 return True
1079 except OSError, inst:
1080 except OSError, inst:
1080 return inst.errno != errno.ESRCH
1081 return inst.errno != errno.ESRCH
1081
1082
1082 def explain_exit(code):
1083 def explain_exit(code):
1083 """return a 2-tuple (desc, code) describing a process's status"""
1084 """return a 2-tuple (desc, code) describing a process's status"""
1084 if os.WIFEXITED(code):
1085 if os.WIFEXITED(code):
1085 val = os.WEXITSTATUS(code)
1086 val = os.WEXITSTATUS(code)
1086 return _("exited with status %d") % val, val
1087 return _("exited with status %d") % val, val
1087 elif os.WIFSIGNALED(code):
1088 elif os.WIFSIGNALED(code):
1088 val = os.WTERMSIG(code)
1089 val = os.WTERMSIG(code)
1089 return _("killed by signal %d") % val, val
1090 return _("killed by signal %d") % val, val
1090 elif os.WIFSTOPPED(code):
1091 elif os.WIFSTOPPED(code):
1091 val = os.WSTOPSIG(code)
1092 val = os.WSTOPSIG(code)
1092 return _("stopped by signal %d") % val, val
1093 return _("stopped by signal %d") % val, val
1093 raise ValueError(_("invalid exit code"))
1094 raise ValueError(_("invalid exit code"))
1094
1095
1095 def isowner(fp, st=None):
1096 def isowner(fp, st=None):
1096 """Return True if the file object f belongs to the current user.
1097 """Return True if the file object f belongs to the current user.
1097
1098
1098 The return value of a util.fstat(f) may be passed as the st argument.
1099 The return value of a util.fstat(f) may be passed as the st argument.
1099 """
1100 """
1100 if st is None:
1101 if st is None:
1101 st = fstat(fp)
1102 st = fstat(fp)
1102 return st.st_uid == os.getuid()
1103 return st.st_uid == os.getuid()
1103
1104
1104 def find_in_path(name, path, default=None):
1105 def find_in_path(name, path, default=None):
1105 '''find name in search path. path can be string (will be split
1106 '''find name in search path. path can be string (will be split
1106 with os.pathsep), or iterable thing that returns strings. if name
1107 with os.pathsep), or iterable thing that returns strings. if name
1107 found, return path to name. else return default.'''
1108 found, return path to name. else return default.'''
1108 if isinstance(path, str):
1109 if isinstance(path, str):
1109 path = path.split(os.pathsep)
1110 path = path.split(os.pathsep)
1110 for p in path:
1111 for p in path:
1111 p_name = os.path.join(p, name)
1112 p_name = os.path.join(p, name)
1112 if os.path.exists(p_name):
1113 if os.path.exists(p_name):
1113 return p_name
1114 return p_name
1114 return default
1115 return default
1115
1116
1116 def set_signal_handler():
1117 def set_signal_handler():
1117 pass
1118 pass
1118
1119
1119 def find_exe(name, default=None):
1120 def find_exe(name, default=None):
1120 '''find path of an executable.
1121 '''find path of an executable.
1121 if name contains a path component, return it as is. otherwise,
1122 if name contains a path component, return it as is. otherwise,
1122 use normal executable search path.'''
1123 use normal executable search path.'''
1123
1124
1124 if os.sep in name:
1125 if os.sep in name:
1125 # don't check the executable bit. if the file isn't
1126 # don't check the executable bit. if the file isn't
1126 # executable, whoever tries to actually run it will give a
1127 # executable, whoever tries to actually run it will give a
1127 # much more useful error message.
1128 # much more useful error message.
1128 return name
1129 return name
1129 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1130 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1130
1131
1131 def _buildencodefun():
1132 def _buildencodefun():
1132 e = '_'
1133 e = '_'
1133 win_reserved = [ord(x) for x in '\\:*?"<>|']
1134 win_reserved = [ord(x) for x in '\\:*?"<>|']
1134 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1135 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1135 for x in (range(32) + range(126, 256) + win_reserved):
1136 for x in (range(32) + range(126, 256) + win_reserved):
1136 cmap[chr(x)] = "~%02x" % x
1137 cmap[chr(x)] = "~%02x" % x
1137 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1138 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1138 cmap[chr(x)] = e + chr(x).lower()
1139 cmap[chr(x)] = e + chr(x).lower()
1139 dmap = {}
1140 dmap = {}
1140 for k, v in cmap.iteritems():
1141 for k, v in cmap.iteritems():
1141 dmap[v] = k
1142 dmap[v] = k
1142 def decode(s):
1143 def decode(s):
1143 i = 0
1144 i = 0
1144 while i < len(s):
1145 while i < len(s):
1145 for l in xrange(1, 4):
1146 for l in xrange(1, 4):
1146 try:
1147 try:
1147 yield dmap[s[i:i+l]]
1148 yield dmap[s[i:i+l]]
1148 i += l
1149 i += l
1149 break
1150 break
1150 except KeyError:
1151 except KeyError:
1151 pass
1152 pass
1152 else:
1153 else:
1153 raise KeyError
1154 raise KeyError
1154 return (lambda s: "".join([cmap[c] for c in s]),
1155 return (lambda s: "".join([cmap[c] for c in s]),
1155 lambda s: "".join(list(decode(s))))
1156 lambda s: "".join(list(decode(s))))
1156
1157
1157 encodefilename, decodefilename = _buildencodefun()
1158 encodefilename, decodefilename = _buildencodefun()
1158
1159
1159 def encodedopener(openerfn, fn):
1160 def encodedopener(openerfn, fn):
1160 def o(path, *args, **kw):
1161 def o(path, *args, **kw):
1161 return openerfn(fn(path), *args, **kw)
1162 return openerfn(fn(path), *args, **kw)
1162 return o
1163 return o
1163
1164
1164 def opener(base, audit=True):
1165 def opener(base, audit=True):
1165 """
1166 """
1166 return a function that opens files relative to base
1167 return a function that opens files relative to base
1167
1168
1168 this function is used to hide the details of COW semantics and
1169 this function is used to hide the details of COW semantics and
1169 remote file access from higher level code.
1170 remote file access from higher level code.
1170 """
1171 """
1171 def mktempcopy(name, emptyok=False):
1172 def mktempcopy(name, emptyok=False):
1172 d, fn = os.path.split(name)
1173 d, fn = os.path.split(name)
1173 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1174 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1174 os.close(fd)
1175 os.close(fd)
1175 # Temporary files are created with mode 0600, which is usually not
1176 # Temporary files are created with mode 0600, which is usually not
1176 # what we want. If the original file already exists, just copy
1177 # what we want. If the original file already exists, just copy
1177 # its mode. Otherwise, manually obey umask.
1178 # its mode. Otherwise, manually obey umask.
1178 try:
1179 try:
1179 st_mode = os.lstat(name).st_mode
1180 st_mode = os.lstat(name).st_mode
1180 except OSError, inst:
1181 except OSError, inst:
1181 if inst.errno != errno.ENOENT:
1182 if inst.errno != errno.ENOENT:
1182 raise
1183 raise
1183 st_mode = 0666 & ~_umask
1184 st_mode = 0666 & ~_umask
1184 os.chmod(temp, st_mode)
1185 os.chmod(temp, st_mode)
1185 if emptyok:
1186 if emptyok:
1186 return temp
1187 return temp
1187 try:
1188 try:
1188 try:
1189 try:
1189 ifp = posixfile(name, "rb")
1190 ifp = posixfile(name, "rb")
1190 except IOError, inst:
1191 except IOError, inst:
1191 if inst.errno == errno.ENOENT:
1192 if inst.errno == errno.ENOENT:
1192 return temp
1193 return temp
1193 if not getattr(inst, 'filename', None):
1194 if not getattr(inst, 'filename', None):
1194 inst.filename = name
1195 inst.filename = name
1195 raise
1196 raise
1196 ofp = posixfile(temp, "wb")
1197 ofp = posixfile(temp, "wb")
1197 for chunk in filechunkiter(ifp):
1198 for chunk in filechunkiter(ifp):
1198 ofp.write(chunk)
1199 ofp.write(chunk)
1199 ifp.close()
1200 ifp.close()
1200 ofp.close()
1201 ofp.close()
1201 except:
1202 except:
1202 try: os.unlink(temp)
1203 try: os.unlink(temp)
1203 except: pass
1204 except: pass
1204 raise
1205 raise
1205 return temp
1206 return temp
1206
1207
1207 class atomictempfile(posixfile):
1208 class atomictempfile(posixfile):
1208 """the file will only be copied when rename is called"""
1209 """the file will only be copied when rename is called"""
1209 def __init__(self, name, mode):
1210 def __init__(self, name, mode):
1210 self.__name = name
1211 self.__name = name
1211 self.temp = mktempcopy(name, emptyok=('w' in mode))
1212 self.temp = mktempcopy(name, emptyok=('w' in mode))
1212 posixfile.__init__(self, self.temp, mode)
1213 posixfile.__init__(self, self.temp, mode)
1213 def rename(self):
1214 def rename(self):
1214 if not self.closed:
1215 if not self.closed:
1215 posixfile.close(self)
1216 posixfile.close(self)
1216 rename(self.temp, localpath(self.__name))
1217 rename(self.temp, localpath(self.__name))
1217 def __del__(self):
1218 def __del__(self):
1218 if not self.closed:
1219 if not self.closed:
1219 try:
1220 try:
1220 os.unlink(self.temp)
1221 os.unlink(self.temp)
1221 except: pass
1222 except: pass
1222 posixfile.close(self)
1223 posixfile.close(self)
1223
1224
1224 def o(path, mode="r", text=False, atomictemp=False):
1225 def o(path, mode="r", text=False, atomictemp=False):
1225 if audit:
1226 if audit:
1226 audit_path(path)
1227 audit_path(path)
1227 f = os.path.join(base, path)
1228 f = os.path.join(base, path)
1228
1229
1229 if not text:
1230 if not text:
1230 mode += "b" # for that other OS
1231 mode += "b" # for that other OS
1231
1232
1232 if mode[0] != "r":
1233 if mode[0] != "r":
1233 try:
1234 try:
1234 nlink = nlinks(f)
1235 nlink = nlinks(f)
1235 except OSError:
1236 except OSError:
1236 nlink = 0
1237 nlink = 0
1237 d = os.path.dirname(f)
1238 d = os.path.dirname(f)
1238 if not os.path.isdir(d):
1239 if not os.path.isdir(d):
1239 os.makedirs(d)
1240 os.makedirs(d)
1240 if atomictemp:
1241 if atomictemp:
1241 return atomictempfile(f, mode)
1242 return atomictempfile(f, mode)
1242 if nlink > 1:
1243 if nlink > 1:
1243 rename(mktempcopy(f), f)
1244 rename(mktempcopy(f), f)
1244 return posixfile(f, mode)
1245 return posixfile(f, mode)
1245
1246
1246 return o
1247 return o
1247
1248
1248 class chunkbuffer(object):
1249 class chunkbuffer(object):
1249 """Allow arbitrary sized chunks of data to be efficiently read from an
1250 """Allow arbitrary sized chunks of data to be efficiently read from an
1250 iterator over chunks of arbitrary size."""
1251 iterator over chunks of arbitrary size."""
1251
1252
1252 def __init__(self, in_iter, targetsize = 2**16):
1253 def __init__(self, in_iter, targetsize = 2**16):
1253 """in_iter is the iterator that's iterating over the input chunks.
1254 """in_iter is the iterator that's iterating over the input chunks.
1254 targetsize is how big a buffer to try to maintain."""
1255 targetsize is how big a buffer to try to maintain."""
1255 self.in_iter = iter(in_iter)
1256 self.in_iter = iter(in_iter)
1256 self.buf = ''
1257 self.buf = ''
1257 self.targetsize = int(targetsize)
1258 self.targetsize = int(targetsize)
1258 if self.targetsize <= 0:
1259 if self.targetsize <= 0:
1259 raise ValueError(_("targetsize must be greater than 0, was %d") %
1260 raise ValueError(_("targetsize must be greater than 0, was %d") %
1260 targetsize)
1261 targetsize)
1261 self.iterempty = False
1262 self.iterempty = False
1262
1263
1263 def fillbuf(self):
1264 def fillbuf(self):
1264 """Ignore target size; read every chunk from iterator until empty."""
1265 """Ignore target size; read every chunk from iterator until empty."""
1265 if not self.iterempty:
1266 if not self.iterempty:
1266 collector = cStringIO.StringIO()
1267 collector = cStringIO.StringIO()
1267 collector.write(self.buf)
1268 collector.write(self.buf)
1268 for ch in self.in_iter:
1269 for ch in self.in_iter:
1269 collector.write(ch)
1270 collector.write(ch)
1270 self.buf = collector.getvalue()
1271 self.buf = collector.getvalue()
1271 self.iterempty = True
1272 self.iterempty = True
1272
1273
1273 def read(self, l):
1274 def read(self, l):
1274 """Read L bytes of data from the iterator of chunks of data.
1275 """Read L bytes of data from the iterator of chunks of data.
1275 Returns less than L bytes if the iterator runs dry."""
1276 Returns less than L bytes if the iterator runs dry."""
1276 if l > len(self.buf) and not self.iterempty:
1277 if l > len(self.buf) and not self.iterempty:
1277 # Clamp to a multiple of self.targetsize
1278 # Clamp to a multiple of self.targetsize
1278 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1279 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1279 collector = cStringIO.StringIO()
1280 collector = cStringIO.StringIO()
1280 collector.write(self.buf)
1281 collector.write(self.buf)
1281 collected = len(self.buf)
1282 collected = len(self.buf)
1282 for chunk in self.in_iter:
1283 for chunk in self.in_iter:
1283 collector.write(chunk)
1284 collector.write(chunk)
1284 collected += len(chunk)
1285 collected += len(chunk)
1285 if collected >= targetsize:
1286 if collected >= targetsize:
1286 break
1287 break
1287 if collected < targetsize:
1288 if collected < targetsize:
1288 self.iterempty = True
1289 self.iterempty = True
1289 self.buf = collector.getvalue()
1290 self.buf = collector.getvalue()
1290 s, self.buf = self.buf[:l], buffer(self.buf, l)
1291 s, self.buf = self.buf[:l], buffer(self.buf, l)
1291 return s
1292 return s
1292
1293
1293 def filechunkiter(f, size=65536, limit=None):
1294 def filechunkiter(f, size=65536, limit=None):
1294 """Create a generator that produces the data in the file size
1295 """Create a generator that produces the data in the file size
1295 (default 65536) bytes at a time, up to optional limit (default is
1296 (default 65536) bytes at a time, up to optional limit (default is
1296 to read all data). Chunks may be less than size bytes if the
1297 to read all data). Chunks may be less than size bytes if the
1297 chunk is the last chunk in the file, or the file is a socket or
1298 chunk is the last chunk in the file, or the file is a socket or
1298 some other type of file that sometimes reads less data than is
1299 some other type of file that sometimes reads less data than is
1299 requested."""
1300 requested."""
1300 assert size >= 0
1301 assert size >= 0
1301 assert limit is None or limit >= 0
1302 assert limit is None or limit >= 0
1302 while True:
1303 while True:
1303 if limit is None: nbytes = size
1304 if limit is None: nbytes = size
1304 else: nbytes = min(limit, size)
1305 else: nbytes = min(limit, size)
1305 s = nbytes and f.read(nbytes)
1306 s = nbytes and f.read(nbytes)
1306 if not s: break
1307 if not s: break
1307 if limit: limit -= len(s)
1308 if limit: limit -= len(s)
1308 yield s
1309 yield s
1309
1310
1310 def makedate():
1311 def makedate():
1311 lt = time.localtime()
1312 lt = time.localtime()
1312 if lt[8] == 1 and time.daylight:
1313 if lt[8] == 1 and time.daylight:
1313 tz = time.altzone
1314 tz = time.altzone
1314 else:
1315 else:
1315 tz = time.timezone
1316 tz = time.timezone
1316 return time.mktime(lt), tz
1317 return time.mktime(lt), tz
1317
1318
1318 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1319 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1319 """represent a (unixtime, offset) tuple as a localized time.
1320 """represent a (unixtime, offset) tuple as a localized time.
1320 unixtime is seconds since the epoch, and offset is the time zone's
1321 unixtime is seconds since the epoch, and offset is the time zone's
1321 number of seconds away from UTC. if timezone is false, do not
1322 number of seconds away from UTC. if timezone is false, do not
1322 append time zone to string."""
1323 append time zone to string."""
1323 t, tz = date or makedate()
1324 t, tz = date or makedate()
1324 s = time.strftime(format, time.gmtime(float(t) - tz))
1325 s = time.strftime(format, time.gmtime(float(t) - tz))
1325 if timezone:
1326 if timezone:
1326 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1327 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1327 return s
1328 return s
1328
1329
1329 def strdate(string, format, defaults):
1330 def strdate(string, format, defaults):
1330 """parse a localized time string and return a (unixtime, offset) tuple.
1331 """parse a localized time string and return a (unixtime, offset) tuple.
1331 if the string cannot be parsed, ValueError is raised."""
1332 if the string cannot be parsed, ValueError is raised."""
1332 def timezone(string):
1333 def timezone(string):
1333 tz = string.split()[-1]
1334 tz = string.split()[-1]
1334 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1335 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1335 tz = int(tz)
1336 tz = int(tz)
1336 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1337 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1337 return offset
1338 return offset
1338 if tz == "GMT" or tz == "UTC":
1339 if tz == "GMT" or tz == "UTC":
1339 return 0
1340 return 0
1340 return None
1341 return None
1341
1342
1342 # NOTE: unixtime = localunixtime + offset
1343 # NOTE: unixtime = localunixtime + offset
1343 offset, date = timezone(string), string
1344 offset, date = timezone(string), string
1344 if offset != None:
1345 if offset != None:
1345 date = " ".join(string.split()[:-1])
1346 date = " ".join(string.split()[:-1])
1346
1347
1347 # add missing elements from defaults
1348 # add missing elements from defaults
1348 for part in defaults:
1349 for part in defaults:
1349 found = [True for p in part if ("%"+p) in format]
1350 found = [True for p in part if ("%"+p) in format]
1350 if not found:
1351 if not found:
1351 date += "@" + defaults[part]
1352 date += "@" + defaults[part]
1352 format += "@%" + part[0]
1353 format += "@%" + part[0]
1353
1354
1354 timetuple = time.strptime(date, format)
1355 timetuple = time.strptime(date, format)
1355 localunixtime = int(calendar.timegm(timetuple))
1356 localunixtime = int(calendar.timegm(timetuple))
1356 if offset is None:
1357 if offset is None:
1357 # local timezone
1358 # local timezone
1358 unixtime = int(time.mktime(timetuple))
1359 unixtime = int(time.mktime(timetuple))
1359 offset = unixtime - localunixtime
1360 offset = unixtime - localunixtime
1360 else:
1361 else:
1361 unixtime = localunixtime + offset
1362 unixtime = localunixtime + offset
1362 return unixtime, offset
1363 return unixtime, offset
1363
1364
1364 def parsedate(string, formats=None, defaults=None):
1365 def parsedate(string, formats=None, defaults=None):
1365 """parse a localized time string and return a (unixtime, offset) tuple.
1366 """parse a localized time string and return a (unixtime, offset) tuple.
1366 The date may be a "unixtime offset" string or in one of the specified
1367 The date may be a "unixtime offset" string or in one of the specified
1367 formats."""
1368 formats."""
1368 if not string:
1369 if not string:
1369 return 0, 0
1370 return 0, 0
1370 if not formats:
1371 if not formats:
1371 formats = defaultdateformats
1372 formats = defaultdateformats
1372 string = string.strip()
1373 string = string.strip()
1373 try:
1374 try:
1374 when, offset = map(int, string.split(' '))
1375 when, offset = map(int, string.split(' '))
1375 except ValueError:
1376 except ValueError:
1376 # fill out defaults
1377 # fill out defaults
1377 if not defaults:
1378 if not defaults:
1378 defaults = {}
1379 defaults = {}
1379 now = makedate()
1380 now = makedate()
1380 for part in "d mb yY HI M S".split():
1381 for part in "d mb yY HI M S".split():
1381 if part not in defaults:
1382 if part not in defaults:
1382 if part[0] in "HMS":
1383 if part[0] in "HMS":
1383 defaults[part] = "00"
1384 defaults[part] = "00"
1384 elif part[0] in "dm":
1385 elif part[0] in "dm":
1385 defaults[part] = "1"
1386 defaults[part] = "1"
1386 else:
1387 else:
1387 defaults[part] = datestr(now, "%" + part[0], False)
1388 defaults[part] = datestr(now, "%" + part[0], False)
1388
1389
1389 for format in formats:
1390 for format in formats:
1390 try:
1391 try:
1391 when, offset = strdate(string, format, defaults)
1392 when, offset = strdate(string, format, defaults)
1392 except ValueError:
1393 except ValueError:
1393 pass
1394 pass
1394 else:
1395 else:
1395 break
1396 break
1396 else:
1397 else:
1397 raise Abort(_('invalid date: %r ') % string)
1398 raise Abort(_('invalid date: %r ') % string)
1398 # validate explicit (probably user-specified) date and
1399 # validate explicit (probably user-specified) date and
1399 # time zone offset. values must fit in signed 32 bits for
1400 # time zone offset. values must fit in signed 32 bits for
1400 # current 32-bit linux runtimes. timezones go from UTC-12
1401 # current 32-bit linux runtimes. timezones go from UTC-12
1401 # to UTC+14
1402 # to UTC+14
1402 if abs(when) > 0x7fffffff:
1403 if abs(when) > 0x7fffffff:
1403 raise Abort(_('date exceeds 32 bits: %d') % when)
1404 raise Abort(_('date exceeds 32 bits: %d') % when)
1404 if offset < -50400 or offset > 43200:
1405 if offset < -50400 or offset > 43200:
1405 raise Abort(_('impossible time zone offset: %d') % offset)
1406 raise Abort(_('impossible time zone offset: %d') % offset)
1406 return when, offset
1407 return when, offset
1407
1408
1408 def matchdate(date):
1409 def matchdate(date):
1409 """Return a function that matches a given date match specifier
1410 """Return a function that matches a given date match specifier
1410
1411
1411 Formats include:
1412 Formats include:
1412
1413
1413 '{date}' match a given date to the accuracy provided
1414 '{date}' match a given date to the accuracy provided
1414
1415
1415 '<{date}' on or before a given date
1416 '<{date}' on or before a given date
1416
1417
1417 '>{date}' on or after a given date
1418 '>{date}' on or after a given date
1418
1419
1419 """
1420 """
1420
1421
1421 def lower(date):
1422 def lower(date):
1422 return parsedate(date, extendeddateformats)[0]
1423 return parsedate(date, extendeddateformats)[0]
1423
1424
1424 def upper(date):
1425 def upper(date):
1425 d = dict(mb="12", HI="23", M="59", S="59")
1426 d = dict(mb="12", HI="23", M="59", S="59")
1426 for days in "31 30 29".split():
1427 for days in "31 30 29".split():
1427 try:
1428 try:
1428 d["d"] = days
1429 d["d"] = days
1429 return parsedate(date, extendeddateformats, d)[0]
1430 return parsedate(date, extendeddateformats, d)[0]
1430 except:
1431 except:
1431 pass
1432 pass
1432 d["d"] = "28"
1433 d["d"] = "28"
1433 return parsedate(date, extendeddateformats, d)[0]
1434 return parsedate(date, extendeddateformats, d)[0]
1434
1435
1435 if date[0] == "<":
1436 if date[0] == "<":
1436 when = upper(date[1:])
1437 when = upper(date[1:])
1437 return lambda x: x <= when
1438 return lambda x: x <= when
1438 elif date[0] == ">":
1439 elif date[0] == ">":
1439 when = lower(date[1:])
1440 when = lower(date[1:])
1440 return lambda x: x >= when
1441 return lambda x: x >= when
1441 elif date[0] == "-":
1442 elif date[0] == "-":
1442 try:
1443 try:
1443 days = int(date[1:])
1444 days = int(date[1:])
1444 except ValueError:
1445 except ValueError:
1445 raise Abort(_("invalid day spec: %s") % date[1:])
1446 raise Abort(_("invalid day spec: %s") % date[1:])
1446 when = makedate()[0] - days * 3600 * 24
1447 when = makedate()[0] - days * 3600 * 24
1447 return lambda x: x >= when
1448 return lambda x: x >= when
1448 elif " to " in date:
1449 elif " to " in date:
1449 a, b = date.split(" to ")
1450 a, b = date.split(" to ")
1450 start, stop = lower(a), upper(b)
1451 start, stop = lower(a), upper(b)
1451 return lambda x: x >= start and x <= stop
1452 return lambda x: x >= start and x <= stop
1452 else:
1453 else:
1453 start, stop = lower(date), upper(date)
1454 start, stop = lower(date), upper(date)
1454 return lambda x: x >= start and x <= stop
1455 return lambda x: x >= start and x <= stop
1455
1456
1456 def shortuser(user):
1457 def shortuser(user):
1457 """Return a short representation of a user name or email address."""
1458 """Return a short representation of a user name or email address."""
1458 f = user.find('@')
1459 f = user.find('@')
1459 if f >= 0:
1460 if f >= 0:
1460 user = user[:f]
1461 user = user[:f]
1461 f = user.find('<')
1462 f = user.find('<')
1462 if f >= 0:
1463 if f >= 0:
1463 user = user[f+1:]
1464 user = user[f+1:]
1464 f = user.find(' ')
1465 f = user.find(' ')
1465 if f >= 0:
1466 if f >= 0:
1466 user = user[:f]
1467 user = user[:f]
1467 f = user.find('.')
1468 f = user.find('.')
1468 if f >= 0:
1469 if f >= 0:
1469 user = user[:f]
1470 user = user[:f]
1470 return user
1471 return user
1471
1472
1472 def ellipsis(text, maxlength=400):
1473 def ellipsis(text, maxlength=400):
1473 """Trim string to at most maxlength (default: 400) characters."""
1474 """Trim string to at most maxlength (default: 400) characters."""
1474 if len(text) <= maxlength:
1475 if len(text) <= maxlength:
1475 return text
1476 return text
1476 else:
1477 else:
1477 return "%s..." % (text[:maxlength-3])
1478 return "%s..." % (text[:maxlength-3])
1478
1479
1479 def walkrepos(path):
1480 def walkrepos(path):
1480 '''yield every hg repository under path, recursively.'''
1481 '''yield every hg repository under path, recursively.'''
1481 def errhandler(err):
1482 def errhandler(err):
1482 if err.filename == path:
1483 if err.filename == path:
1483 raise err
1484 raise err
1484
1485
1485 for root, dirs, files in os.walk(path, onerror=errhandler):
1486 for root, dirs, files in os.walk(path, onerror=errhandler):
1486 for d in dirs:
1487 for d in dirs:
1487 if d == '.hg':
1488 if d == '.hg':
1488 yield root
1489 yield root
1489 dirs[:] = []
1490 dirs[:] = []
1490 break
1491 break
1491
1492
1492 _rcpath = None
1493 _rcpath = None
1493
1494
1494 def os_rcpath():
1495 def os_rcpath():
1495 '''return default os-specific hgrc search path'''
1496 '''return default os-specific hgrc search path'''
1496 path = system_rcpath()
1497 path = system_rcpath()
1497 path.extend(user_rcpath())
1498 path.extend(user_rcpath())
1498 path = [os.path.normpath(f) for f in path]
1499 path = [os.path.normpath(f) for f in path]
1499 return path
1500 return path
1500
1501
1501 def rcpath():
1502 def rcpath():
1502 '''return hgrc search path. if env var HGRCPATH is set, use it.
1503 '''return hgrc search path. if env var HGRCPATH is set, use it.
1503 for each item in path, if directory, use files ending in .rc,
1504 for each item in path, if directory, use files ending in .rc,
1504 else use item.
1505 else use item.
1505 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1506 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1506 if no HGRCPATH, use default os-specific path.'''
1507 if no HGRCPATH, use default os-specific path.'''
1507 global _rcpath
1508 global _rcpath
1508 if _rcpath is None:
1509 if _rcpath is None:
1509 if 'HGRCPATH' in os.environ:
1510 if 'HGRCPATH' in os.environ:
1510 _rcpath = []
1511 _rcpath = []
1511 for p in os.environ['HGRCPATH'].split(os.pathsep):
1512 for p in os.environ['HGRCPATH'].split(os.pathsep):
1512 if not p: continue
1513 if not p: continue
1513 if os.path.isdir(p):
1514 if os.path.isdir(p):
1514 for f in os.listdir(p):
1515 for f in os.listdir(p):
1515 if f.endswith('.rc'):
1516 if f.endswith('.rc'):
1516 _rcpath.append(os.path.join(p, f))
1517 _rcpath.append(os.path.join(p, f))
1517 else:
1518 else:
1518 _rcpath.append(p)
1519 _rcpath.append(p)
1519 else:
1520 else:
1520 _rcpath = os_rcpath()
1521 _rcpath = os_rcpath()
1521 return _rcpath
1522 return _rcpath
1522
1523
1523 def bytecount(nbytes):
1524 def bytecount(nbytes):
1524 '''return byte count formatted as readable string, with units'''
1525 '''return byte count formatted as readable string, with units'''
1525
1526
1526 units = (
1527 units = (
1527 (100, 1<<30, _('%.0f GB')),
1528 (100, 1<<30, _('%.0f GB')),
1528 (10, 1<<30, _('%.1f GB')),
1529 (10, 1<<30, _('%.1f GB')),
1529 (1, 1<<30, _('%.2f GB')),
1530 (1, 1<<30, _('%.2f GB')),
1530 (100, 1<<20, _('%.0f MB')),
1531 (100, 1<<20, _('%.0f MB')),
1531 (10, 1<<20, _('%.1f MB')),
1532 (10, 1<<20, _('%.1f MB')),
1532 (1, 1<<20, _('%.2f MB')),
1533 (1, 1<<20, _('%.2f MB')),
1533 (100, 1<<10, _('%.0f KB')),
1534 (100, 1<<10, _('%.0f KB')),
1534 (10, 1<<10, _('%.1f KB')),
1535 (10, 1<<10, _('%.1f KB')),
1535 (1, 1<<10, _('%.2f KB')),
1536 (1, 1<<10, _('%.2f KB')),
1536 (1, 1, _('%.0f bytes')),
1537 (1, 1, _('%.0f bytes')),
1537 )
1538 )
1538
1539
1539 for multiplier, divisor, format in units:
1540 for multiplier, divisor, format in units:
1540 if nbytes >= divisor * multiplier:
1541 if nbytes >= divisor * multiplier:
1541 return format % (nbytes / float(divisor))
1542 return format % (nbytes / float(divisor))
1542 return units[-1][2] % nbytes
1543 return units[-1][2] % nbytes
1543
1544
1544 def drop_scheme(scheme, path):
1545 def drop_scheme(scheme, path):
1545 sc = scheme + ':'
1546 sc = scheme + ':'
1546 if path.startswith(sc):
1547 if path.startswith(sc):
1547 path = path[len(sc):]
1548 path = path[len(sc):]
1548 if path.startswith('//'):
1549 if path.startswith('//'):
1549 path = path[2:]
1550 path = path[2:]
1550 return path
1551 return path
@@ -1,57 +1,57 b''
1 default = 'summary'
1 default = 'summary'
2 header = header.tmpl
2 header = header.tmpl
3 footer = footer.tmpl
3 footer = footer.tmpl
4 search = search.tmpl
4 search = search.tmpl
5 changelog = changelog.tmpl
5 changelog = changelog.tmpl
6 summary = summary.tmpl
6 summary = summary.tmpl
7 error = error.tmpl
7 error = error.tmpl
8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 filenodelink = '<tr class="parity#parity#"><td><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">#file|escape#</a></td><td></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> | <a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a></td></tr>'
12 filenodelink = '<tr class="parity#parity#"><td><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">#file|escape#</a></td><td></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> | <a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a></td></tr>'
13 fileellipses = '...'
13 fileellipses = '...'
14 changelogentry = changelogentry.tmpl
14 changelogentry = changelogentry.tmpl
15 searchentry = changelogentry.tmpl
15 searchentry = changelogentry.tmpl
16 changeset = changeset.tmpl
16 changeset = changeset.tmpl
17 manifest = manifest.tmpl
17 manifest = manifest.tmpl
18 manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a></td></tr>'
18 manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a></td></tr>'
19 manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td style="font-family:monospace" align=right>#size#</td><td class="list"><a class="list" href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a></td></tr>'
19 manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td style="font-family:monospace" align=right>#size#</td><td class="list"><a class="list" href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a></td></tr>'
20 filerevision = filerevision.tmpl
20 filerevision = filerevision.tmpl
21 fileannotate = fileannotate.tmpl
21 fileannotate = fileannotate.tmpl
22 filediff = filediff.tmpl
22 filediff = filediff.tmpl
23 filelog = filelog.tmpl
23 filelog = filelog.tmpl
24 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><span class="linenr"> #linenumber#</span> #line|escape#</pre></div>'
24 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><span class="linenr"> #linenumber#</span> #line|escape#</pre></div>'
25 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
25 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
26 difflineplus = '<div style="color:#008800;">#line|escape#</div>'
26 difflineplus = '<div style="color:#008800;">#line|escape#</div>'
27 difflineminus = '<div style="color:#cc0000;">#line|escape#</div>'
27 difflineminus = '<div style="color:#cc0000;">#line|escape#</div>'
28 difflineat = '<div style="color:#990099;">#line|escape#</div>'
28 difflineat = '<div style="color:#990099;">#line|escape#</div>'
29 diffline = '<div>#line|escape#</div>'
29 diffline = '<div>#line|escape#</div>'
30 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
30 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
31 changesetparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
31 changesetparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
32 filerevparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
32 filerevparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
33 filerename = '{file|escape}@'
33 filerename = '{file|escape}@'
34 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
34 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
35 fileannotateparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
35 fileannotateparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
36 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
36 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
37 changesetchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
37 changesetchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
38 filerevchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
38 filerevchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
39 fileannotatechild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
39 fileannotatechild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
40 tags = tags.tmpl
40 tags = tags.tmpl
41 tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>#tag|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/#node|short#{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
41 tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>#tag|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/#node|short#{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
42 branchentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
42 branchentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
43 diffblock = '<pre>#lines#</pre>'
43 diffblock = '<pre>#lines#</pre>'
44 filediffparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
44 filediffparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
45 filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="{url}file/{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
45 filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="{url}file/{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
46 filediffchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
46 filediffchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
47 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="{url}file{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="{url}file{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 shortlog = shortlog.tmpl
48 shortlog = shortlog.tmpl
49 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
49 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
50 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
50 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
51 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b> <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
51 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author|person#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b> <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
52 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url}diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url}annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> #rename%filelogrename#</td></tr>'
52 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url}diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url}annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> #rename%filelogrename#</td></tr>'
53 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
53 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
54 indexentry = '<tr class="parity#parity#"><td><a class="list" href="#url#{sessionvars%urlparameter}"><b>#name|escape#</b></a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a class="rss_logo" href="#url#rss-log">RSS</a> #archives%archiveentry#</td></tr>'
54 indexentry = '<tr class="parity#parity#"><td><a class="list" href="#url#{sessionvars%urlparameter}"><b>#name|escape#</b></a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a class="rss_logo" href="#url#rss-log">RSS</a> #archives%archiveentry#</td></tr>'
55 index = index.tmpl
55 index = index.tmpl
56 urlparameter = '#separator##name#=#value|urlescape#'
56 urlparameter = '#separator##name#=#value|urlescape#'
57 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
57 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,54 +1,54 b''
1 #header#
1 #header#
2 <title>#repo|escape#: Summary</title>
2 <title>#repo|escape#: Summary</title>
3 <link rel="alternate" type="application/rss+xml"
3 <link rel="alternate" type="application/rss+xml"
4 href="{url}rss-log" title="RSS feed for #repo|escape#">
4 href="{url}rss-log" title="RSS feed for #repo|escape#">
5 </head>
5 </head>
6 <body>
6 <body>
7
7
8 <div class="page_header">
8 <div class="page_header">
9 <a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="{url}summary{sessionvars%urlparameter}">#repo|escape#</a> / summary
9 <a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="{url}summary{sessionvars%urlparameter}">#repo|escape#</a> / summary
10
10
11 <form action="{url}log">
11 <form action="{url}log">
12 {sessionvars%hiddenformentry}
12 {sessionvars%hiddenformentry}
13 <div class="search">
13 <div class="search">
14 <input type="text" name="rev" />
14 <input type="text" name="rev" />
15 </div>
15 </div>
16 </form>
16 </form>
17 </div>
17 </div>
18
18
19 <div class="page_nav">
19 <div class="page_nav">
20 summary |
20 summary |
21 <a href="{url}shortlog{sessionvars%urlparameter}">shortlog</a> |
21 <a href="{url}shortlog{sessionvars%urlparameter}">shortlog</a> |
22 <a href="{url}log{sessionvars%urlparameter}">changelog</a> |
22 <a href="{url}log{sessionvars%urlparameter}">changelog</a> |
23 <a href="{url}tags{sessionvars%urlparameter}">tags</a> |
23 <a href="{url}tags{sessionvars%urlparameter}">tags</a> |
24 <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a>#archives%archiveentry#
24 <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a>#archives%archiveentry#
25 <br/>
25 <br/>
26 </div>
26 </div>
27
27
28 <div class="title">&nbsp;</div>
28 <div class="title">&nbsp;</div>
29 <table cellspacing="0">
29 <table cellspacing="0">
30 <tr><td>description</td><td>#desc#</td></tr>
30 <tr><td>description</td><td>#desc#</td></tr>
31 <tr><td>owner</td><td>#owner|escape#</td></tr>
31 <tr><td>owner</td><td>#owner|escape#</td></tr>
32 <tr><td>last change</td><td>#lastchange|rfc822date#</td></tr>
32 <tr><td>last change</td><td>#lastchange|rfc822date#</td></tr>
33 </table>
33 </table>
34
34
35 <div><a class="title" href="{url}log{sessionvars%urlparameter}">changes</a></div>
35 <div><a class="title" href="{url}shortlog{sessionvars%urlparameter}">changes</a></div>
36 <table cellspacing="0">
36 <table cellspacing="0">
37 #shortlog#
37 #shortlog#
38 <tr class="light"><td colspan="4"><a class="list" href="{url}log{sessionvars%urlparameter}">...</a></td></tr>
38 <tr class="light"><td colspan="4"><a class="list" href="{url}shortlog{sessionvars%urlparameter}">...</a></td></tr>
39 </table>
39 </table>
40
40
41 <div><a class="title" href="{url}tags{sessionvars%urlparameter}">tags</a></div>
41 <div><a class="title" href="{url}tags{sessionvars%urlparameter}">tags</a></div>
42 <table cellspacing="0">
42 <table cellspacing="0">
43 #tags#
43 #tags#
44 <tr class="light"><td colspan="3"><a class="list" href="{url}tags{sessionvars%urlparameter}">...</a></td></tr>
44 <tr class="light"><td colspan="3"><a class="list" href="{url}tags{sessionvars%urlparameter}">...</a></td></tr>
45 </table>
45 </table>
46
46
47 <div><a class="title" href="#">branches</a></div>
47 <div><a class="title" href="#">branches</a></div>
48 <table cellspacing="0">
48 <table cellspacing="0">
49 {branches%branchentry}
49 {branches%branchentry}
50 <tr class="light">
50 <tr class="light">
51 <td colspan="4"><a class="list" href="#">...</a></td>
51 <td colspan="4"><a class="list" href="#">...</a></td>
52 </tr>
52 </tr>
53 </table>
53 </table>
54 #footer#
54 #footer#
@@ -1,56 +1,56 b''
1 default = 'changelog'
1 default = 'shortlog'
2 header = header.tmpl
2 header = header.tmpl
3 footer = footer.tmpl
3 footer = footer.tmpl
4 search = search.tmpl
4 search = search.tmpl
5 changelog = changelog.tmpl
5 changelog = changelog.tmpl
6 shortlog = shortlog.tmpl
6 shortlog = shortlog.tmpl
7 shortlogentry = shortlogentry.tmpl
7 shortlogentry = shortlogentry.tmpl
8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 filenodelink = '<a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 filenodelink = '<a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 fileellipses = '...'
13 fileellipses = '...'
14 changelogentry = changelogentry.tmpl
14 changelogentry = changelogentry.tmpl
15 searchentry = changelogentry.tmpl
15 searchentry = changelogentry.tmpl
16 changeset = changeset.tmpl
16 changeset = changeset.tmpl
17 manifest = manifest.tmpl
17 manifest = manifest.tmpl
18 manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td>&nbsp;<td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a>'
18 manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td>&nbsp;<td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a>'
19 manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td align=right><tt>#size#</tt>&nbsp;<td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a>'
19 manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td align=right><tt>#size#</tt>&nbsp;<td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a>'
20 filerevision = filerevision.tmpl
20 filerevision = filerevision.tmpl
21 fileannotate = fileannotate.tmpl
21 fileannotate = fileannotate.tmpl
22 filediff = filediff.tmpl
22 filediff = filediff.tmpl
23 filelog = filelog.tmpl
23 filelog = filelog.tmpl
24 fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
24 fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
25 filelogentry = filelogentry.tmpl
25 filelogentry = filelogentry.tmpl
26 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
26 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
27 difflineplus = '<span class="plusline">#line|escape#</span>'
27 difflineplus = '<span class="plusline">#line|escape#</span>'
28 difflineminus = '<span class="minusline">#line|escape#</span>'
28 difflineminus = '<span class="minusline">#line|escape#</span>'
29 difflineat = '<span class="atline">#line|escape#</span>'
29 difflineat = '<span class="atline">#line|escape#</span>'
30 diffline = '#line|escape#'
30 diffline = '#line|escape#'
31 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
31 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
32 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
32 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
33 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
33 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
34 filerename = '{file|escape}@'
34 filerename = '{file|escape}@'
35 filelogrename = '<tr><th>base:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#@#node|short#</a></td></tr>'
35 filelogrename = '<tr><th>base:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#@#node|short#</a></td></tr>'
36 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
36 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
37 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
37 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
38 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
38 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
39 filerevchild = '<tr><td class="metatag">child:</td><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
39 filerevchild = '<tr><td class="metatag">child:</td><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
40 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
40 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
41 tags = tags.tmpl
41 tags = tags.tmpl
42 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="#url#rev/#node|short#{sessionvars%urlparameter}">#tag|escape#</a></li>'
42 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="#url#rev/#node|short#{sessionvars%urlparameter}">#tag|escape#</a></li>'
43 diffblock = '<pre class="parity#parity#">#lines#</pre>'
43 diffblock = '<pre class="parity#parity#">#lines#</pre>'
44 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
44 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
46 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
46 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 filelogparent = '<tr><th>parent #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 filelogparent = '<tr><th>parent #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 filelogchild = '<tr><th>child #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 filelogchild = '<tr><th>child #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
50 indexentry = '<tr class="parity#parity#"><td><a href="#url#{sessionvars%urlparameter}">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#rss-log">RSS</a> #archives%archiveentry#</td></tr>'
50 indexentry = '<tr class="parity#parity#"><td><a href="#url#{sessionvars%urlparameter}">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#rss-log">RSS</a> #archives%archiveentry#</td></tr>'
51 index = index.tmpl
51 index = index.tmpl
52 archiveentry = '<a href="#url#archive/#node|short##extension|urlescape#">#type|escape#</a> '
52 archiveentry = '<a href="#url#archive/#node|short##extension|urlescape#">#type|escape#</a> '
53 notfound = notfound.tmpl
53 notfound = notfound.tmpl
54 error = error.tmpl
54 error = error.tmpl
55 urlparameter = '#separator##name#=#value|urlescape#'
55 urlparameter = '#separator##name#=#value|urlescape#'
56 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
56 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,7 +1,7 b''
1 <table class="slogEntry parity#parity#">
1 <table class="slogEntry parity#parity#">
2 <tr>
2 <tr>
3 <td class="age">#date|age#</td>
3 <td class="age">#date|age#</td>
4 <td class="author">#author|obfuscate#</td>
4 <td class="author">#author|person#</td>
5 <td class="node"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#desc|strip|firstline|escape#</a></td>
5 <td class="node"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#desc|strip|firstline|escape#</a></td>
6 </tr>
6 </tr>
7 </table>
7 </table>
@@ -1,78 +1,78 b''
1 a { text-decoration:none; }
1 a { text-decoration:none; }
2 .age { white-space:nowrap; }
2 .age { white-space:nowrap; }
3 .indexlinks { white-space:nowrap; }
3 .indexlinks { white-space:nowrap; }
4 .parity0 { background-color: #dddddd; }
4 .parity0 { background-color: #dddddd; }
5 .parity1 { background-color: #eeeeee; }
5 .parity1 { background-color: #eeeeee; }
6 .lineno { width: 60px; color: #aaaaaa; font-size: smaller;
6 .lineno { width: 60px; color: #aaaaaa; font-size: smaller;
7 text-align: right; padding-right:1em; }
7 text-align: right; padding-right:1em; }
8 .plusline { color: green; }
8 .plusline { color: green; }
9 .minusline { color: red; }
9 .minusline { color: red; }
10 .atline { color: purple; }
10 .atline { color: purple; }
11 .annotate { font-size: smaller; text-align: right; padding-right: 1em; }
11 .annotate { font-size: smaller; text-align: right; padding-right: 1em; }
12 .buttons a {
12 .buttons a {
13 background-color: #666666;
13 background-color: #666666;
14 padding: 2pt;
14 padding: 2pt;
15 color: white;
15 color: white;
16 font-family: sans;
16 font-family: sans;
17 font-weight: bold;
17 font-weight: bold;
18 }
18 }
19 .navigate a {
19 .navigate a {
20 background-color: #ccc;
20 background-color: #ccc;
21 padding: 2pt;
21 padding: 2pt;
22 font-family: sans;
22 font-family: sans;
23 color: black;
23 color: black;
24 }
24 }
25
25
26 .metatag {
26 .metatag {
27 background-color: #888888;
27 background-color: #888888;
28 color: white;
28 color: white;
29 text-align: right;
29 text-align: right;
30 }
30 }
31
31
32 /* Common */
32 /* Common */
33 pre { margin: 0; }
33 pre { margin: 0; }
34
34
35 .logo {
35 .logo {
36 background-color: #333;
36 background-color: #333;
37 padding: 4pt;
37 padding: 4pt;
38 margin: 8pt 0 8pt 8pt;
38 margin: 8pt 0 8pt 8pt;
39 font-family: sans;
39 font-family: sans;
40 font-size: 60%;
40 font-size: 60%;
41 color: white;
41 color: white;
42 float: right;
42 float: right;
43 clear: right;
43 clear: right;
44 text-align: left;
44 text-align: left;
45 }
45 }
46
46
47 .logo a {
47 .logo a {
48 font-weight: bold;
48 font-weight: bold;
49 font-size: 150%;
49 font-size: 150%;
50 color: #999;
50 color: #999;
51 }
51 }
52
52
53 /* Changelog/Filelog entries */
53 /* Changelog/Filelog entries */
54 .logEntry { width: 100%; }
54 .logEntry { width: 100%; }
55 .logEntry .age { width: 15%; }
55 .logEntry .age { width: 15%; }
56 .logEntry th { font-weight: normal; text-align: right; vertical-align: top; }
56 .logEntry th { font-weight: normal; text-align: right; vertical-align: top; }
57 .logEntry th.age, .logEntry th.firstline { font-weight: bold; }
57 .logEntry th.age, .logEntry th.firstline { font-weight: bold; }
58 .logEntry th.firstline { text-align: left; width: inherit; }
58 .logEntry th.firstline { text-align: left; width: inherit; }
59
59
60 /* Shortlog entries */
60 /* Shortlog entries */
61 .slogEntry { width: 100%; font-size: smaller; }
61 .slogEntry { width: 100%; }
62 .slogEntry .age { width: 7%; }
62 .slogEntry .age { width: 8em; }
63 .slogEntry td { font-weight: normal; text-align: left; vertical-align: top; }
63 .slogEntry td { font-weight: normal; text-align: left; vertical-align: top; }
64 .slogEntry td.author { width: 35%; }
64 .slogEntry td.author { width: 15em; }
65
65
66 /* Tag entries */
66 /* Tag entries */
67 #tagEntries { list-style: none; margin: 0; padding: 0; }
67 #tagEntries { list-style: none; margin: 0; padding: 0; }
68 #tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
68 #tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
69
69
70 /* Changeset entry */
70 /* Changeset entry */
71 #changesetEntry { }
71 #changesetEntry { }
72 #changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
72 #changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
73 #changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
73 #changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
74
74
75 /* File diff view */
75 /* File diff view */
76 #filediffEntry { }
76 #filediffEntry { }
77 #filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
77 #filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
78
78
General Comments 0
You need to be logged in to leave comments. Login now