##// END OF EJS Templates
Move ui.diffopts to patch.diffopts where it belongs
Matt Mackall -
r2888:38484882 default
parent child Browse files
Show More
@@ -1,1997 +1,1997 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.demandload import *
32 from mercurial.demandload import *
33 from mercurial.i18n import gettext as _
33 from mercurial.i18n import gettext as _
34 demandload(globals(), "os sys re struct traceback errno bz2")
34 demandload(globals(), "os sys re struct traceback errno bz2")
35 demandload(globals(), "mercurial:cmdutil,commands,hg,patch,revlog,ui,util")
35 demandload(globals(), "mercurial:cmdutil,commands,hg,patch,revlog,ui,util")
36
36
37 commands.norepo += " qclone qversion"
37 commands.norepo += " qclone qversion"
38
38
39 class statusentry:
39 class statusentry:
40 def __init__(self, rev, name=None):
40 def __init__(self, rev, name=None):
41 if not name:
41 if not name:
42 fields = rev.split(':')
42 fields = rev.split(':')
43 if len(fields) == 2:
43 if len(fields) == 2:
44 self.rev, self.name = fields
44 self.rev, self.name = fields
45 else:
45 else:
46 self.rev, self.name = None, None
46 self.rev, self.name = None, None
47 else:
47 else:
48 self.rev, self.name = rev, name
48 self.rev, self.name = rev, name
49
49
50 def __str__(self):
50 def __str__(self):
51 return self.rev + ':' + self.name
51 return self.rev + ':' + self.name
52
52
53 class queue:
53 class queue:
54 def __init__(self, ui, path, patchdir=None):
54 def __init__(self, ui, path, patchdir=None):
55 self.basepath = path
55 self.basepath = path
56 self.path = patchdir or os.path.join(path, "patches")
56 self.path = patchdir or os.path.join(path, "patches")
57 self.opener = util.opener(self.path)
57 self.opener = util.opener(self.path)
58 self.ui = ui
58 self.ui = ui
59 self.applied = []
59 self.applied = []
60 self.full_series = []
60 self.full_series = []
61 self.applied_dirty = 0
61 self.applied_dirty = 0
62 self.series_dirty = 0
62 self.series_dirty = 0
63 self.series_path = "series"
63 self.series_path = "series"
64 self.status_path = "status"
64 self.status_path = "status"
65 self.guards_path = "guards"
65 self.guards_path = "guards"
66 self.active_guards = None
66 self.active_guards = None
67 self.guards_dirty = False
67 self.guards_dirty = False
68 self._diffopts = None
68 self._diffopts = None
69
69
70 if os.path.exists(self.join(self.series_path)):
70 if os.path.exists(self.join(self.series_path)):
71 self.full_series = self.opener(self.series_path).read().splitlines()
71 self.full_series = self.opener(self.series_path).read().splitlines()
72 self.parse_series()
72 self.parse_series()
73
73
74 if os.path.exists(self.join(self.status_path)):
74 if os.path.exists(self.join(self.status_path)):
75 lines = self.opener(self.status_path).read().splitlines()
75 lines = self.opener(self.status_path).read().splitlines()
76 self.applied = [statusentry(l) for l in lines]
76 self.applied = [statusentry(l) for l in lines]
77
77
78 def diffopts(self):
78 def diffopts(self):
79 if self._diffopts is None:
79 if self._diffopts is None:
80 self._diffopts = self.ui.diffopts()
80 self._diffopts = patch.diffopts(self.ui)
81 return self._diffopts
81 return self._diffopts
82
82
83 def join(self, *p):
83 def join(self, *p):
84 return os.path.join(self.path, *p)
84 return os.path.join(self.path, *p)
85
85
86 def find_series(self, patch):
86 def find_series(self, patch):
87 pre = re.compile("(\s*)([^#]+)")
87 pre = re.compile("(\s*)([^#]+)")
88 index = 0
88 index = 0
89 for l in self.full_series:
89 for l in self.full_series:
90 m = pre.match(l)
90 m = pre.match(l)
91 if m:
91 if m:
92 s = m.group(2)
92 s = m.group(2)
93 s = s.rstrip()
93 s = s.rstrip()
94 if s == patch:
94 if s == patch:
95 return index
95 return index
96 index += 1
96 index += 1
97 return None
97 return None
98
98
99 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
99 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
100
100
101 def parse_series(self):
101 def parse_series(self):
102 self.series = []
102 self.series = []
103 self.series_guards = []
103 self.series_guards = []
104 for l in self.full_series:
104 for l in self.full_series:
105 h = l.find('#')
105 h = l.find('#')
106 if h == -1:
106 if h == -1:
107 patch = l
107 patch = l
108 comment = ''
108 comment = ''
109 elif h == 0:
109 elif h == 0:
110 continue
110 continue
111 else:
111 else:
112 patch = l[:h]
112 patch = l[:h]
113 comment = l[h:]
113 comment = l[h:]
114 patch = patch.strip()
114 patch = patch.strip()
115 if patch:
115 if patch:
116 self.series.append(patch)
116 self.series.append(patch)
117 self.series_guards.append(self.guard_re.findall(comment))
117 self.series_guards.append(self.guard_re.findall(comment))
118
118
119 def check_guard(self, guard):
119 def check_guard(self, guard):
120 bad_chars = '# \t\r\n\f'
120 bad_chars = '# \t\r\n\f'
121 first = guard[0]
121 first = guard[0]
122 for c in '-+':
122 for c in '-+':
123 if first == c:
123 if first == c:
124 return (_('guard %r starts with invalid character: %r') %
124 return (_('guard %r starts with invalid character: %r') %
125 (guard, c))
125 (guard, c))
126 for c in bad_chars:
126 for c in bad_chars:
127 if c in guard:
127 if c in guard:
128 return _('invalid character in guard %r: %r') % (guard, c)
128 return _('invalid character in guard %r: %r') % (guard, c)
129
129
130 def set_active(self, guards):
130 def set_active(self, guards):
131 for guard in guards:
131 for guard in guards:
132 bad = self.check_guard(guard)
132 bad = self.check_guard(guard)
133 if bad:
133 if bad:
134 raise util.Abort(bad)
134 raise util.Abort(bad)
135 guards = dict.fromkeys(guards).keys()
135 guards = dict.fromkeys(guards).keys()
136 guards.sort()
136 guards.sort()
137 self.ui.debug('active guards: %s\n' % ' '.join(guards))
137 self.ui.debug('active guards: %s\n' % ' '.join(guards))
138 self.active_guards = guards
138 self.active_guards = guards
139 self.guards_dirty = True
139 self.guards_dirty = True
140
140
141 def active(self):
141 def active(self):
142 if self.active_guards is None:
142 if self.active_guards is None:
143 self.active_guards = []
143 self.active_guards = []
144 try:
144 try:
145 guards = self.opener(self.guards_path).read().split()
145 guards = self.opener(self.guards_path).read().split()
146 except IOError, err:
146 except IOError, err:
147 if err.errno != errno.ENOENT: raise
147 if err.errno != errno.ENOENT: raise
148 guards = []
148 guards = []
149 for i, guard in enumerate(guards):
149 for i, guard in enumerate(guards):
150 bad = self.check_guard(guard)
150 bad = self.check_guard(guard)
151 if bad:
151 if bad:
152 self.ui.warn('%s:%d: %s\n' %
152 self.ui.warn('%s:%d: %s\n' %
153 (self.join(self.guards_path), i + 1, bad))
153 (self.join(self.guards_path), i + 1, bad))
154 else:
154 else:
155 self.active_guards.append(guard)
155 self.active_guards.append(guard)
156 return self.active_guards
156 return self.active_guards
157
157
158 def set_guards(self, idx, guards):
158 def set_guards(self, idx, guards):
159 for g in guards:
159 for g in guards:
160 if len(g) < 2:
160 if len(g) < 2:
161 raise util.Abort(_('guard %r too short') % g)
161 raise util.Abort(_('guard %r too short') % g)
162 if g[0] not in '-+':
162 if g[0] not in '-+':
163 raise util.Abort(_('guard %r starts with invalid char') % g)
163 raise util.Abort(_('guard %r starts with invalid char') % g)
164 bad = self.check_guard(g[1:])
164 bad = self.check_guard(g[1:])
165 if bad:
165 if bad:
166 raise util.Abort(bad)
166 raise util.Abort(bad)
167 drop = self.guard_re.sub('', self.full_series[idx])
167 drop = self.guard_re.sub('', self.full_series[idx])
168 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
168 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
169 self.parse_series()
169 self.parse_series()
170 self.series_dirty = True
170 self.series_dirty = True
171
171
172 def pushable(self, idx):
172 def pushable(self, idx):
173 if isinstance(idx, str):
173 if isinstance(idx, str):
174 idx = self.series.index(idx)
174 idx = self.series.index(idx)
175 patchguards = self.series_guards[idx]
175 patchguards = self.series_guards[idx]
176 if not patchguards:
176 if not patchguards:
177 return True, None
177 return True, None
178 default = False
178 default = False
179 guards = self.active()
179 guards = self.active()
180 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
180 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
181 if exactneg:
181 if exactneg:
182 return False, exactneg[0]
182 return False, exactneg[0]
183 pos = [g for g in patchguards if g[0] == '+']
183 pos = [g for g in patchguards if g[0] == '+']
184 exactpos = [g for g in pos if g[1:] in guards]
184 exactpos = [g for g in pos if g[1:] in guards]
185 if pos:
185 if pos:
186 if exactpos:
186 if exactpos:
187 return True, exactpos[0]
187 return True, exactpos[0]
188 return False, pos
188 return False, pos
189 return True, ''
189 return True, ''
190
190
191 def explain_pushable(self, idx, all_patches=False):
191 def explain_pushable(self, idx, all_patches=False):
192 write = all_patches and self.ui.write or self.ui.warn
192 write = all_patches and self.ui.write or self.ui.warn
193 if all_patches or self.ui.verbose:
193 if all_patches or self.ui.verbose:
194 if isinstance(idx, str):
194 if isinstance(idx, str):
195 idx = self.series.index(idx)
195 idx = self.series.index(idx)
196 pushable, why = self.pushable(idx)
196 pushable, why = self.pushable(idx)
197 if all_patches and pushable:
197 if all_patches and pushable:
198 if why is None:
198 if why is None:
199 write(_('allowing %s - no guards in effect\n') %
199 write(_('allowing %s - no guards in effect\n') %
200 self.series[idx])
200 self.series[idx])
201 else:
201 else:
202 if not why:
202 if not why:
203 write(_('allowing %s - no matching negative guards\n') %
203 write(_('allowing %s - no matching negative guards\n') %
204 self.series[idx])
204 self.series[idx])
205 else:
205 else:
206 write(_('allowing %s - guarded by %r\n') %
206 write(_('allowing %s - guarded by %r\n') %
207 (self.series[idx], why))
207 (self.series[idx], why))
208 if not pushable:
208 if not pushable:
209 if why:
209 if why:
210 write(_('skipping %s - guarded by %r\n') %
210 write(_('skipping %s - guarded by %r\n') %
211 (self.series[idx], ' '.join(why)))
211 (self.series[idx], ' '.join(why)))
212 else:
212 else:
213 write(_('skipping %s - no matching guards\n') %
213 write(_('skipping %s - no matching guards\n') %
214 self.series[idx])
214 self.series[idx])
215
215
216 def save_dirty(self):
216 def save_dirty(self):
217 def write_list(items, path):
217 def write_list(items, path):
218 fp = self.opener(path, 'w')
218 fp = self.opener(path, 'w')
219 for i in items:
219 for i in items:
220 print >> fp, i
220 print >> fp, i
221 fp.close()
221 fp.close()
222 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
222 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
223 if self.series_dirty: write_list(self.full_series, self.series_path)
223 if self.series_dirty: write_list(self.full_series, self.series_path)
224 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
224 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
225
225
226 def readheaders(self, patch):
226 def readheaders(self, patch):
227 def eatdiff(lines):
227 def eatdiff(lines):
228 while lines:
228 while lines:
229 l = lines[-1]
229 l = lines[-1]
230 if (l.startswith("diff -") or
230 if (l.startswith("diff -") or
231 l.startswith("Index:") or
231 l.startswith("Index:") or
232 l.startswith("===========")):
232 l.startswith("===========")):
233 del lines[-1]
233 del lines[-1]
234 else:
234 else:
235 break
235 break
236 def eatempty(lines):
236 def eatempty(lines):
237 while lines:
237 while lines:
238 l = lines[-1]
238 l = lines[-1]
239 if re.match('\s*$', l):
239 if re.match('\s*$', l):
240 del lines[-1]
240 del lines[-1]
241 else:
241 else:
242 break
242 break
243
243
244 pf = self.join(patch)
244 pf = self.join(patch)
245 message = []
245 message = []
246 comments = []
246 comments = []
247 user = None
247 user = None
248 date = None
248 date = None
249 format = None
249 format = None
250 subject = None
250 subject = None
251 diffstart = 0
251 diffstart = 0
252
252
253 for line in file(pf):
253 for line in file(pf):
254 line = line.rstrip()
254 line = line.rstrip()
255 if diffstart:
255 if diffstart:
256 if line.startswith('+++ '):
256 if line.startswith('+++ '):
257 diffstart = 2
257 diffstart = 2
258 break
258 break
259 if line.startswith("--- "):
259 if line.startswith("--- "):
260 diffstart = 1
260 diffstart = 1
261 continue
261 continue
262 elif format == "hgpatch":
262 elif format == "hgpatch":
263 # parse values when importing the result of an hg export
263 # parse values when importing the result of an hg export
264 if line.startswith("# User "):
264 if line.startswith("# User "):
265 user = line[7:]
265 user = line[7:]
266 elif line.startswith("# Date "):
266 elif line.startswith("# Date "):
267 date = line[7:]
267 date = line[7:]
268 elif not line.startswith("# ") and line:
268 elif not line.startswith("# ") and line:
269 message.append(line)
269 message.append(line)
270 format = None
270 format = None
271 elif line == '# HG changeset patch':
271 elif line == '# HG changeset patch':
272 format = "hgpatch"
272 format = "hgpatch"
273 elif (format != "tagdone" and (line.startswith("Subject: ") or
273 elif (format != "tagdone" and (line.startswith("Subject: ") or
274 line.startswith("subject: "))):
274 line.startswith("subject: "))):
275 subject = line[9:]
275 subject = line[9:]
276 format = "tag"
276 format = "tag"
277 elif (format != "tagdone" and (line.startswith("From: ") or
277 elif (format != "tagdone" and (line.startswith("From: ") or
278 line.startswith("from: "))):
278 line.startswith("from: "))):
279 user = line[6:]
279 user = line[6:]
280 format = "tag"
280 format = "tag"
281 elif format == "tag" and line == "":
281 elif format == "tag" and line == "":
282 # when looking for tags (subject: from: etc) they
282 # when looking for tags (subject: from: etc) they
283 # end once you find a blank line in the source
283 # end once you find a blank line in the source
284 format = "tagdone"
284 format = "tagdone"
285 elif message or line:
285 elif message or line:
286 message.append(line)
286 message.append(line)
287 comments.append(line)
287 comments.append(line)
288
288
289 eatdiff(message)
289 eatdiff(message)
290 eatdiff(comments)
290 eatdiff(comments)
291 eatempty(message)
291 eatempty(message)
292 eatempty(comments)
292 eatempty(comments)
293
293
294 # make sure message isn't empty
294 # make sure message isn't empty
295 if format and format.startswith("tag") and subject:
295 if format and format.startswith("tag") and subject:
296 message.insert(0, "")
296 message.insert(0, "")
297 message.insert(0, subject)
297 message.insert(0, subject)
298 return (message, comments, user, date, diffstart > 1)
298 return (message, comments, user, date, diffstart > 1)
299
299
300 def printdiff(self, repo, node1, node2=None, files=None,
300 def printdiff(self, repo, node1, node2=None, files=None,
301 fp=None, changes=None, opts=None):
301 fp=None, changes=None, opts=None):
302 patch.diff(repo, node1, node2, files,
302 patch.diff(repo, node1, node2, files,
303 fp=fp, changes=changes, opts=self.diffopts())
303 fp=fp, changes=changes, opts=self.diffopts())
304
304
305 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
305 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
306 # first try just applying the patch
306 # first try just applying the patch
307 (err, n) = self.apply(repo, [ patch ], update_status=False,
307 (err, n) = self.apply(repo, [ patch ], update_status=False,
308 strict=True, merge=rev, wlock=wlock)
308 strict=True, merge=rev, wlock=wlock)
309
309
310 if err == 0:
310 if err == 0:
311 return (err, n)
311 return (err, n)
312
312
313 if n is None:
313 if n is None:
314 raise util.Abort(_("apply failed for patch %s") % patch)
314 raise util.Abort(_("apply failed for patch %s") % patch)
315
315
316 self.ui.warn("patch didn't work out, merging %s\n" % patch)
316 self.ui.warn("patch didn't work out, merging %s\n" % patch)
317
317
318 # apply failed, strip away that rev and merge.
318 # apply failed, strip away that rev and merge.
319 hg.clean(repo, head, wlock=wlock)
319 hg.clean(repo, head, wlock=wlock)
320 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
320 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
321
321
322 c = repo.changelog.read(rev)
322 c = repo.changelog.read(rev)
323 ret = hg.merge(repo, rev, wlock=wlock)
323 ret = hg.merge(repo, rev, wlock=wlock)
324 if ret:
324 if ret:
325 raise util.Abort(_("update returned %d") % ret)
325 raise util.Abort(_("update returned %d") % ret)
326 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
326 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
327 if n == None:
327 if n == None:
328 raise util.Abort(_("repo commit failed"))
328 raise util.Abort(_("repo commit failed"))
329 try:
329 try:
330 message, comments, user, date, patchfound = mergeq.readheaders(patch)
330 message, comments, user, date, patchfound = mergeq.readheaders(patch)
331 except:
331 except:
332 raise util.Abort(_("unable to read %s") % patch)
332 raise util.Abort(_("unable to read %s") % patch)
333
333
334 patchf = self.opener(patch, "w")
334 patchf = self.opener(patch, "w")
335 if comments:
335 if comments:
336 comments = "\n".join(comments) + '\n\n'
336 comments = "\n".join(comments) + '\n\n'
337 patchf.write(comments)
337 patchf.write(comments)
338 self.printdiff(repo, head, n, fp=patchf)
338 self.printdiff(repo, head, n, fp=patchf)
339 patchf.close()
339 patchf.close()
340 return (0, n)
340 return (0, n)
341
341
342 def qparents(self, repo, rev=None):
342 def qparents(self, repo, rev=None):
343 if rev is None:
343 if rev is None:
344 (p1, p2) = repo.dirstate.parents()
344 (p1, p2) = repo.dirstate.parents()
345 if p2 == revlog.nullid:
345 if p2 == revlog.nullid:
346 return p1
346 return p1
347 if len(self.applied) == 0:
347 if len(self.applied) == 0:
348 return None
348 return None
349 return revlog.bin(self.applied[-1].rev)
349 return revlog.bin(self.applied[-1].rev)
350 pp = repo.changelog.parents(rev)
350 pp = repo.changelog.parents(rev)
351 if pp[1] != revlog.nullid:
351 if pp[1] != revlog.nullid:
352 arevs = [ x.rev for x in self.applied ]
352 arevs = [ x.rev for x in self.applied ]
353 p0 = revlog.hex(pp[0])
353 p0 = revlog.hex(pp[0])
354 p1 = revlog.hex(pp[1])
354 p1 = revlog.hex(pp[1])
355 if p0 in arevs:
355 if p0 in arevs:
356 return pp[0]
356 return pp[0]
357 if p1 in arevs:
357 if p1 in arevs:
358 return pp[1]
358 return pp[1]
359 return pp[0]
359 return pp[0]
360
360
361 def mergepatch(self, repo, mergeq, series, wlock):
361 def mergepatch(self, repo, mergeq, series, wlock):
362 if len(self.applied) == 0:
362 if len(self.applied) == 0:
363 # each of the patches merged in will have two parents. This
363 # each of the patches merged in will have two parents. This
364 # can confuse the qrefresh, qdiff, and strip code because it
364 # can confuse the qrefresh, qdiff, and strip code because it
365 # needs to know which parent is actually in the patch queue.
365 # needs to know which parent is actually in the patch queue.
366 # so, we insert a merge marker with only one parent. This way
366 # so, we insert a merge marker with only one parent. This way
367 # the first patch in the queue is never a merge patch
367 # the first patch in the queue is never a merge patch
368 #
368 #
369 pname = ".hg.patches.merge.marker"
369 pname = ".hg.patches.merge.marker"
370 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
370 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
371 wlock=wlock)
371 wlock=wlock)
372 self.applied.append(statusentry(revlog.hex(n), pname))
372 self.applied.append(statusentry(revlog.hex(n), pname))
373 self.applied_dirty = 1
373 self.applied_dirty = 1
374
374
375 head = self.qparents(repo)
375 head = self.qparents(repo)
376
376
377 for patch in series:
377 for patch in series:
378 patch = mergeq.lookup(patch, strict=True)
378 patch = mergeq.lookup(patch, strict=True)
379 if not patch:
379 if not patch:
380 self.ui.warn("patch %s does not exist\n" % patch)
380 self.ui.warn("patch %s does not exist\n" % patch)
381 return (1, None)
381 return (1, None)
382 pushable, reason = self.pushable(patch)
382 pushable, reason = self.pushable(patch)
383 if not pushable:
383 if not pushable:
384 self.explain_pushable(patch, all_patches=True)
384 self.explain_pushable(patch, all_patches=True)
385 continue
385 continue
386 info = mergeq.isapplied(patch)
386 info = mergeq.isapplied(patch)
387 if not info:
387 if not info:
388 self.ui.warn("patch %s is not applied\n" % patch)
388 self.ui.warn("patch %s is not applied\n" % patch)
389 return (1, None)
389 return (1, None)
390 rev = revlog.bin(info[1])
390 rev = revlog.bin(info[1])
391 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
391 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
392 if head:
392 if head:
393 self.applied.append(statusentry(revlog.hex(head), patch))
393 self.applied.append(statusentry(revlog.hex(head), patch))
394 self.applied_dirty = 1
394 self.applied_dirty = 1
395 if err:
395 if err:
396 return (err, head)
396 return (err, head)
397 return (0, head)
397 return (0, head)
398
398
399 def patch(self, repo, patchfile):
399 def patch(self, repo, patchfile):
400 '''Apply patchfile to the working directory.
400 '''Apply patchfile to the working directory.
401 patchfile: file name of patch'''
401 patchfile: file name of patch'''
402 try:
402 try:
403 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
403 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
404 f = os.popen("%s -d %s -p1 --no-backup-if-mismatch < %s" %
404 f = os.popen("%s -d %s -p1 --no-backup-if-mismatch < %s" %
405 (pp, util.shellquote(repo.root), util.shellquote(patchfile)))
405 (pp, util.shellquote(repo.root), util.shellquote(patchfile)))
406 except:
406 except:
407 self.ui.warn("patch failed, unable to continue (try -v)\n")
407 self.ui.warn("patch failed, unable to continue (try -v)\n")
408 return (None, [], False)
408 return (None, [], False)
409 files = []
409 files = []
410 fuzz = False
410 fuzz = False
411 for l in f:
411 for l in f:
412 l = l.rstrip('\r\n');
412 l = l.rstrip('\r\n');
413 if self.ui.verbose:
413 if self.ui.verbose:
414 self.ui.warn(l + "\n")
414 self.ui.warn(l + "\n")
415 if l[:14] == 'patching file ':
415 if l[:14] == 'patching file ':
416 pf = os.path.normpath(util.parse_patch_output(l))
416 pf = os.path.normpath(util.parse_patch_output(l))
417 if pf not in files:
417 if pf not in files:
418 files.append(pf)
418 files.append(pf)
419 printed_file = False
419 printed_file = False
420 file_str = l
420 file_str = l
421 elif l.find('with fuzz') >= 0:
421 elif l.find('with fuzz') >= 0:
422 if not printed_file:
422 if not printed_file:
423 self.ui.warn(file_str + '\n')
423 self.ui.warn(file_str + '\n')
424 printed_file = True
424 printed_file = True
425 self.ui.warn(l + '\n')
425 self.ui.warn(l + '\n')
426 fuzz = True
426 fuzz = True
427 elif l.find('saving rejects to file') >= 0:
427 elif l.find('saving rejects to file') >= 0:
428 self.ui.warn(l + '\n')
428 self.ui.warn(l + '\n')
429 elif l.find('FAILED') >= 0:
429 elif l.find('FAILED') >= 0:
430 if not printed_file:
430 if not printed_file:
431 self.ui.warn(file_str + '\n')
431 self.ui.warn(file_str + '\n')
432 printed_file = True
432 printed_file = True
433 self.ui.warn(l + '\n')
433 self.ui.warn(l + '\n')
434
434
435 return (not f.close(), files, fuzz)
435 return (not f.close(), files, fuzz)
436
436
437 def apply(self, repo, series, list=False, update_status=True,
437 def apply(self, repo, series, list=False, update_status=True,
438 strict=False, patchdir=None, merge=None, wlock=None):
438 strict=False, patchdir=None, merge=None, wlock=None):
439 # TODO unify with commands.py
439 # TODO unify with commands.py
440 if not patchdir:
440 if not patchdir:
441 patchdir = self.path
441 patchdir = self.path
442 err = 0
442 err = 0
443 if not wlock:
443 if not wlock:
444 wlock = repo.wlock()
444 wlock = repo.wlock()
445 lock = repo.lock()
445 lock = repo.lock()
446 tr = repo.transaction()
446 tr = repo.transaction()
447 n = None
447 n = None
448 for patch in series:
448 for patch in series:
449 pushable, reason = self.pushable(patch)
449 pushable, reason = self.pushable(patch)
450 if not pushable:
450 if not pushable:
451 self.explain_pushable(patch, all_patches=True)
451 self.explain_pushable(patch, all_patches=True)
452 continue
452 continue
453 self.ui.warn("applying %s\n" % patch)
453 self.ui.warn("applying %s\n" % patch)
454 pf = os.path.join(patchdir, patch)
454 pf = os.path.join(patchdir, patch)
455
455
456 try:
456 try:
457 message, comments, user, date, patchfound = self.readheaders(patch)
457 message, comments, user, date, patchfound = self.readheaders(patch)
458 except:
458 except:
459 self.ui.warn("Unable to read %s\n" % pf)
459 self.ui.warn("Unable to read %s\n" % pf)
460 err = 1
460 err = 1
461 break
461 break
462
462
463 if not message:
463 if not message:
464 message = "imported patch %s\n" % patch
464 message = "imported patch %s\n" % patch
465 else:
465 else:
466 if list:
466 if list:
467 message.append("\nimported patch %s" % patch)
467 message.append("\nimported patch %s" % patch)
468 message = '\n'.join(message)
468 message = '\n'.join(message)
469
469
470 (patcherr, files, fuzz) = self.patch(repo, pf)
470 (patcherr, files, fuzz) = self.patch(repo, pf)
471 patcherr = not patcherr
471 patcherr = not patcherr
472
472
473 if merge and len(files) > 0:
473 if merge and len(files) > 0:
474 # Mark as merged and update dirstate parent info
474 # Mark as merged and update dirstate parent info
475 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
475 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
476 p1, p2 = repo.dirstate.parents()
476 p1, p2 = repo.dirstate.parents()
477 repo.dirstate.setparents(p1, merge)
477 repo.dirstate.setparents(p1, merge)
478 if len(files) > 0:
478 if len(files) > 0:
479 cwd = repo.getcwd()
479 cwd = repo.getcwd()
480 cfiles = files
480 cfiles = files
481 if cwd:
481 if cwd:
482 cfiles = [util.pathto(cwd, f) for f in files]
482 cfiles = [util.pathto(cwd, f) for f in files]
483 cmdutil.addremove(repo, cfiles, wlock=wlock)
483 cmdutil.addremove(repo, cfiles, wlock=wlock)
484 n = repo.commit(files, message, user, date, force=1, lock=lock,
484 n = repo.commit(files, message, user, date, force=1, lock=lock,
485 wlock=wlock)
485 wlock=wlock)
486
486
487 if n == None:
487 if n == None:
488 raise util.Abort(_("repo commit failed"))
488 raise util.Abort(_("repo commit failed"))
489
489
490 if update_status:
490 if update_status:
491 self.applied.append(statusentry(revlog.hex(n), patch))
491 self.applied.append(statusentry(revlog.hex(n), patch))
492
492
493 if patcherr:
493 if patcherr:
494 if not patchfound:
494 if not patchfound:
495 self.ui.warn("patch %s is empty\n" % patch)
495 self.ui.warn("patch %s is empty\n" % patch)
496 err = 0
496 err = 0
497 else:
497 else:
498 self.ui.warn("patch failed, rejects left in working dir\n")
498 self.ui.warn("patch failed, rejects left in working dir\n")
499 err = 1
499 err = 1
500 break
500 break
501
501
502 if fuzz and strict:
502 if fuzz and strict:
503 self.ui.warn("fuzz found when applying patch, stopping\n")
503 self.ui.warn("fuzz found when applying patch, stopping\n")
504 err = 1
504 err = 1
505 break
505 break
506 tr.close()
506 tr.close()
507 return (err, n)
507 return (err, n)
508
508
509 def delete(self, repo, patch, force=False):
509 def delete(self, repo, patch, force=False):
510 patch = self.lookup(patch, strict=True)
510 patch = self.lookup(patch, strict=True)
511 info = self.isapplied(patch)
511 info = self.isapplied(patch)
512 if info:
512 if info:
513 raise util.Abort(_("cannot delete applied patch %s") % patch)
513 raise util.Abort(_("cannot delete applied patch %s") % patch)
514 if patch not in self.series:
514 if patch not in self.series:
515 raise util.Abort(_("patch %s not in series file") % patch)
515 raise util.Abort(_("patch %s not in series file") % patch)
516 if force:
516 if force:
517 r = self.qrepo()
517 r = self.qrepo()
518 if r:
518 if r:
519 r.remove([patch], True)
519 r.remove([patch], True)
520 else:
520 else:
521 os.unlink(self.join(patch))
521 os.unlink(self.join(patch))
522 i = self.find_series(patch)
522 i = self.find_series(patch)
523 del self.full_series[i]
523 del self.full_series[i]
524 self.parse_series()
524 self.parse_series()
525 self.series_dirty = 1
525 self.series_dirty = 1
526
526
527 def check_toppatch(self, repo):
527 def check_toppatch(self, repo):
528 if len(self.applied) > 0:
528 if len(self.applied) > 0:
529 top = revlog.bin(self.applied[-1].rev)
529 top = revlog.bin(self.applied[-1].rev)
530 pp = repo.dirstate.parents()
530 pp = repo.dirstate.parents()
531 if top not in pp:
531 if top not in pp:
532 raise util.Abort(_("queue top not at same revision as working directory"))
532 raise util.Abort(_("queue top not at same revision as working directory"))
533 return top
533 return top
534 return None
534 return None
535 def check_localchanges(self, repo, force=False, refresh=True):
535 def check_localchanges(self, repo, force=False, refresh=True):
536 m, a, r, d = repo.status()[:4]
536 m, a, r, d = repo.status()[:4]
537 if m or a or r or d:
537 if m or a or r or d:
538 if not force:
538 if not force:
539 if refresh:
539 if refresh:
540 raise util.Abort(_("local changes found, refresh first"))
540 raise util.Abort(_("local changes found, refresh first"))
541 else:
541 else:
542 raise util.Abort(_("local changes found"))
542 raise util.Abort(_("local changes found"))
543 return m, a, r, d
543 return m, a, r, d
544 def new(self, repo, patch, msg=None, force=None):
544 def new(self, repo, patch, msg=None, force=None):
545 if os.path.exists(self.join(patch)):
545 if os.path.exists(self.join(patch)):
546 raise util.Abort(_('patch "%s" already exists') % patch)
546 raise util.Abort(_('patch "%s" already exists') % patch)
547 m, a, r, d = self.check_localchanges(repo, force)
547 m, a, r, d = self.check_localchanges(repo, force)
548 commitfiles = m + a + r
548 commitfiles = m + a + r
549 self.check_toppatch(repo)
549 self.check_toppatch(repo)
550 wlock = repo.wlock()
550 wlock = repo.wlock()
551 insert = self.full_series_end()
551 insert = self.full_series_end()
552 if msg:
552 if msg:
553 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
553 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
554 wlock=wlock)
554 wlock=wlock)
555 else:
555 else:
556 n = repo.commit(commitfiles,
556 n = repo.commit(commitfiles,
557 "New patch: %s" % patch, force=True, wlock=wlock)
557 "New patch: %s" % patch, force=True, wlock=wlock)
558 if n == None:
558 if n == None:
559 raise util.Abort(_("repo commit failed"))
559 raise util.Abort(_("repo commit failed"))
560 self.full_series[insert:insert] = [patch]
560 self.full_series[insert:insert] = [patch]
561 self.applied.append(statusentry(revlog.hex(n), patch))
561 self.applied.append(statusentry(revlog.hex(n), patch))
562 self.parse_series()
562 self.parse_series()
563 self.series_dirty = 1
563 self.series_dirty = 1
564 self.applied_dirty = 1
564 self.applied_dirty = 1
565 p = self.opener(patch, "w")
565 p = self.opener(patch, "w")
566 if msg:
566 if msg:
567 msg = msg + "\n"
567 msg = msg + "\n"
568 p.write(msg)
568 p.write(msg)
569 p.close()
569 p.close()
570 wlock = None
570 wlock = None
571 r = self.qrepo()
571 r = self.qrepo()
572 if r: r.add([patch])
572 if r: r.add([patch])
573 if commitfiles:
573 if commitfiles:
574 self.refresh(repo, short=True)
574 self.refresh(repo, short=True)
575
575
576 def strip(self, repo, rev, update=True, backup="all", wlock=None):
576 def strip(self, repo, rev, update=True, backup="all", wlock=None):
577 def limitheads(chlog, stop):
577 def limitheads(chlog, stop):
578 """return the list of all nodes that have no children"""
578 """return the list of all nodes that have no children"""
579 p = {}
579 p = {}
580 h = []
580 h = []
581 stoprev = 0
581 stoprev = 0
582 if stop in chlog.nodemap:
582 if stop in chlog.nodemap:
583 stoprev = chlog.rev(stop)
583 stoprev = chlog.rev(stop)
584
584
585 for r in range(chlog.count() - 1, -1, -1):
585 for r in range(chlog.count() - 1, -1, -1):
586 n = chlog.node(r)
586 n = chlog.node(r)
587 if n not in p:
587 if n not in p:
588 h.append(n)
588 h.append(n)
589 if n == stop:
589 if n == stop:
590 break
590 break
591 if r < stoprev:
591 if r < stoprev:
592 break
592 break
593 for pn in chlog.parents(n):
593 for pn in chlog.parents(n):
594 p[pn] = 1
594 p[pn] = 1
595 return h
595 return h
596
596
597 def bundle(cg):
597 def bundle(cg):
598 backupdir = repo.join("strip-backup")
598 backupdir = repo.join("strip-backup")
599 if not os.path.isdir(backupdir):
599 if not os.path.isdir(backupdir):
600 os.mkdir(backupdir)
600 os.mkdir(backupdir)
601 name = os.path.join(backupdir, "%s" % revlog.short(rev))
601 name = os.path.join(backupdir, "%s" % revlog.short(rev))
602 name = savename(name)
602 name = savename(name)
603 self.ui.warn("saving bundle to %s\n" % name)
603 self.ui.warn("saving bundle to %s\n" % name)
604 # TODO, exclusive open
604 # TODO, exclusive open
605 f = open(name, "wb")
605 f = open(name, "wb")
606 try:
606 try:
607 f.write("HG10")
607 f.write("HG10")
608 z = bz2.BZ2Compressor(9)
608 z = bz2.BZ2Compressor(9)
609 while 1:
609 while 1:
610 chunk = cg.read(4096)
610 chunk = cg.read(4096)
611 if not chunk:
611 if not chunk:
612 break
612 break
613 f.write(z.compress(chunk))
613 f.write(z.compress(chunk))
614 f.write(z.flush())
614 f.write(z.flush())
615 except:
615 except:
616 os.unlink(name)
616 os.unlink(name)
617 raise
617 raise
618 f.close()
618 f.close()
619 return name
619 return name
620
620
621 def stripall(rev, revnum):
621 def stripall(rev, revnum):
622 cl = repo.changelog
622 cl = repo.changelog
623 c = cl.read(rev)
623 c = cl.read(rev)
624 mm = repo.manifest.read(c[0])
624 mm = repo.manifest.read(c[0])
625 seen = {}
625 seen = {}
626
626
627 for x in xrange(revnum, cl.count()):
627 for x in xrange(revnum, cl.count()):
628 c = cl.read(cl.node(x))
628 c = cl.read(cl.node(x))
629 for f in c[3]:
629 for f in c[3]:
630 if f in seen:
630 if f in seen:
631 continue
631 continue
632 seen[f] = 1
632 seen[f] = 1
633 if f in mm:
633 if f in mm:
634 filerev = mm[f]
634 filerev = mm[f]
635 else:
635 else:
636 filerev = 0
636 filerev = 0
637 seen[f] = filerev
637 seen[f] = filerev
638 # we go in two steps here so the strip loop happens in a
638 # we go in two steps here so the strip loop happens in a
639 # sensible order. When stripping many files, this helps keep
639 # sensible order. When stripping many files, this helps keep
640 # our disk access patterns under control.
640 # our disk access patterns under control.
641 seen_list = seen.keys()
641 seen_list = seen.keys()
642 seen_list.sort()
642 seen_list.sort()
643 for f in seen_list:
643 for f in seen_list:
644 ff = repo.file(f)
644 ff = repo.file(f)
645 filerev = seen[f]
645 filerev = seen[f]
646 if filerev != 0:
646 if filerev != 0:
647 if filerev in ff.nodemap:
647 if filerev in ff.nodemap:
648 filerev = ff.rev(filerev)
648 filerev = ff.rev(filerev)
649 else:
649 else:
650 filerev = 0
650 filerev = 0
651 ff.strip(filerev, revnum)
651 ff.strip(filerev, revnum)
652
652
653 if not wlock:
653 if not wlock:
654 wlock = repo.wlock()
654 wlock = repo.wlock()
655 lock = repo.lock()
655 lock = repo.lock()
656 chlog = repo.changelog
656 chlog = repo.changelog
657 # TODO delete the undo files, and handle undo of merge sets
657 # TODO delete the undo files, and handle undo of merge sets
658 pp = chlog.parents(rev)
658 pp = chlog.parents(rev)
659 revnum = chlog.rev(rev)
659 revnum = chlog.rev(rev)
660
660
661 if update:
661 if update:
662 self.check_localchanges(repo, refresh=False)
662 self.check_localchanges(repo, refresh=False)
663 urev = self.qparents(repo, rev)
663 urev = self.qparents(repo, rev)
664 hg.clean(repo, urev, wlock=wlock)
664 hg.clean(repo, urev, wlock=wlock)
665 repo.dirstate.write()
665 repo.dirstate.write()
666
666
667 # save is a list of all the branches we are truncating away
667 # save is a list of all the branches we are truncating away
668 # that we actually want to keep. changegroup will be used
668 # that we actually want to keep. changegroup will be used
669 # to preserve them and add them back after the truncate
669 # to preserve them and add them back after the truncate
670 saveheads = []
670 saveheads = []
671 savebases = {}
671 savebases = {}
672
672
673 heads = limitheads(chlog, rev)
673 heads = limitheads(chlog, rev)
674 seen = {}
674 seen = {}
675
675
676 # search through all the heads, finding those where the revision
676 # search through all the heads, finding those where the revision
677 # we want to strip away is an ancestor. Also look for merges
677 # we want to strip away is an ancestor. Also look for merges
678 # that might be turned into new heads by the strip.
678 # that might be turned into new heads by the strip.
679 while heads:
679 while heads:
680 h = heads.pop()
680 h = heads.pop()
681 n = h
681 n = h
682 while True:
682 while True:
683 seen[n] = 1
683 seen[n] = 1
684 pp = chlog.parents(n)
684 pp = chlog.parents(n)
685 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
685 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
686 if pp[1] not in seen:
686 if pp[1] not in seen:
687 heads.append(pp[1])
687 heads.append(pp[1])
688 if pp[0] == revlog.nullid:
688 if pp[0] == revlog.nullid:
689 break
689 break
690 if chlog.rev(pp[0]) < revnum:
690 if chlog.rev(pp[0]) < revnum:
691 break
691 break
692 n = pp[0]
692 n = pp[0]
693 if n == rev:
693 if n == rev:
694 break
694 break
695 r = chlog.reachable(h, rev)
695 r = chlog.reachable(h, rev)
696 if rev not in r:
696 if rev not in r:
697 saveheads.append(h)
697 saveheads.append(h)
698 for x in r:
698 for x in r:
699 if chlog.rev(x) > revnum:
699 if chlog.rev(x) > revnum:
700 savebases[x] = 1
700 savebases[x] = 1
701
701
702 # create a changegroup for all the branches we need to keep
702 # create a changegroup for all the branches we need to keep
703 if backup == "all":
703 if backup == "all":
704 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
704 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
705 bundle(backupch)
705 bundle(backupch)
706 if saveheads:
706 if saveheads:
707 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
707 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
708 chgrpfile = bundle(backupch)
708 chgrpfile = bundle(backupch)
709
709
710 stripall(rev, revnum)
710 stripall(rev, revnum)
711
711
712 change = chlog.read(rev)
712 change = chlog.read(rev)
713 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
713 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
714 chlog.strip(revnum, revnum)
714 chlog.strip(revnum, revnum)
715 if saveheads:
715 if saveheads:
716 self.ui.status("adding branch\n")
716 self.ui.status("adding branch\n")
717 commands.unbundle(self.ui, repo, chgrpfile, update=False)
717 commands.unbundle(self.ui, repo, chgrpfile, update=False)
718 if backup != "strip":
718 if backup != "strip":
719 os.unlink(chgrpfile)
719 os.unlink(chgrpfile)
720
720
721 def isapplied(self, patch):
721 def isapplied(self, patch):
722 """returns (index, rev, patch)"""
722 """returns (index, rev, patch)"""
723 for i in xrange(len(self.applied)):
723 for i in xrange(len(self.applied)):
724 a = self.applied[i]
724 a = self.applied[i]
725 if a.name == patch:
725 if a.name == patch:
726 return (i, a.rev, a.name)
726 return (i, a.rev, a.name)
727 return None
727 return None
728
728
729 # if the exact patch name does not exist, we try a few
729 # if the exact patch name does not exist, we try a few
730 # variations. If strict is passed, we try only #1
730 # variations. If strict is passed, we try only #1
731 #
731 #
732 # 1) a number to indicate an offset in the series file
732 # 1) a number to indicate an offset in the series file
733 # 2) a unique substring of the patch name was given
733 # 2) a unique substring of the patch name was given
734 # 3) patchname[-+]num to indicate an offset in the series file
734 # 3) patchname[-+]num to indicate an offset in the series file
735 def lookup(self, patch, strict=False):
735 def lookup(self, patch, strict=False):
736 patch = patch and str(patch)
736 patch = patch and str(patch)
737
737
738 def partial_name(s):
738 def partial_name(s):
739 if s in self.series:
739 if s in self.series:
740 return s
740 return s
741 matches = [x for x in self.series if s in x]
741 matches = [x for x in self.series if s in x]
742 if len(matches) > 1:
742 if len(matches) > 1:
743 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
743 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
744 for m in matches:
744 for m in matches:
745 self.ui.warn(' %s\n' % m)
745 self.ui.warn(' %s\n' % m)
746 return None
746 return None
747 if matches:
747 if matches:
748 return matches[0]
748 return matches[0]
749 if len(self.series) > 0 and len(self.applied) > 0:
749 if len(self.series) > 0 and len(self.applied) > 0:
750 if s == 'qtip':
750 if s == 'qtip':
751 return self.series[self.series_end()-1]
751 return self.series[self.series_end()-1]
752 if s == 'qbase':
752 if s == 'qbase':
753 return self.series[0]
753 return self.series[0]
754 return None
754 return None
755 if patch == None:
755 if patch == None:
756 return None
756 return None
757
757
758 # we don't want to return a partial match until we make
758 # we don't want to return a partial match until we make
759 # sure the file name passed in does not exist (checked below)
759 # sure the file name passed in does not exist (checked below)
760 res = partial_name(patch)
760 res = partial_name(patch)
761 if res and res == patch:
761 if res and res == patch:
762 return res
762 return res
763
763
764 if not os.path.isfile(self.join(patch)):
764 if not os.path.isfile(self.join(patch)):
765 try:
765 try:
766 sno = int(patch)
766 sno = int(patch)
767 except(ValueError, OverflowError):
767 except(ValueError, OverflowError):
768 pass
768 pass
769 else:
769 else:
770 if sno < len(self.series):
770 if sno < len(self.series):
771 return self.series[sno]
771 return self.series[sno]
772 if not strict:
772 if not strict:
773 # return any partial match made above
773 # return any partial match made above
774 if res:
774 if res:
775 return res
775 return res
776 minus = patch.rsplit('-', 1)
776 minus = patch.rsplit('-', 1)
777 if len(minus) > 1:
777 if len(minus) > 1:
778 res = partial_name(minus[0])
778 res = partial_name(minus[0])
779 if res:
779 if res:
780 i = self.series.index(res)
780 i = self.series.index(res)
781 try:
781 try:
782 off = int(minus[1] or 1)
782 off = int(minus[1] or 1)
783 except(ValueError, OverflowError):
783 except(ValueError, OverflowError):
784 pass
784 pass
785 else:
785 else:
786 if i - off >= 0:
786 if i - off >= 0:
787 return self.series[i - off]
787 return self.series[i - off]
788 plus = patch.rsplit('+', 1)
788 plus = patch.rsplit('+', 1)
789 if len(plus) > 1:
789 if len(plus) > 1:
790 res = partial_name(plus[0])
790 res = partial_name(plus[0])
791 if res:
791 if res:
792 i = self.series.index(res)
792 i = self.series.index(res)
793 try:
793 try:
794 off = int(plus[1] or 1)
794 off = int(plus[1] or 1)
795 except(ValueError, OverflowError):
795 except(ValueError, OverflowError):
796 pass
796 pass
797 else:
797 else:
798 if i + off < len(self.series):
798 if i + off < len(self.series):
799 return self.series[i + off]
799 return self.series[i + off]
800 raise util.Abort(_("patch %s not in series") % patch)
800 raise util.Abort(_("patch %s not in series") % patch)
801
801
802 def push(self, repo, patch=None, force=False, list=False,
802 def push(self, repo, patch=None, force=False, list=False,
803 mergeq=None, wlock=None):
803 mergeq=None, wlock=None):
804 if not wlock:
804 if not wlock:
805 wlock = repo.wlock()
805 wlock = repo.wlock()
806 patch = self.lookup(patch)
806 patch = self.lookup(patch)
807 if patch and self.isapplied(patch):
807 if patch and self.isapplied(patch):
808 self.ui.warn(_("patch %s is already applied\n") % patch)
808 self.ui.warn(_("patch %s is already applied\n") % patch)
809 sys.exit(1)
809 sys.exit(1)
810 if self.series_end() == len(self.series):
810 if self.series_end() == len(self.series):
811 self.ui.warn(_("patch series fully applied\n"))
811 self.ui.warn(_("patch series fully applied\n"))
812 sys.exit(1)
812 sys.exit(1)
813 if not force:
813 if not force:
814 self.check_localchanges(repo)
814 self.check_localchanges(repo)
815
815
816 self.applied_dirty = 1;
816 self.applied_dirty = 1;
817 start = self.series_end()
817 start = self.series_end()
818 if start > 0:
818 if start > 0:
819 self.check_toppatch(repo)
819 self.check_toppatch(repo)
820 if not patch:
820 if not patch:
821 patch = self.series[start]
821 patch = self.series[start]
822 end = start + 1
822 end = start + 1
823 else:
823 else:
824 end = self.series.index(patch, start) + 1
824 end = self.series.index(patch, start) + 1
825 s = self.series[start:end]
825 s = self.series[start:end]
826 if mergeq:
826 if mergeq:
827 ret = self.mergepatch(repo, mergeq, s, wlock)
827 ret = self.mergepatch(repo, mergeq, s, wlock)
828 else:
828 else:
829 ret = self.apply(repo, s, list, wlock=wlock)
829 ret = self.apply(repo, s, list, wlock=wlock)
830 top = self.applied[-1].name
830 top = self.applied[-1].name
831 if ret[0]:
831 if ret[0]:
832 self.ui.write("Errors during apply, please fix and refresh %s\n" %
832 self.ui.write("Errors during apply, please fix and refresh %s\n" %
833 top)
833 top)
834 else:
834 else:
835 self.ui.write("Now at: %s\n" % top)
835 self.ui.write("Now at: %s\n" % top)
836 return ret[0]
836 return ret[0]
837
837
838 def pop(self, repo, patch=None, force=False, update=True, all=False,
838 def pop(self, repo, patch=None, force=False, update=True, all=False,
839 wlock=None):
839 wlock=None):
840 def getfile(f, rev):
840 def getfile(f, rev):
841 t = repo.file(f).read(rev)
841 t = repo.file(f).read(rev)
842 try:
842 try:
843 repo.wfile(f, "w").write(t)
843 repo.wfile(f, "w").write(t)
844 except IOError:
844 except IOError:
845 try:
845 try:
846 os.makedirs(os.path.dirname(repo.wjoin(f)))
846 os.makedirs(os.path.dirname(repo.wjoin(f)))
847 except OSError, err:
847 except OSError, err:
848 if err.errno != errno.EEXIST: raise
848 if err.errno != errno.EEXIST: raise
849 repo.wfile(f, "w").write(t)
849 repo.wfile(f, "w").write(t)
850
850
851 if not wlock:
851 if not wlock:
852 wlock = repo.wlock()
852 wlock = repo.wlock()
853 if patch:
853 if patch:
854 # index, rev, patch
854 # index, rev, patch
855 info = self.isapplied(patch)
855 info = self.isapplied(patch)
856 if not info:
856 if not info:
857 patch = self.lookup(patch)
857 patch = self.lookup(patch)
858 info = self.isapplied(patch)
858 info = self.isapplied(patch)
859 if not info:
859 if not info:
860 raise util.Abort(_("patch %s is not applied") % patch)
860 raise util.Abort(_("patch %s is not applied") % patch)
861 if len(self.applied) == 0:
861 if len(self.applied) == 0:
862 self.ui.warn(_("no patches applied\n"))
862 self.ui.warn(_("no patches applied\n"))
863 sys.exit(1)
863 sys.exit(1)
864
864
865 if not update:
865 if not update:
866 parents = repo.dirstate.parents()
866 parents = repo.dirstate.parents()
867 rr = [ revlog.bin(x.rev) for x in self.applied ]
867 rr = [ revlog.bin(x.rev) for x in self.applied ]
868 for p in parents:
868 for p in parents:
869 if p in rr:
869 if p in rr:
870 self.ui.warn("qpop: forcing dirstate update\n")
870 self.ui.warn("qpop: forcing dirstate update\n")
871 update = True
871 update = True
872
872
873 if not force and update:
873 if not force and update:
874 self.check_localchanges(repo)
874 self.check_localchanges(repo)
875
875
876 self.applied_dirty = 1;
876 self.applied_dirty = 1;
877 end = len(self.applied)
877 end = len(self.applied)
878 if not patch:
878 if not patch:
879 if all:
879 if all:
880 popi = 0
880 popi = 0
881 else:
881 else:
882 popi = len(self.applied) - 1
882 popi = len(self.applied) - 1
883 else:
883 else:
884 popi = info[0] + 1
884 popi = info[0] + 1
885 if popi >= end:
885 if popi >= end:
886 self.ui.warn("qpop: %s is already at the top\n" % patch)
886 self.ui.warn("qpop: %s is already at the top\n" % patch)
887 return
887 return
888 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
888 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
889
889
890 start = info[0]
890 start = info[0]
891 rev = revlog.bin(info[1])
891 rev = revlog.bin(info[1])
892
892
893 # we know there are no local changes, so we can make a simplified
893 # we know there are no local changes, so we can make a simplified
894 # form of hg.update.
894 # form of hg.update.
895 if update:
895 if update:
896 top = self.check_toppatch(repo)
896 top = self.check_toppatch(repo)
897 qp = self.qparents(repo, rev)
897 qp = self.qparents(repo, rev)
898 changes = repo.changelog.read(qp)
898 changes = repo.changelog.read(qp)
899 mmap = repo.manifest.read(changes[0])
899 mmap = repo.manifest.read(changes[0])
900 m, a, r, d, u = repo.status(qp, top)[:5]
900 m, a, r, d, u = repo.status(qp, top)[:5]
901 if d:
901 if d:
902 raise util.Abort("deletions found between repo revs")
902 raise util.Abort("deletions found between repo revs")
903 for f in m:
903 for f in m:
904 getfile(f, mmap[f])
904 getfile(f, mmap[f])
905 for f in r:
905 for f in r:
906 getfile(f, mmap[f])
906 getfile(f, mmap[f])
907 util.set_exec(repo.wjoin(f), mmap.execf(f))
907 util.set_exec(repo.wjoin(f), mmap.execf(f))
908 repo.dirstate.update(m + r, 'n')
908 repo.dirstate.update(m + r, 'n')
909 for f in a:
909 for f in a:
910 try: os.unlink(repo.wjoin(f))
910 try: os.unlink(repo.wjoin(f))
911 except: raise
911 except: raise
912 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
912 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
913 except: pass
913 except: pass
914 if a:
914 if a:
915 repo.dirstate.forget(a)
915 repo.dirstate.forget(a)
916 repo.dirstate.setparents(qp, revlog.nullid)
916 repo.dirstate.setparents(qp, revlog.nullid)
917 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
917 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
918 del self.applied[start:end]
918 del self.applied[start:end]
919 if len(self.applied):
919 if len(self.applied):
920 self.ui.write("Now at: %s\n" % self.applied[-1].name)
920 self.ui.write("Now at: %s\n" % self.applied[-1].name)
921 else:
921 else:
922 self.ui.write("Patch queue now empty\n")
922 self.ui.write("Patch queue now empty\n")
923
923
924 def diff(self, repo, files):
924 def diff(self, repo, files):
925 top = self.check_toppatch(repo)
925 top = self.check_toppatch(repo)
926 if not top:
926 if not top:
927 self.ui.write("No patches applied\n")
927 self.ui.write("No patches applied\n")
928 return
928 return
929 qp = self.qparents(repo, top)
929 qp = self.qparents(repo, top)
930 self.printdiff(repo, qp, files=files)
930 self.printdiff(repo, qp, files=files)
931
931
932 def refresh(self, repo, msg='', short=False):
932 def refresh(self, repo, msg='', short=False):
933 if len(self.applied) == 0:
933 if len(self.applied) == 0:
934 self.ui.write("No patches applied\n")
934 self.ui.write("No patches applied\n")
935 return
935 return
936 wlock = repo.wlock()
936 wlock = repo.wlock()
937 self.check_toppatch(repo)
937 self.check_toppatch(repo)
938 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
938 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
939 top = revlog.bin(top)
939 top = revlog.bin(top)
940 cparents = repo.changelog.parents(top)
940 cparents = repo.changelog.parents(top)
941 patchparent = self.qparents(repo, top)
941 patchparent = self.qparents(repo, top)
942 message, comments, user, date, patchfound = self.readheaders(patch)
942 message, comments, user, date, patchfound = self.readheaders(patch)
943
943
944 patchf = self.opener(patch, "w")
944 patchf = self.opener(patch, "w")
945 msg = msg.rstrip()
945 msg = msg.rstrip()
946 if msg:
946 if msg:
947 if comments:
947 if comments:
948 # Remove existing message.
948 # Remove existing message.
949 ci = 0
949 ci = 0
950 for mi in range(len(message)):
950 for mi in range(len(message)):
951 while message[mi] != comments[ci]:
951 while message[mi] != comments[ci]:
952 ci += 1
952 ci += 1
953 del comments[ci]
953 del comments[ci]
954 comments.append(msg)
954 comments.append(msg)
955 if comments:
955 if comments:
956 comments = "\n".join(comments) + '\n\n'
956 comments = "\n".join(comments) + '\n\n'
957 patchf.write(comments)
957 patchf.write(comments)
958
958
959 tip = repo.changelog.tip()
959 tip = repo.changelog.tip()
960 if top == tip:
960 if top == tip:
961 # if the top of our patch queue is also the tip, there is an
961 # if the top of our patch queue is also the tip, there is an
962 # optimization here. We update the dirstate in place and strip
962 # optimization here. We update the dirstate in place and strip
963 # off the tip commit. Then just commit the current directory
963 # off the tip commit. Then just commit the current directory
964 # tree. We can also send repo.commit the list of files
964 # tree. We can also send repo.commit the list of files
965 # changed to speed up the diff
965 # changed to speed up the diff
966 #
966 #
967 # in short mode, we only diff the files included in the
967 # in short mode, we only diff the files included in the
968 # patch already
968 # patch already
969 #
969 #
970 # this should really read:
970 # this should really read:
971 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
971 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
972 # but we do it backwards to take advantage of manifest/chlog
972 # but we do it backwards to take advantage of manifest/chlog
973 # caching against the next repo.status call
973 # caching against the next repo.status call
974 #
974 #
975 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
975 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
976 if short:
976 if short:
977 filelist = mm + aa + dd
977 filelist = mm + aa + dd
978 else:
978 else:
979 filelist = None
979 filelist = None
980 m, a, r, d, u = repo.status(files=filelist)[:5]
980 m, a, r, d, u = repo.status(files=filelist)[:5]
981
981
982 # we might end up with files that were added between tip and
982 # we might end up with files that were added between tip and
983 # the dirstate parent, but then changed in the local dirstate.
983 # the dirstate parent, but then changed in the local dirstate.
984 # in this case, we want them to only show up in the added section
984 # in this case, we want them to only show up in the added section
985 for x in m:
985 for x in m:
986 if x not in aa:
986 if x not in aa:
987 mm.append(x)
987 mm.append(x)
988 # we might end up with files added by the local dirstate that
988 # we might end up with files added by the local dirstate that
989 # were deleted by the patch. In this case, they should only
989 # were deleted by the patch. In this case, they should only
990 # show up in the changed section.
990 # show up in the changed section.
991 for x in a:
991 for x in a:
992 if x in dd:
992 if x in dd:
993 del dd[dd.index(x)]
993 del dd[dd.index(x)]
994 mm.append(x)
994 mm.append(x)
995 else:
995 else:
996 aa.append(x)
996 aa.append(x)
997 # make sure any files deleted in the local dirstate
997 # make sure any files deleted in the local dirstate
998 # are not in the add or change column of the patch
998 # are not in the add or change column of the patch
999 forget = []
999 forget = []
1000 for x in d + r:
1000 for x in d + r:
1001 if x in aa:
1001 if x in aa:
1002 del aa[aa.index(x)]
1002 del aa[aa.index(x)]
1003 forget.append(x)
1003 forget.append(x)
1004 continue
1004 continue
1005 elif x in mm:
1005 elif x in mm:
1006 del mm[mm.index(x)]
1006 del mm[mm.index(x)]
1007 dd.append(x)
1007 dd.append(x)
1008
1008
1009 m = list(util.unique(mm))
1009 m = list(util.unique(mm))
1010 r = list(util.unique(dd))
1010 r = list(util.unique(dd))
1011 a = list(util.unique(aa))
1011 a = list(util.unique(aa))
1012 filelist = list(util.unique(m + r + a))
1012 filelist = list(util.unique(m + r + a))
1013 self.printdiff(repo, patchparent, files=filelist,
1013 self.printdiff(repo, patchparent, files=filelist,
1014 changes=(m, a, r, [], u), fp=patchf)
1014 changes=(m, a, r, [], u), fp=patchf)
1015 patchf.close()
1015 patchf.close()
1016
1016
1017 changes = repo.changelog.read(tip)
1017 changes = repo.changelog.read(tip)
1018 repo.dirstate.setparents(*cparents)
1018 repo.dirstate.setparents(*cparents)
1019 repo.dirstate.update(a, 'a')
1019 repo.dirstate.update(a, 'a')
1020 repo.dirstate.update(r, 'r')
1020 repo.dirstate.update(r, 'r')
1021 repo.dirstate.update(m, 'n')
1021 repo.dirstate.update(m, 'n')
1022 repo.dirstate.forget(forget)
1022 repo.dirstate.forget(forget)
1023
1023
1024 if not msg:
1024 if not msg:
1025 if not message:
1025 if not message:
1026 message = "patch queue: %s\n" % patch
1026 message = "patch queue: %s\n" % patch
1027 else:
1027 else:
1028 message = "\n".join(message)
1028 message = "\n".join(message)
1029 else:
1029 else:
1030 message = msg
1030 message = msg
1031
1031
1032 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1032 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1033 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1033 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1034 self.applied[-1] = statusentry(revlog.hex(n), patch)
1034 self.applied[-1] = statusentry(revlog.hex(n), patch)
1035 self.applied_dirty = 1
1035 self.applied_dirty = 1
1036 else:
1036 else:
1037 self.printdiff(repo, patchparent, fp=patchf)
1037 self.printdiff(repo, patchparent, fp=patchf)
1038 patchf.close()
1038 patchf.close()
1039 self.pop(repo, force=True, wlock=wlock)
1039 self.pop(repo, force=True, wlock=wlock)
1040 self.push(repo, force=True, wlock=wlock)
1040 self.push(repo, force=True, wlock=wlock)
1041
1041
1042 def init(self, repo, create=False):
1042 def init(self, repo, create=False):
1043 if os.path.isdir(self.path):
1043 if os.path.isdir(self.path):
1044 raise util.Abort(_("patch queue directory already exists"))
1044 raise util.Abort(_("patch queue directory already exists"))
1045 os.mkdir(self.path)
1045 os.mkdir(self.path)
1046 if create:
1046 if create:
1047 return self.qrepo(create=True)
1047 return self.qrepo(create=True)
1048
1048
1049 def unapplied(self, repo, patch=None):
1049 def unapplied(self, repo, patch=None):
1050 if patch and patch not in self.series:
1050 if patch and patch not in self.series:
1051 raise util.Abort(_("patch %s is not in series file") % patch)
1051 raise util.Abort(_("patch %s is not in series file") % patch)
1052 if not patch:
1052 if not patch:
1053 start = self.series_end()
1053 start = self.series_end()
1054 else:
1054 else:
1055 start = self.series.index(patch) + 1
1055 start = self.series.index(patch) + 1
1056 unapplied = []
1056 unapplied = []
1057 for i in xrange(start, len(self.series)):
1057 for i in xrange(start, len(self.series)):
1058 pushable, reason = self.pushable(i)
1058 pushable, reason = self.pushable(i)
1059 if pushable:
1059 if pushable:
1060 unapplied.append((i, self.series[i]))
1060 unapplied.append((i, self.series[i]))
1061 self.explain_pushable(i)
1061 self.explain_pushable(i)
1062 return unapplied
1062 return unapplied
1063
1063
1064 def qseries(self, repo, missing=None, summary=False):
1064 def qseries(self, repo, missing=None, summary=False):
1065 start = self.series_end(all_patches=True)
1065 start = self.series_end(all_patches=True)
1066 if not missing:
1066 if not missing:
1067 for i in range(len(self.series)):
1067 for i in range(len(self.series)):
1068 patch = self.series[i]
1068 patch = self.series[i]
1069 if self.ui.verbose:
1069 if self.ui.verbose:
1070 if i < start:
1070 if i < start:
1071 status = 'A'
1071 status = 'A'
1072 elif self.pushable(i)[0]:
1072 elif self.pushable(i)[0]:
1073 status = 'U'
1073 status = 'U'
1074 else:
1074 else:
1075 status = 'G'
1075 status = 'G'
1076 self.ui.write('%d %s ' % (i, status))
1076 self.ui.write('%d %s ' % (i, status))
1077 if summary:
1077 if summary:
1078 msg = self.readheaders(patch)[0]
1078 msg = self.readheaders(patch)[0]
1079 msg = msg and ': ' + msg[0] or ': '
1079 msg = msg and ': ' + msg[0] or ': '
1080 else:
1080 else:
1081 msg = ''
1081 msg = ''
1082 self.ui.write('%s%s\n' % (patch, msg))
1082 self.ui.write('%s%s\n' % (patch, msg))
1083 else:
1083 else:
1084 msng_list = []
1084 msng_list = []
1085 for root, dirs, files in os.walk(self.path):
1085 for root, dirs, files in os.walk(self.path):
1086 d = root[len(self.path) + 1:]
1086 d = root[len(self.path) + 1:]
1087 for f in files:
1087 for f in files:
1088 fl = os.path.join(d, f)
1088 fl = os.path.join(d, f)
1089 if (fl not in self.series and
1089 if (fl not in self.series and
1090 fl not in (self.status_path, self.series_path)
1090 fl not in (self.status_path, self.series_path)
1091 and not fl.startswith('.')):
1091 and not fl.startswith('.')):
1092 msng_list.append(fl)
1092 msng_list.append(fl)
1093 msng_list.sort()
1093 msng_list.sort()
1094 for x in msng_list:
1094 for x in msng_list:
1095 if self.ui.verbose:
1095 if self.ui.verbose:
1096 self.ui.write("D ")
1096 self.ui.write("D ")
1097 self.ui.write("%s\n" % x)
1097 self.ui.write("%s\n" % x)
1098
1098
1099 def issaveline(self, l):
1099 def issaveline(self, l):
1100 if l.name == '.hg.patches.save.line':
1100 if l.name == '.hg.patches.save.line':
1101 return True
1101 return True
1102
1102
1103 def qrepo(self, create=False):
1103 def qrepo(self, create=False):
1104 if create or os.path.isdir(self.join(".hg")):
1104 if create or os.path.isdir(self.join(".hg")):
1105 return hg.repository(self.ui, path=self.path, create=create)
1105 return hg.repository(self.ui, path=self.path, create=create)
1106
1106
1107 def restore(self, repo, rev, delete=None, qupdate=None):
1107 def restore(self, repo, rev, delete=None, qupdate=None):
1108 c = repo.changelog.read(rev)
1108 c = repo.changelog.read(rev)
1109 desc = c[4].strip()
1109 desc = c[4].strip()
1110 lines = desc.splitlines()
1110 lines = desc.splitlines()
1111 i = 0
1111 i = 0
1112 datastart = None
1112 datastart = None
1113 series = []
1113 series = []
1114 applied = []
1114 applied = []
1115 qpp = None
1115 qpp = None
1116 for i in xrange(0, len(lines)):
1116 for i in xrange(0, len(lines)):
1117 if lines[i] == 'Patch Data:':
1117 if lines[i] == 'Patch Data:':
1118 datastart = i + 1
1118 datastart = i + 1
1119 elif lines[i].startswith('Dirstate:'):
1119 elif lines[i].startswith('Dirstate:'):
1120 l = lines[i].rstrip()
1120 l = lines[i].rstrip()
1121 l = l[10:].split(' ')
1121 l = l[10:].split(' ')
1122 qpp = [ hg.bin(x) for x in l ]
1122 qpp = [ hg.bin(x) for x in l ]
1123 elif datastart != None:
1123 elif datastart != None:
1124 l = lines[i].rstrip()
1124 l = lines[i].rstrip()
1125 se = statusentry(l)
1125 se = statusentry(l)
1126 file_ = se.name
1126 file_ = se.name
1127 if se.rev:
1127 if se.rev:
1128 applied.append(se)
1128 applied.append(se)
1129 series.append(file_)
1129 series.append(file_)
1130 if datastart == None:
1130 if datastart == None:
1131 self.ui.warn("No saved patch data found\n")
1131 self.ui.warn("No saved patch data found\n")
1132 return 1
1132 return 1
1133 self.ui.warn("restoring status: %s\n" % lines[0])
1133 self.ui.warn("restoring status: %s\n" % lines[0])
1134 self.full_series = series
1134 self.full_series = series
1135 self.applied = applied
1135 self.applied = applied
1136 self.parse_series()
1136 self.parse_series()
1137 self.series_dirty = 1
1137 self.series_dirty = 1
1138 self.applied_dirty = 1
1138 self.applied_dirty = 1
1139 heads = repo.changelog.heads()
1139 heads = repo.changelog.heads()
1140 if delete:
1140 if delete:
1141 if rev not in heads:
1141 if rev not in heads:
1142 self.ui.warn("save entry has children, leaving it alone\n")
1142 self.ui.warn("save entry has children, leaving it alone\n")
1143 else:
1143 else:
1144 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1144 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1145 pp = repo.dirstate.parents()
1145 pp = repo.dirstate.parents()
1146 if rev in pp:
1146 if rev in pp:
1147 update = True
1147 update = True
1148 else:
1148 else:
1149 update = False
1149 update = False
1150 self.strip(repo, rev, update=update, backup='strip')
1150 self.strip(repo, rev, update=update, backup='strip')
1151 if qpp:
1151 if qpp:
1152 self.ui.warn("saved queue repository parents: %s %s\n" %
1152 self.ui.warn("saved queue repository parents: %s %s\n" %
1153 (hg.short(qpp[0]), hg.short(qpp[1])))
1153 (hg.short(qpp[0]), hg.short(qpp[1])))
1154 if qupdate:
1154 if qupdate:
1155 print "queue directory updating"
1155 print "queue directory updating"
1156 r = self.qrepo()
1156 r = self.qrepo()
1157 if not r:
1157 if not r:
1158 self.ui.warn("Unable to load queue repository\n")
1158 self.ui.warn("Unable to load queue repository\n")
1159 return 1
1159 return 1
1160 hg.clean(r, qpp[0])
1160 hg.clean(r, qpp[0])
1161
1161
1162 def save(self, repo, msg=None):
1162 def save(self, repo, msg=None):
1163 if len(self.applied) == 0:
1163 if len(self.applied) == 0:
1164 self.ui.warn("save: no patches applied, exiting\n")
1164 self.ui.warn("save: no patches applied, exiting\n")
1165 return 1
1165 return 1
1166 if self.issaveline(self.applied[-1]):
1166 if self.issaveline(self.applied[-1]):
1167 self.ui.warn("status is already saved\n")
1167 self.ui.warn("status is already saved\n")
1168 return 1
1168 return 1
1169
1169
1170 ar = [ ':' + x for x in self.full_series ]
1170 ar = [ ':' + x for x in self.full_series ]
1171 if not msg:
1171 if not msg:
1172 msg = "hg patches saved state"
1172 msg = "hg patches saved state"
1173 else:
1173 else:
1174 msg = "hg patches: " + msg.rstrip('\r\n')
1174 msg = "hg patches: " + msg.rstrip('\r\n')
1175 r = self.qrepo()
1175 r = self.qrepo()
1176 if r:
1176 if r:
1177 pp = r.dirstate.parents()
1177 pp = r.dirstate.parents()
1178 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1178 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1179 msg += "\n\nPatch Data:\n"
1179 msg += "\n\nPatch Data:\n"
1180 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1180 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1181 "\n".join(ar) + '\n' or "")
1181 "\n".join(ar) + '\n' or "")
1182 n = repo.commit(None, text, user=None, force=1)
1182 n = repo.commit(None, text, user=None, force=1)
1183 if not n:
1183 if not n:
1184 self.ui.warn("repo commit failed\n")
1184 self.ui.warn("repo commit failed\n")
1185 return 1
1185 return 1
1186 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1186 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1187 self.applied_dirty = 1
1187 self.applied_dirty = 1
1188
1188
1189 def full_series_end(self):
1189 def full_series_end(self):
1190 if len(self.applied) > 0:
1190 if len(self.applied) > 0:
1191 p = self.applied[-1].name
1191 p = self.applied[-1].name
1192 end = self.find_series(p)
1192 end = self.find_series(p)
1193 if end == None:
1193 if end == None:
1194 return len(self.full_series)
1194 return len(self.full_series)
1195 return end + 1
1195 return end + 1
1196 return 0
1196 return 0
1197
1197
1198 def series_end(self, all_patches=False):
1198 def series_end(self, all_patches=False):
1199 end = 0
1199 end = 0
1200 def next(start):
1200 def next(start):
1201 if all_patches:
1201 if all_patches:
1202 return start
1202 return start
1203 i = start
1203 i = start
1204 while i < len(self.series):
1204 while i < len(self.series):
1205 p, reason = self.pushable(i)
1205 p, reason = self.pushable(i)
1206 if p:
1206 if p:
1207 break
1207 break
1208 self.explain_pushable(i)
1208 self.explain_pushable(i)
1209 i += 1
1209 i += 1
1210 return i
1210 return i
1211 if len(self.applied) > 0:
1211 if len(self.applied) > 0:
1212 p = self.applied[-1].name
1212 p = self.applied[-1].name
1213 try:
1213 try:
1214 end = self.series.index(p)
1214 end = self.series.index(p)
1215 except ValueError:
1215 except ValueError:
1216 return 0
1216 return 0
1217 return next(end + 1)
1217 return next(end + 1)
1218 return next(end)
1218 return next(end)
1219
1219
1220 def qapplied(self, repo, patch=None):
1220 def qapplied(self, repo, patch=None):
1221 if patch and patch not in self.series:
1221 if patch and patch not in self.series:
1222 raise util.Abort(_("patch %s is not in series file") % patch)
1222 raise util.Abort(_("patch %s is not in series file") % patch)
1223 if not patch:
1223 if not patch:
1224 end = len(self.applied)
1224 end = len(self.applied)
1225 else:
1225 else:
1226 end = self.series.index(patch) + 1
1226 end = self.series.index(patch) + 1
1227 for x in xrange(end):
1227 for x in xrange(end):
1228 p = self.appliedname(x)
1228 p = self.appliedname(x)
1229 self.ui.write("%s\n" % p)
1229 self.ui.write("%s\n" % p)
1230
1230
1231 def appliedname(self, index):
1231 def appliedname(self, index):
1232 pname = self.applied[index].name
1232 pname = self.applied[index].name
1233 if not self.ui.verbose:
1233 if not self.ui.verbose:
1234 p = pname
1234 p = pname
1235 else:
1235 else:
1236 p = str(self.series.index(pname)) + " " + p
1236 p = str(self.series.index(pname)) + " " + p
1237 return p
1237 return p
1238
1238
1239 def top(self, repo):
1239 def top(self, repo):
1240 if len(self.applied):
1240 if len(self.applied):
1241 p = self.appliedname(-1)
1241 p = self.appliedname(-1)
1242 self.ui.write(p + '\n')
1242 self.ui.write(p + '\n')
1243 else:
1243 else:
1244 self.ui.write("No patches applied\n")
1244 self.ui.write("No patches applied\n")
1245
1245
1246 def next(self, repo):
1246 def next(self, repo):
1247 end = self.series_end()
1247 end = self.series_end()
1248 if end == len(self.series):
1248 if end == len(self.series):
1249 self.ui.write("All patches applied\n")
1249 self.ui.write("All patches applied\n")
1250 else:
1250 else:
1251 p = self.series[end]
1251 p = self.series[end]
1252 if self.ui.verbose:
1252 if self.ui.verbose:
1253 self.ui.write("%d " % self.series.index(p))
1253 self.ui.write("%d " % self.series.index(p))
1254 self.ui.write(p + '\n')
1254 self.ui.write(p + '\n')
1255
1255
1256 def prev(self, repo):
1256 def prev(self, repo):
1257 if len(self.applied) > 1:
1257 if len(self.applied) > 1:
1258 p = self.appliedname(-2)
1258 p = self.appliedname(-2)
1259 self.ui.write(p + '\n')
1259 self.ui.write(p + '\n')
1260 elif len(self.applied) == 1:
1260 elif len(self.applied) == 1:
1261 self.ui.write("Only one patch applied\n")
1261 self.ui.write("Only one patch applied\n")
1262 else:
1262 else:
1263 self.ui.write("No patches applied\n")
1263 self.ui.write("No patches applied\n")
1264
1264
1265 def qimport(self, repo, files, patch=None, existing=None, force=None):
1265 def qimport(self, repo, files, patch=None, existing=None, force=None):
1266 if len(files) > 1 and patch:
1266 if len(files) > 1 and patch:
1267 raise util.Abort(_('option "-n" not valid when importing multiple '
1267 raise util.Abort(_('option "-n" not valid when importing multiple '
1268 'files'))
1268 'files'))
1269 i = 0
1269 i = 0
1270 added = []
1270 added = []
1271 for filename in files:
1271 for filename in files:
1272 if existing:
1272 if existing:
1273 if not patch:
1273 if not patch:
1274 patch = filename
1274 patch = filename
1275 if not os.path.isfile(self.join(patch)):
1275 if not os.path.isfile(self.join(patch)):
1276 raise util.Abort(_("patch %s does not exist") % patch)
1276 raise util.Abort(_("patch %s does not exist") % patch)
1277 else:
1277 else:
1278 try:
1278 try:
1279 text = file(filename).read()
1279 text = file(filename).read()
1280 except IOError:
1280 except IOError:
1281 raise util.Abort(_("unable to read %s") % patch)
1281 raise util.Abort(_("unable to read %s") % patch)
1282 if not patch:
1282 if not patch:
1283 patch = os.path.split(filename)[1]
1283 patch = os.path.split(filename)[1]
1284 if not force and os.path.exists(self.join(patch)):
1284 if not force and os.path.exists(self.join(patch)):
1285 raise util.Abort(_('patch "%s" already exists') % patch)
1285 raise util.Abort(_('patch "%s" already exists') % patch)
1286 patchf = self.opener(patch, "w")
1286 patchf = self.opener(patch, "w")
1287 patchf.write(text)
1287 patchf.write(text)
1288 if patch in self.series:
1288 if patch in self.series:
1289 raise util.Abort(_('patch %s is already in the series file')
1289 raise util.Abort(_('patch %s is already in the series file')
1290 % patch)
1290 % patch)
1291 index = self.full_series_end() + i
1291 index = self.full_series_end() + i
1292 self.full_series[index:index] = [patch]
1292 self.full_series[index:index] = [patch]
1293 self.parse_series()
1293 self.parse_series()
1294 self.ui.warn("adding %s to series file\n" % patch)
1294 self.ui.warn("adding %s to series file\n" % patch)
1295 i += 1
1295 i += 1
1296 added.append(patch)
1296 added.append(patch)
1297 patch = None
1297 patch = None
1298 self.series_dirty = 1
1298 self.series_dirty = 1
1299 qrepo = self.qrepo()
1299 qrepo = self.qrepo()
1300 if qrepo:
1300 if qrepo:
1301 qrepo.add(added)
1301 qrepo.add(added)
1302
1302
1303 def delete(ui, repo, patch, **opts):
1303 def delete(ui, repo, patch, **opts):
1304 """remove a patch from the series file
1304 """remove a patch from the series file
1305
1305
1306 The patch must not be applied.
1306 The patch must not be applied.
1307 With -f, deletes the patch file as well as the series entry."""
1307 With -f, deletes the patch file as well as the series entry."""
1308 q = repo.mq
1308 q = repo.mq
1309 q.delete(repo, patch, force=opts.get('force'))
1309 q.delete(repo, patch, force=opts.get('force'))
1310 q.save_dirty()
1310 q.save_dirty()
1311 return 0
1311 return 0
1312
1312
1313 def applied(ui, repo, patch=None, **opts):
1313 def applied(ui, repo, patch=None, **opts):
1314 """print the patches already applied"""
1314 """print the patches already applied"""
1315 repo.mq.qapplied(repo, patch)
1315 repo.mq.qapplied(repo, patch)
1316 return 0
1316 return 0
1317
1317
1318 def unapplied(ui, repo, patch=None, **opts):
1318 def unapplied(ui, repo, patch=None, **opts):
1319 """print the patches not yet applied"""
1319 """print the patches not yet applied"""
1320 for i, p in repo.mq.unapplied(repo, patch):
1320 for i, p in repo.mq.unapplied(repo, patch):
1321 if ui.verbose:
1321 if ui.verbose:
1322 ui.write("%d " % i)
1322 ui.write("%d " % i)
1323 ui.write("%s\n" % p)
1323 ui.write("%s\n" % p)
1324
1324
1325 def qimport(ui, repo, *filename, **opts):
1325 def qimport(ui, repo, *filename, **opts):
1326 """import a patch"""
1326 """import a patch"""
1327 q = repo.mq
1327 q = repo.mq
1328 q.qimport(repo, filename, patch=opts['name'],
1328 q.qimport(repo, filename, patch=opts['name'],
1329 existing=opts['existing'], force=opts['force'])
1329 existing=opts['existing'], force=opts['force'])
1330 q.save_dirty()
1330 q.save_dirty()
1331 return 0
1331 return 0
1332
1332
1333 def init(ui, repo, **opts):
1333 def init(ui, repo, **opts):
1334 """init a new queue repository
1334 """init a new queue repository
1335
1335
1336 The queue repository is unversioned by default. If -c is
1336 The queue repository is unversioned by default. If -c is
1337 specified, qinit will create a separate nested repository
1337 specified, qinit will create a separate nested repository
1338 for patches. Use qcommit to commit changes to this queue
1338 for patches. Use qcommit to commit changes to this queue
1339 repository."""
1339 repository."""
1340 q = repo.mq
1340 q = repo.mq
1341 r = q.init(repo, create=opts['create_repo'])
1341 r = q.init(repo, create=opts['create_repo'])
1342 q.save_dirty()
1342 q.save_dirty()
1343 if r:
1343 if r:
1344 fp = r.wopener('.hgignore', 'w')
1344 fp = r.wopener('.hgignore', 'w')
1345 print >> fp, 'syntax: glob'
1345 print >> fp, 'syntax: glob'
1346 print >> fp, 'status'
1346 print >> fp, 'status'
1347 fp.close()
1347 fp.close()
1348 r.wopener('series', 'w').close()
1348 r.wopener('series', 'w').close()
1349 r.add(['.hgignore', 'series'])
1349 r.add(['.hgignore', 'series'])
1350 return 0
1350 return 0
1351
1351
1352 def clone(ui, source, dest=None, **opts):
1352 def clone(ui, source, dest=None, **opts):
1353 '''clone main and patch repository at same time
1353 '''clone main and patch repository at same time
1354
1354
1355 If source is local, destination will have no patches applied. If
1355 If source is local, destination will have no patches applied. If
1356 source is remote, this command can not check if patches are
1356 source is remote, this command can not check if patches are
1357 applied in source, so cannot guarantee that patches are not
1357 applied in source, so cannot guarantee that patches are not
1358 applied in destination. If you clone remote repository, be sure
1358 applied in destination. If you clone remote repository, be sure
1359 before that it has no patches applied.
1359 before that it has no patches applied.
1360
1360
1361 Source patch repository is looked for in <src>/.hg/patches by
1361 Source patch repository is looked for in <src>/.hg/patches by
1362 default. Use -p <url> to change.
1362 default. Use -p <url> to change.
1363 '''
1363 '''
1364 commands.setremoteconfig(ui, opts)
1364 commands.setremoteconfig(ui, opts)
1365 if dest is None:
1365 if dest is None:
1366 dest = hg.defaultdest(source)
1366 dest = hg.defaultdest(source)
1367 sr = hg.repository(ui, ui.expandpath(source))
1367 sr = hg.repository(ui, ui.expandpath(source))
1368 qbase, destrev = None, None
1368 qbase, destrev = None, None
1369 if sr.local():
1369 if sr.local():
1370 reposetup(ui, sr)
1370 reposetup(ui, sr)
1371 if sr.mq.applied:
1371 if sr.mq.applied:
1372 qbase = revlog.bin(sr.mq.applied[0].rev)
1372 qbase = revlog.bin(sr.mq.applied[0].rev)
1373 if not hg.islocal(dest):
1373 if not hg.islocal(dest):
1374 destrev = sr.parents(qbase)[0]
1374 destrev = sr.parents(qbase)[0]
1375 ui.note(_('cloning main repo\n'))
1375 ui.note(_('cloning main repo\n'))
1376 sr, dr = hg.clone(ui, sr, dest,
1376 sr, dr = hg.clone(ui, sr, dest,
1377 pull=opts['pull'],
1377 pull=opts['pull'],
1378 rev=destrev,
1378 rev=destrev,
1379 update=False,
1379 update=False,
1380 stream=opts['uncompressed'])
1380 stream=opts['uncompressed'])
1381 ui.note(_('cloning patch repo\n'))
1381 ui.note(_('cloning patch repo\n'))
1382 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1382 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1383 dr.url() + '/.hg/patches',
1383 dr.url() + '/.hg/patches',
1384 pull=opts['pull'],
1384 pull=opts['pull'],
1385 update=not opts['noupdate'],
1385 update=not opts['noupdate'],
1386 stream=opts['uncompressed'])
1386 stream=opts['uncompressed'])
1387 if dr.local():
1387 if dr.local():
1388 if qbase:
1388 if qbase:
1389 ui.note(_('stripping applied patches from destination repo\n'))
1389 ui.note(_('stripping applied patches from destination repo\n'))
1390 reposetup(ui, dr)
1390 reposetup(ui, dr)
1391 dr.mq.strip(dr, qbase, update=False, backup=None)
1391 dr.mq.strip(dr, qbase, update=False, backup=None)
1392 if not opts['noupdate']:
1392 if not opts['noupdate']:
1393 ui.note(_('updating destination repo\n'))
1393 ui.note(_('updating destination repo\n'))
1394 hg.update(dr, dr.changelog.tip())
1394 hg.update(dr, dr.changelog.tip())
1395
1395
1396 def commit(ui, repo, *pats, **opts):
1396 def commit(ui, repo, *pats, **opts):
1397 """commit changes in the queue repository"""
1397 """commit changes in the queue repository"""
1398 q = repo.mq
1398 q = repo.mq
1399 r = q.qrepo()
1399 r = q.qrepo()
1400 if not r: raise util.Abort('no queue repository')
1400 if not r: raise util.Abort('no queue repository')
1401 commands.commit(r.ui, r, *pats, **opts)
1401 commands.commit(r.ui, r, *pats, **opts)
1402
1402
1403 def series(ui, repo, **opts):
1403 def series(ui, repo, **opts):
1404 """print the entire series file"""
1404 """print the entire series file"""
1405 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1405 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1406 return 0
1406 return 0
1407
1407
1408 def top(ui, repo, **opts):
1408 def top(ui, repo, **opts):
1409 """print the name of the current patch"""
1409 """print the name of the current patch"""
1410 repo.mq.top(repo)
1410 repo.mq.top(repo)
1411 return 0
1411 return 0
1412
1412
1413 def next(ui, repo, **opts):
1413 def next(ui, repo, **opts):
1414 """print the name of the next patch"""
1414 """print the name of the next patch"""
1415 repo.mq.next(repo)
1415 repo.mq.next(repo)
1416 return 0
1416 return 0
1417
1417
1418 def prev(ui, repo, **opts):
1418 def prev(ui, repo, **opts):
1419 """print the name of the previous patch"""
1419 """print the name of the previous patch"""
1420 repo.mq.prev(repo)
1420 repo.mq.prev(repo)
1421 return 0
1421 return 0
1422
1422
1423 def new(ui, repo, patch, **opts):
1423 def new(ui, repo, patch, **opts):
1424 """create a new patch
1424 """create a new patch
1425
1425
1426 qnew creates a new patch on top of the currently-applied patch
1426 qnew creates a new patch on top of the currently-applied patch
1427 (if any). It will refuse to run if there are any outstanding
1427 (if any). It will refuse to run if there are any outstanding
1428 changes unless -f is specified, in which case the patch will
1428 changes unless -f is specified, in which case the patch will
1429 be initialised with them.
1429 be initialised with them.
1430
1430
1431 -m or -l set the patch header as well as the commit message.
1431 -m or -l set the patch header as well as the commit message.
1432 If neither is specified, the patch header is empty and the
1432 If neither is specified, the patch header is empty and the
1433 commit message is 'New patch: PATCH'"""
1433 commit message is 'New patch: PATCH'"""
1434 q = repo.mq
1434 q = repo.mq
1435 message = commands.logmessage(opts)
1435 message = commands.logmessage(opts)
1436 q.new(repo, patch, msg=message, force=opts['force'])
1436 q.new(repo, patch, msg=message, force=opts['force'])
1437 q.save_dirty()
1437 q.save_dirty()
1438 return 0
1438 return 0
1439
1439
1440 def refresh(ui, repo, **opts):
1440 def refresh(ui, repo, **opts):
1441 """update the current patch"""
1441 """update the current patch"""
1442 q = repo.mq
1442 q = repo.mq
1443 message = commands.logmessage(opts)
1443 message = commands.logmessage(opts)
1444 if opts['edit']:
1444 if opts['edit']:
1445 if message:
1445 if message:
1446 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1446 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1447 patch = q.applied[-1].name
1447 patch = q.applied[-1].name
1448 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1448 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1449 message = ui.edit('\n'.join(message), user or ui.username())
1449 message = ui.edit('\n'.join(message), user or ui.username())
1450 q.refresh(repo, msg=message, short=opts['short'])
1450 q.refresh(repo, msg=message, short=opts['short'])
1451 q.save_dirty()
1451 q.save_dirty()
1452 return 0
1452 return 0
1453
1453
1454 def diff(ui, repo, *files, **opts):
1454 def diff(ui, repo, *files, **opts):
1455 """diff of the current patch"""
1455 """diff of the current patch"""
1456 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1456 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1457 repo.mq.diff(repo, list(files))
1457 repo.mq.diff(repo, list(files))
1458 return 0
1458 return 0
1459
1459
1460 def fold(ui, repo, *files, **opts):
1460 def fold(ui, repo, *files, **opts):
1461 """fold the named patches into the current patch
1461 """fold the named patches into the current patch
1462
1462
1463 Patches must not yet be applied. Each patch will be successively
1463 Patches must not yet be applied. Each patch will be successively
1464 applied to the current patch in the order given. If all the
1464 applied to the current patch in the order given. If all the
1465 patches apply successfully, the current patch will be refreshed
1465 patches apply successfully, the current patch will be refreshed
1466 with the new cumulative patch, and the folded patches will
1466 with the new cumulative patch, and the folded patches will
1467 be deleted. With -f/--force, the folded patch files will
1467 be deleted. With -f/--force, the folded patch files will
1468 be removed afterwards.
1468 be removed afterwards.
1469
1469
1470 The header for each folded patch will be concatenated with
1470 The header for each folded patch will be concatenated with
1471 the current patch header, separated by a line of '* * *'."""
1471 the current patch header, separated by a line of '* * *'."""
1472
1472
1473 q = repo.mq
1473 q = repo.mq
1474
1474
1475 if not files:
1475 if not files:
1476 raise util.Abort(_('qfold requires at least one patch name'))
1476 raise util.Abort(_('qfold requires at least one patch name'))
1477 if not q.check_toppatch(repo):
1477 if not q.check_toppatch(repo):
1478 raise util.Abort(_('No patches applied\n'))
1478 raise util.Abort(_('No patches applied\n'))
1479
1479
1480 message = commands.logmessage(opts)
1480 message = commands.logmessage(opts)
1481 if opts['edit']:
1481 if opts['edit']:
1482 if message:
1482 if message:
1483 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1483 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1484
1484
1485 parent = q.lookup('qtip')
1485 parent = q.lookup('qtip')
1486 patches = []
1486 patches = []
1487 messages = []
1487 messages = []
1488 for f in files:
1488 for f in files:
1489 patch = q.lookup(f)
1489 patch = q.lookup(f)
1490 if patch in patches or patch == parent:
1490 if patch in patches or patch == parent:
1491 ui.warn(_('Skipping already folded patch %s') % patch)
1491 ui.warn(_('Skipping already folded patch %s') % patch)
1492 if q.isapplied(patch):
1492 if q.isapplied(patch):
1493 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1493 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1494 patches.append(patch)
1494 patches.append(patch)
1495
1495
1496 for patch in patches:
1496 for patch in patches:
1497 if not message:
1497 if not message:
1498 messages.append(q.readheaders(patch)[0])
1498 messages.append(q.readheaders(patch)[0])
1499 pf = q.join(patch)
1499 pf = q.join(patch)
1500 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1500 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1501 if not patchsuccess:
1501 if not patchsuccess:
1502 raise util.Abort(_('Error folding patch %s') % patch)
1502 raise util.Abort(_('Error folding patch %s') % patch)
1503
1503
1504 if not message:
1504 if not message:
1505 message, comments, user = q.readheaders(parent)[0:3]
1505 message, comments, user = q.readheaders(parent)[0:3]
1506 for msg in messages:
1506 for msg in messages:
1507 message.append('* * *')
1507 message.append('* * *')
1508 message.extend(msg)
1508 message.extend(msg)
1509 message = '\n'.join(message)
1509 message = '\n'.join(message)
1510
1510
1511 if opts['edit']:
1511 if opts['edit']:
1512 message = ui.edit(message, user or ui.username())
1512 message = ui.edit(message, user or ui.username())
1513
1513
1514 q.refresh(repo, msg=message)
1514 q.refresh(repo, msg=message)
1515
1515
1516 for patch in patches:
1516 for patch in patches:
1517 q.delete(repo, patch, force=opts['force'])
1517 q.delete(repo, patch, force=opts['force'])
1518
1518
1519 q.save_dirty()
1519 q.save_dirty()
1520
1520
1521 def guard(ui, repo, *args, **opts):
1521 def guard(ui, repo, *args, **opts):
1522 '''set or print guards for a patch
1522 '''set or print guards for a patch
1523
1523
1524 guards control whether a patch can be pushed. a patch with no
1524 guards control whether a patch can be pushed. a patch with no
1525 guards is aways pushed. a patch with posative guard ("+foo") is
1525 guards is aways pushed. a patch with posative guard ("+foo") is
1526 pushed only if qselect command enables guard "foo". a patch with
1526 pushed only if qselect command enables guard "foo". a patch with
1527 nagative guard ("-foo") is never pushed if qselect command enables
1527 nagative guard ("-foo") is never pushed if qselect command enables
1528 guard "foo".
1528 guard "foo".
1529
1529
1530 with no arguments, default is to print current active guards.
1530 with no arguments, default is to print current active guards.
1531 with arguments, set active guards for patch.
1531 with arguments, set active guards for patch.
1532
1532
1533 to set nagative guard "-foo" on topmost patch ("--" is needed so
1533 to set nagative guard "-foo" on topmost patch ("--" is needed so
1534 hg will not interpret "-foo" as argument):
1534 hg will not interpret "-foo" as argument):
1535 hg qguard -- -foo
1535 hg qguard -- -foo
1536
1536
1537 to set guards on other patch:
1537 to set guards on other patch:
1538 hg qguard other.patch +2.6.17 -stable
1538 hg qguard other.patch +2.6.17 -stable
1539 '''
1539 '''
1540 def status(idx):
1540 def status(idx):
1541 guards = q.series_guards[idx] or ['unguarded']
1541 guards = q.series_guards[idx] or ['unguarded']
1542 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1542 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1543 q = repo.mq
1543 q = repo.mq
1544 patch = None
1544 patch = None
1545 args = list(args)
1545 args = list(args)
1546 if opts['list']:
1546 if opts['list']:
1547 if args or opts['none']:
1547 if args or opts['none']:
1548 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1548 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1549 for i in xrange(len(q.series)):
1549 for i in xrange(len(q.series)):
1550 status(i)
1550 status(i)
1551 return
1551 return
1552 if not args or args[0][0:1] in '-+':
1552 if not args or args[0][0:1] in '-+':
1553 if not q.applied:
1553 if not q.applied:
1554 raise util.Abort(_('no patches applied'))
1554 raise util.Abort(_('no patches applied'))
1555 patch = q.applied[-1].name
1555 patch = q.applied[-1].name
1556 if patch is None and args[0][0:1] not in '-+':
1556 if patch is None and args[0][0:1] not in '-+':
1557 patch = args.pop(0)
1557 patch = args.pop(0)
1558 if patch is None:
1558 if patch is None:
1559 raise util.Abort(_('no patch to work with'))
1559 raise util.Abort(_('no patch to work with'))
1560 if args or opts['none']:
1560 if args or opts['none']:
1561 q.set_guards(q.find_series(patch), args)
1561 q.set_guards(q.find_series(patch), args)
1562 q.save_dirty()
1562 q.save_dirty()
1563 else:
1563 else:
1564 status(q.series.index(q.lookup(patch)))
1564 status(q.series.index(q.lookup(patch)))
1565
1565
1566 def header(ui, repo, patch=None):
1566 def header(ui, repo, patch=None):
1567 """Print the header of the topmost or specified patch"""
1567 """Print the header of the topmost or specified patch"""
1568 q = repo.mq
1568 q = repo.mq
1569
1569
1570 if patch:
1570 if patch:
1571 patch = q.lookup(patch)
1571 patch = q.lookup(patch)
1572 else:
1572 else:
1573 if not q.applied:
1573 if not q.applied:
1574 ui.write('No patches applied\n')
1574 ui.write('No patches applied\n')
1575 return
1575 return
1576 patch = q.lookup('qtip')
1576 patch = q.lookup('qtip')
1577 message = repo.mq.readheaders(patch)[0]
1577 message = repo.mq.readheaders(patch)[0]
1578
1578
1579 ui.write('\n'.join(message) + '\n')
1579 ui.write('\n'.join(message) + '\n')
1580
1580
1581 def lastsavename(path):
1581 def lastsavename(path):
1582 (directory, base) = os.path.split(path)
1582 (directory, base) = os.path.split(path)
1583 names = os.listdir(directory)
1583 names = os.listdir(directory)
1584 namere = re.compile("%s.([0-9]+)" % base)
1584 namere = re.compile("%s.([0-9]+)" % base)
1585 maxindex = None
1585 maxindex = None
1586 maxname = None
1586 maxname = None
1587 for f in names:
1587 for f in names:
1588 m = namere.match(f)
1588 m = namere.match(f)
1589 if m:
1589 if m:
1590 index = int(m.group(1))
1590 index = int(m.group(1))
1591 if maxindex == None or index > maxindex:
1591 if maxindex == None or index > maxindex:
1592 maxindex = index
1592 maxindex = index
1593 maxname = f
1593 maxname = f
1594 if maxname:
1594 if maxname:
1595 return (os.path.join(directory, maxname), maxindex)
1595 return (os.path.join(directory, maxname), maxindex)
1596 return (None, None)
1596 return (None, None)
1597
1597
1598 def savename(path):
1598 def savename(path):
1599 (last, index) = lastsavename(path)
1599 (last, index) = lastsavename(path)
1600 if last is None:
1600 if last is None:
1601 index = 0
1601 index = 0
1602 newpath = path + ".%d" % (index + 1)
1602 newpath = path + ".%d" % (index + 1)
1603 return newpath
1603 return newpath
1604
1604
1605 def push(ui, repo, patch=None, **opts):
1605 def push(ui, repo, patch=None, **opts):
1606 """push the next patch onto the stack"""
1606 """push the next patch onto the stack"""
1607 q = repo.mq
1607 q = repo.mq
1608 mergeq = None
1608 mergeq = None
1609
1609
1610 if opts['all']:
1610 if opts['all']:
1611 patch = q.series[-1]
1611 patch = q.series[-1]
1612 if opts['merge']:
1612 if opts['merge']:
1613 if opts['name']:
1613 if opts['name']:
1614 newpath = opts['name']
1614 newpath = opts['name']
1615 else:
1615 else:
1616 newpath, i = lastsavename(q.path)
1616 newpath, i = lastsavename(q.path)
1617 if not newpath:
1617 if not newpath:
1618 ui.warn("no saved queues found, please use -n\n")
1618 ui.warn("no saved queues found, please use -n\n")
1619 return 1
1619 return 1
1620 mergeq = queue(ui, repo.join(""), newpath)
1620 mergeq = queue(ui, repo.join(""), newpath)
1621 ui.warn("merging with queue at: %s\n" % mergeq.path)
1621 ui.warn("merging with queue at: %s\n" % mergeq.path)
1622 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1622 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1623 mergeq=mergeq)
1623 mergeq=mergeq)
1624 q.save_dirty()
1624 q.save_dirty()
1625 return ret
1625 return ret
1626
1626
1627 def pop(ui, repo, patch=None, **opts):
1627 def pop(ui, repo, patch=None, **opts):
1628 """pop the current patch off the stack"""
1628 """pop the current patch off the stack"""
1629 localupdate = True
1629 localupdate = True
1630 if opts['name']:
1630 if opts['name']:
1631 q = queue(ui, repo.join(""), repo.join(opts['name']))
1631 q = queue(ui, repo.join(""), repo.join(opts['name']))
1632 ui.warn('using patch queue: %s\n' % q.path)
1632 ui.warn('using patch queue: %s\n' % q.path)
1633 localupdate = False
1633 localupdate = False
1634 else:
1634 else:
1635 q = repo.mq
1635 q = repo.mq
1636 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1636 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1637 q.save_dirty()
1637 q.save_dirty()
1638 return 0
1638 return 0
1639
1639
1640 def rename(ui, repo, patch, name=None, **opts):
1640 def rename(ui, repo, patch, name=None, **opts):
1641 """rename a patch
1641 """rename a patch
1642
1642
1643 With one argument, renames the current patch to PATCH1.
1643 With one argument, renames the current patch to PATCH1.
1644 With two arguments, renames PATCH1 to PATCH2."""
1644 With two arguments, renames PATCH1 to PATCH2."""
1645
1645
1646 q = repo.mq
1646 q = repo.mq
1647
1647
1648 if not name:
1648 if not name:
1649 name = patch
1649 name = patch
1650 patch = None
1650 patch = None
1651
1651
1652 if name in q.series:
1652 if name in q.series:
1653 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1653 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1654
1654
1655 absdest = q.join(name)
1655 absdest = q.join(name)
1656 if os.path.exists(absdest):
1656 if os.path.exists(absdest):
1657 raise util.Abort(_('%s already exists') % absdest)
1657 raise util.Abort(_('%s already exists') % absdest)
1658
1658
1659 if patch:
1659 if patch:
1660 patch = q.lookup(patch)
1660 patch = q.lookup(patch)
1661 else:
1661 else:
1662 if not q.applied:
1662 if not q.applied:
1663 ui.write(_('No patches applied\n'))
1663 ui.write(_('No patches applied\n'))
1664 return
1664 return
1665 patch = q.lookup('qtip')
1665 patch = q.lookup('qtip')
1666
1666
1667 if ui.verbose:
1667 if ui.verbose:
1668 ui.write('Renaming %s to %s\n' % (patch, name))
1668 ui.write('Renaming %s to %s\n' % (patch, name))
1669 i = q.find_series(patch)
1669 i = q.find_series(patch)
1670 q.full_series[i] = name
1670 q.full_series[i] = name
1671 q.parse_series()
1671 q.parse_series()
1672 q.series_dirty = 1
1672 q.series_dirty = 1
1673
1673
1674 info = q.isapplied(patch)
1674 info = q.isapplied(patch)
1675 if info:
1675 if info:
1676 q.applied[info[0]] = statusentry(info[1], name)
1676 q.applied[info[0]] = statusentry(info[1], name)
1677 q.applied_dirty = 1
1677 q.applied_dirty = 1
1678
1678
1679 util.rename(q.join(patch), absdest)
1679 util.rename(q.join(patch), absdest)
1680 r = q.qrepo()
1680 r = q.qrepo()
1681 if r:
1681 if r:
1682 wlock = r.wlock()
1682 wlock = r.wlock()
1683 if r.dirstate.state(name) == 'r':
1683 if r.dirstate.state(name) == 'r':
1684 r.undelete([name], wlock)
1684 r.undelete([name], wlock)
1685 r.copy(patch, name, wlock)
1685 r.copy(patch, name, wlock)
1686 r.remove([patch], False, wlock)
1686 r.remove([patch], False, wlock)
1687
1687
1688 q.save_dirty()
1688 q.save_dirty()
1689
1689
1690 def restore(ui, repo, rev, **opts):
1690 def restore(ui, repo, rev, **opts):
1691 """restore the queue state saved by a rev"""
1691 """restore the queue state saved by a rev"""
1692 rev = repo.lookup(rev)
1692 rev = repo.lookup(rev)
1693 q = repo.mq
1693 q = repo.mq
1694 q.restore(repo, rev, delete=opts['delete'],
1694 q.restore(repo, rev, delete=opts['delete'],
1695 qupdate=opts['update'])
1695 qupdate=opts['update'])
1696 q.save_dirty()
1696 q.save_dirty()
1697 return 0
1697 return 0
1698
1698
1699 def save(ui, repo, **opts):
1699 def save(ui, repo, **opts):
1700 """save current queue state"""
1700 """save current queue state"""
1701 q = repo.mq
1701 q = repo.mq
1702 message = commands.logmessage(opts)
1702 message = commands.logmessage(opts)
1703 ret = q.save(repo, msg=message)
1703 ret = q.save(repo, msg=message)
1704 if ret:
1704 if ret:
1705 return ret
1705 return ret
1706 q.save_dirty()
1706 q.save_dirty()
1707 if opts['copy']:
1707 if opts['copy']:
1708 path = q.path
1708 path = q.path
1709 if opts['name']:
1709 if opts['name']:
1710 newpath = os.path.join(q.basepath, opts['name'])
1710 newpath = os.path.join(q.basepath, opts['name'])
1711 if os.path.exists(newpath):
1711 if os.path.exists(newpath):
1712 if not os.path.isdir(newpath):
1712 if not os.path.isdir(newpath):
1713 raise util.Abort(_('destination %s exists and is not '
1713 raise util.Abort(_('destination %s exists and is not '
1714 'a directory') % newpath)
1714 'a directory') % newpath)
1715 if not opts['force']:
1715 if not opts['force']:
1716 raise util.Abort(_('destination %s exists, '
1716 raise util.Abort(_('destination %s exists, '
1717 'use -f to force') % newpath)
1717 'use -f to force') % newpath)
1718 else:
1718 else:
1719 newpath = savename(path)
1719 newpath = savename(path)
1720 ui.warn("copy %s to %s\n" % (path, newpath))
1720 ui.warn("copy %s to %s\n" % (path, newpath))
1721 util.copyfiles(path, newpath)
1721 util.copyfiles(path, newpath)
1722 if opts['empty']:
1722 if opts['empty']:
1723 try:
1723 try:
1724 os.unlink(q.join(q.status_path))
1724 os.unlink(q.join(q.status_path))
1725 except:
1725 except:
1726 pass
1726 pass
1727 return 0
1727 return 0
1728
1728
1729 def strip(ui, repo, rev, **opts):
1729 def strip(ui, repo, rev, **opts):
1730 """strip a revision and all later revs on the same branch"""
1730 """strip a revision and all later revs on the same branch"""
1731 rev = repo.lookup(rev)
1731 rev = repo.lookup(rev)
1732 backup = 'all'
1732 backup = 'all'
1733 if opts['backup']:
1733 if opts['backup']:
1734 backup = 'strip'
1734 backup = 'strip'
1735 elif opts['nobackup']:
1735 elif opts['nobackup']:
1736 backup = 'none'
1736 backup = 'none'
1737 repo.mq.strip(repo, rev, backup=backup)
1737 repo.mq.strip(repo, rev, backup=backup)
1738 return 0
1738 return 0
1739
1739
1740 def select(ui, repo, *args, **opts):
1740 def select(ui, repo, *args, **opts):
1741 '''set or print guarded patches to push
1741 '''set or print guarded patches to push
1742
1742
1743 use qguard command to set or print guards on patch. then use
1743 use qguard command to set or print guards on patch. then use
1744 qselect to tell mq which guards to use. example:
1744 qselect to tell mq which guards to use. example:
1745
1745
1746 qguard foo.patch -stable (nagative guard)
1746 qguard foo.patch -stable (nagative guard)
1747 qguard bar.patch +stable (posative guard)
1747 qguard bar.patch +stable (posative guard)
1748 qselect stable
1748 qselect stable
1749
1749
1750 this sets "stable" guard. mq will skip foo.patch (because it has
1750 this sets "stable" guard. mq will skip foo.patch (because it has
1751 nagative match) but push bar.patch (because it has posative
1751 nagative match) but push bar.patch (because it has posative
1752 match). patch is pushed if any posative guards match and no
1752 match). patch is pushed if any posative guards match and no
1753 nagative guards match.
1753 nagative guards match.
1754
1754
1755 with no arguments, default is to print current active guards.
1755 with no arguments, default is to print current active guards.
1756 with arguments, set active guards as given.
1756 with arguments, set active guards as given.
1757
1757
1758 use -n/--none to deactivate guards (no other arguments needed).
1758 use -n/--none to deactivate guards (no other arguments needed).
1759 when no guards active, patches with posative guards are skipped,
1759 when no guards active, patches with posative guards are skipped,
1760 patches with nagative guards are pushed.
1760 patches with nagative guards are pushed.
1761
1761
1762 qselect can change guards of applied patches. it does not pop
1762 qselect can change guards of applied patches. it does not pop
1763 guarded patches by default. use --pop to pop back to last applied
1763 guarded patches by default. use --pop to pop back to last applied
1764 patch that is not guarded. use --reapply (implies --pop) to push
1764 patch that is not guarded. use --reapply (implies --pop) to push
1765 back to current patch afterwards, but skip guarded patches.
1765 back to current patch afterwards, but skip guarded patches.
1766
1766
1767 use -s/--series to print list of all guards in series file (no
1767 use -s/--series to print list of all guards in series file (no
1768 other arguments needed). use -v for more information.'''
1768 other arguments needed). use -v for more information.'''
1769
1769
1770 q = repo.mq
1770 q = repo.mq
1771 guards = q.active()
1771 guards = q.active()
1772 if args or opts['none']:
1772 if args or opts['none']:
1773 old_unapplied = q.unapplied(repo)
1773 old_unapplied = q.unapplied(repo)
1774 old_guarded = [i for i in xrange(len(q.applied)) if
1774 old_guarded = [i for i in xrange(len(q.applied)) if
1775 not q.pushable(i)[0]]
1775 not q.pushable(i)[0]]
1776 q.set_active(args)
1776 q.set_active(args)
1777 q.save_dirty()
1777 q.save_dirty()
1778 if not args:
1778 if not args:
1779 ui.status(_('guards deactivated\n'))
1779 ui.status(_('guards deactivated\n'))
1780 if not opts['pop'] and not opts['reapply']:
1780 if not opts['pop'] and not opts['reapply']:
1781 unapplied = q.unapplied(repo)
1781 unapplied = q.unapplied(repo)
1782 guarded = [i for i in xrange(len(q.applied))
1782 guarded = [i for i in xrange(len(q.applied))
1783 if not q.pushable(i)[0]]
1783 if not q.pushable(i)[0]]
1784 if len(unapplied) != len(old_unapplied):
1784 if len(unapplied) != len(old_unapplied):
1785 ui.status(_('number of unguarded, unapplied patches has '
1785 ui.status(_('number of unguarded, unapplied patches has '
1786 'changed from %d to %d\n') %
1786 'changed from %d to %d\n') %
1787 (len(old_unapplied), len(unapplied)))
1787 (len(old_unapplied), len(unapplied)))
1788 if len(guarded) != len(old_guarded):
1788 if len(guarded) != len(old_guarded):
1789 ui.status(_('number of guarded, applied patches has changed '
1789 ui.status(_('number of guarded, applied patches has changed '
1790 'from %d to %d\n') %
1790 'from %d to %d\n') %
1791 (len(old_guarded), len(guarded)))
1791 (len(old_guarded), len(guarded)))
1792 elif opts['series']:
1792 elif opts['series']:
1793 guards = {}
1793 guards = {}
1794 noguards = 0
1794 noguards = 0
1795 for gs in q.series_guards:
1795 for gs in q.series_guards:
1796 if not gs:
1796 if not gs:
1797 noguards += 1
1797 noguards += 1
1798 for g in gs:
1798 for g in gs:
1799 guards.setdefault(g, 0)
1799 guards.setdefault(g, 0)
1800 guards[g] += 1
1800 guards[g] += 1
1801 if ui.verbose:
1801 if ui.verbose:
1802 guards['NONE'] = noguards
1802 guards['NONE'] = noguards
1803 guards = guards.items()
1803 guards = guards.items()
1804 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1804 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1805 if guards:
1805 if guards:
1806 ui.note(_('guards in series file:\n'))
1806 ui.note(_('guards in series file:\n'))
1807 for guard, count in guards:
1807 for guard, count in guards:
1808 ui.note('%2d ' % count)
1808 ui.note('%2d ' % count)
1809 ui.write(guard, '\n')
1809 ui.write(guard, '\n')
1810 else:
1810 else:
1811 ui.note(_('no guards in series file\n'))
1811 ui.note(_('no guards in series file\n'))
1812 else:
1812 else:
1813 if guards:
1813 if guards:
1814 ui.note(_('active guards:\n'))
1814 ui.note(_('active guards:\n'))
1815 for g in guards:
1815 for g in guards:
1816 ui.write(g, '\n')
1816 ui.write(g, '\n')
1817 else:
1817 else:
1818 ui.write(_('no active guards\n'))
1818 ui.write(_('no active guards\n'))
1819 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1819 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1820 popped = False
1820 popped = False
1821 if opts['pop'] or opts['reapply']:
1821 if opts['pop'] or opts['reapply']:
1822 for i in xrange(len(q.applied)):
1822 for i in xrange(len(q.applied)):
1823 pushable, reason = q.pushable(i)
1823 pushable, reason = q.pushable(i)
1824 if not pushable:
1824 if not pushable:
1825 ui.status(_('popping guarded patches\n'))
1825 ui.status(_('popping guarded patches\n'))
1826 popped = True
1826 popped = True
1827 if i == 0:
1827 if i == 0:
1828 q.pop(repo, all=True)
1828 q.pop(repo, all=True)
1829 else:
1829 else:
1830 q.pop(repo, i-1)
1830 q.pop(repo, i-1)
1831 break
1831 break
1832 if popped:
1832 if popped:
1833 try:
1833 try:
1834 if reapply:
1834 if reapply:
1835 ui.status(_('reapplying unguarded patches\n'))
1835 ui.status(_('reapplying unguarded patches\n'))
1836 q.push(repo, reapply)
1836 q.push(repo, reapply)
1837 finally:
1837 finally:
1838 q.save_dirty()
1838 q.save_dirty()
1839
1839
1840 def reposetup(ui, repo):
1840 def reposetup(ui, repo):
1841 class mqrepo(repo.__class__):
1841 class mqrepo(repo.__class__):
1842 def abort_if_wdir_patched(self, errmsg, force=False):
1842 def abort_if_wdir_patched(self, errmsg, force=False):
1843 if self.mq.applied and not force:
1843 if self.mq.applied and not force:
1844 parent = revlog.hex(self.dirstate.parents()[0])
1844 parent = revlog.hex(self.dirstate.parents()[0])
1845 if parent in [s.rev for s in self.mq.applied]:
1845 if parent in [s.rev for s in self.mq.applied]:
1846 raise util.Abort(errmsg)
1846 raise util.Abort(errmsg)
1847
1847
1848 def commit(self, *args, **opts):
1848 def commit(self, *args, **opts):
1849 if len(args) >= 6:
1849 if len(args) >= 6:
1850 force = args[5]
1850 force = args[5]
1851 else:
1851 else:
1852 force = opts.get('force')
1852 force = opts.get('force')
1853 self.abort_if_wdir_patched(
1853 self.abort_if_wdir_patched(
1854 _('cannot commit over an applied mq patch'),
1854 _('cannot commit over an applied mq patch'),
1855 force)
1855 force)
1856
1856
1857 return super(mqrepo, self).commit(*args, **opts)
1857 return super(mqrepo, self).commit(*args, **opts)
1858
1858
1859 def push(self, remote, force=False, revs=None):
1859 def push(self, remote, force=False, revs=None):
1860 if self.mq.applied and not force:
1860 if self.mq.applied and not force:
1861 raise util.Abort(_('source has mq patches applied'))
1861 raise util.Abort(_('source has mq patches applied'))
1862 return super(mqrepo, self).push(remote, force, revs)
1862 return super(mqrepo, self).push(remote, force, revs)
1863
1863
1864 def tags(self):
1864 def tags(self):
1865 if self.tagscache:
1865 if self.tagscache:
1866 return self.tagscache
1866 return self.tagscache
1867
1867
1868 tagscache = super(mqrepo, self).tags()
1868 tagscache = super(mqrepo, self).tags()
1869
1869
1870 q = self.mq
1870 q = self.mq
1871 if not q.applied:
1871 if not q.applied:
1872 return tagscache
1872 return tagscache
1873
1873
1874 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1874 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1875 mqtags.append((mqtags[-1][0], 'qtip'))
1875 mqtags.append((mqtags[-1][0], 'qtip'))
1876 mqtags.append((mqtags[0][0], 'qbase'))
1876 mqtags.append((mqtags[0][0], 'qbase'))
1877 for patch in mqtags:
1877 for patch in mqtags:
1878 if patch[1] in tagscache:
1878 if patch[1] in tagscache:
1879 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1879 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1880 else:
1880 else:
1881 tagscache[patch[1]] = revlog.bin(patch[0])
1881 tagscache[patch[1]] = revlog.bin(patch[0])
1882
1882
1883 return tagscache
1883 return tagscache
1884
1884
1885 if repo.local():
1885 if repo.local():
1886 repo.__class__ = mqrepo
1886 repo.__class__ = mqrepo
1887 repo.mq = queue(ui, repo.join(""))
1887 repo.mq = queue(ui, repo.join(""))
1888
1888
1889 cmdtable = {
1889 cmdtable = {
1890 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1890 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1891 "qclone": (clone,
1891 "qclone": (clone,
1892 [('', 'pull', None, _('use pull protocol to copy metadata')),
1892 [('', 'pull', None, _('use pull protocol to copy metadata')),
1893 ('U', 'noupdate', None, _('do not update the new working directories')),
1893 ('U', 'noupdate', None, _('do not update the new working directories')),
1894 ('', 'uncompressed', None,
1894 ('', 'uncompressed', None,
1895 _('use uncompressed transfer (fast over LAN)')),
1895 _('use uncompressed transfer (fast over LAN)')),
1896 ('e', 'ssh', '', _('specify ssh command to use')),
1896 ('e', 'ssh', '', _('specify ssh command to use')),
1897 ('p', 'patches', '', _('location of source patch repo')),
1897 ('p', 'patches', '', _('location of source patch repo')),
1898 ('', 'remotecmd', '',
1898 ('', 'remotecmd', '',
1899 _('specify hg command to run on the remote side'))],
1899 _('specify hg command to run on the remote side'))],
1900 'hg qclone [OPTION]... SOURCE [DEST]'),
1900 'hg qclone [OPTION]... SOURCE [DEST]'),
1901 "qcommit|qci":
1901 "qcommit|qci":
1902 (commit,
1902 (commit,
1903 commands.table["^commit|ci"][1],
1903 commands.table["^commit|ci"][1],
1904 'hg qcommit [OPTION]... [FILE]...'),
1904 'hg qcommit [OPTION]... [FILE]...'),
1905 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1905 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1906 "qdelete":
1906 "qdelete":
1907 (delete,
1907 (delete,
1908 [('f', 'force', None, _('delete patch file'))],
1908 [('f', 'force', None, _('delete patch file'))],
1909 'hg qdelete [-f] PATCH'),
1909 'hg qdelete [-f] PATCH'),
1910 'qfold':
1910 'qfold':
1911 (fold,
1911 (fold,
1912 [('e', 'edit', None, _('edit patch header')),
1912 [('e', 'edit', None, _('edit patch header')),
1913 ('f', 'force', None, _('delete folded patch files')),
1913 ('f', 'force', None, _('delete folded patch files')),
1914 ('m', 'message', '', _('set patch header to <text>')),
1914 ('m', 'message', '', _('set patch header to <text>')),
1915 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1915 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1916 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1916 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1917 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
1917 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
1918 ('n', 'none', None, _('drop all guards'))],
1918 ('n', 'none', None, _('drop all guards'))],
1919 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
1919 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
1920 'qheader': (header, [],
1920 'qheader': (header, [],
1921 _('hg qheader [PATCH]')),
1921 _('hg qheader [PATCH]')),
1922 "^qimport":
1922 "^qimport":
1923 (qimport,
1923 (qimport,
1924 [('e', 'existing', None, 'import file in patch dir'),
1924 [('e', 'existing', None, 'import file in patch dir'),
1925 ('n', 'name', '', 'patch file name'),
1925 ('n', 'name', '', 'patch file name'),
1926 ('f', 'force', None, 'overwrite existing files')],
1926 ('f', 'force', None, 'overwrite existing files')],
1927 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1927 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1928 "^qinit":
1928 "^qinit":
1929 (init,
1929 (init,
1930 [('c', 'create-repo', None, 'create queue repository')],
1930 [('c', 'create-repo', None, 'create queue repository')],
1931 'hg qinit [-c]'),
1931 'hg qinit [-c]'),
1932 "qnew":
1932 "qnew":
1933 (new,
1933 (new,
1934 [('m', 'message', '', _('use <text> as commit message')),
1934 [('m', 'message', '', _('use <text> as commit message')),
1935 ('l', 'logfile', '', _('read the commit message from <file>')),
1935 ('l', 'logfile', '', _('read the commit message from <file>')),
1936 ('f', 'force', None, _('import uncommitted changes into patch'))],
1936 ('f', 'force', None, _('import uncommitted changes into patch'))],
1937 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1937 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1938 "qnext": (next, [], 'hg qnext'),
1938 "qnext": (next, [], 'hg qnext'),
1939 "qprev": (prev, [], 'hg qprev'),
1939 "qprev": (prev, [], 'hg qprev'),
1940 "^qpop":
1940 "^qpop":
1941 (pop,
1941 (pop,
1942 [('a', 'all', None, 'pop all patches'),
1942 [('a', 'all', None, 'pop all patches'),
1943 ('n', 'name', '', 'queue name to pop'),
1943 ('n', 'name', '', 'queue name to pop'),
1944 ('f', 'force', None, 'forget any local changes')],
1944 ('f', 'force', None, 'forget any local changes')],
1945 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1945 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1946 "^qpush":
1946 "^qpush":
1947 (push,
1947 (push,
1948 [('f', 'force', None, 'apply if the patch has rejects'),
1948 [('f', 'force', None, 'apply if the patch has rejects'),
1949 ('l', 'list', None, 'list patch name in commit text'),
1949 ('l', 'list', None, 'list patch name in commit text'),
1950 ('a', 'all', None, 'apply all patches'),
1950 ('a', 'all', None, 'apply all patches'),
1951 ('m', 'merge', None, 'merge from another queue'),
1951 ('m', 'merge', None, 'merge from another queue'),
1952 ('n', 'name', '', 'merge queue name')],
1952 ('n', 'name', '', 'merge queue name')],
1953 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1953 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1954 "^qrefresh":
1954 "^qrefresh":
1955 (refresh,
1955 (refresh,
1956 [('e', 'edit', None, _('edit commit message')),
1956 [('e', 'edit', None, _('edit commit message')),
1957 ('m', 'message', '', _('change commit message with <text>')),
1957 ('m', 'message', '', _('change commit message with <text>')),
1958 ('l', 'logfile', '', _('change commit message with <file> content')),
1958 ('l', 'logfile', '', _('change commit message with <file> content')),
1959 ('s', 'short', None, 'short refresh')],
1959 ('s', 'short', None, 'short refresh')],
1960 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1960 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1961 'qrename|qmv':
1961 'qrename|qmv':
1962 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1962 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1963 "qrestore":
1963 "qrestore":
1964 (restore,
1964 (restore,
1965 [('d', 'delete', None, 'delete save entry'),
1965 [('d', 'delete', None, 'delete save entry'),
1966 ('u', 'update', None, 'update queue working dir')],
1966 ('u', 'update', None, 'update queue working dir')],
1967 'hg qrestore [-d] [-u] REV'),
1967 'hg qrestore [-d] [-u] REV'),
1968 "qsave":
1968 "qsave":
1969 (save,
1969 (save,
1970 [('m', 'message', '', _('use <text> as commit message')),
1970 [('m', 'message', '', _('use <text> as commit message')),
1971 ('l', 'logfile', '', _('read the commit message from <file>')),
1971 ('l', 'logfile', '', _('read the commit message from <file>')),
1972 ('c', 'copy', None, 'copy patch directory'),
1972 ('c', 'copy', None, 'copy patch directory'),
1973 ('n', 'name', '', 'copy directory name'),
1973 ('n', 'name', '', 'copy directory name'),
1974 ('e', 'empty', None, 'clear queue status file'),
1974 ('e', 'empty', None, 'clear queue status file'),
1975 ('f', 'force', None, 'force copy')],
1975 ('f', 'force', None, 'force copy')],
1976 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1976 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1977 "qselect": (select,
1977 "qselect": (select,
1978 [('n', 'none', None, _('disable all guards')),
1978 [('n', 'none', None, _('disable all guards')),
1979 ('s', 'series', None, _('list all guards in series file')),
1979 ('s', 'series', None, _('list all guards in series file')),
1980 ('', 'pop', None,
1980 ('', 'pop', None,
1981 _('pop to before first guarded applied patch')),
1981 _('pop to before first guarded applied patch')),
1982 ('', 'reapply', None, _('pop, then reapply patches'))],
1982 ('', 'reapply', None, _('pop, then reapply patches'))],
1983 'hg qselect [OPTION...] [GUARD...]'),
1983 'hg qselect [OPTION...] [GUARD...]'),
1984 "qseries":
1984 "qseries":
1985 (series,
1985 (series,
1986 [('m', 'missing', None, 'print patches not in series'),
1986 [('m', 'missing', None, 'print patches not in series'),
1987 ('s', 'summary', None, _('print first line of patch header'))],
1987 ('s', 'summary', None, _('print first line of patch header'))],
1988 'hg qseries [-m]'),
1988 'hg qseries [-m]'),
1989 "^strip":
1989 "^strip":
1990 (strip,
1990 (strip,
1991 [('f', 'force', None, 'force multi-head removal'),
1991 [('f', 'force', None, 'force multi-head removal'),
1992 ('b', 'backup', None, 'bundle unrelated changesets'),
1992 ('b', 'backup', None, 'bundle unrelated changesets'),
1993 ('n', 'nobackup', None, 'no backups')],
1993 ('n', 'nobackup', None, 'no backups')],
1994 'hg strip [-f] [-b] [-n] REV'),
1994 'hg strip [-f] [-b] [-n] REV'),
1995 "qtop": (top, [], 'hg qtop'),
1995 "qtop": (top, [], 'hg qtop'),
1996 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1996 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1997 }
1997 }
@@ -1,3489 +1,3490 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 demandload(globals(), "fnmatch difflib patch random signal tempfile time")
13 demandload(globals(), "fnmatch difflib patch random signal tempfile time")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 demandload(globals(), "archival cStringIO changegroup")
15 demandload(globals(), "archival cStringIO changegroup")
16 demandload(globals(), "cmdutil hgweb.server sshserver")
16 demandload(globals(), "cmdutil hgweb.server sshserver")
17
17
18 class UnknownCommand(Exception):
18 class UnknownCommand(Exception):
19 """Exception raised if command is not in the command table."""
19 """Exception raised if command is not in the command table."""
20 class AmbiguousCommand(Exception):
20 class AmbiguousCommand(Exception):
21 """Exception raised if command shortcut matches more than one command."""
21 """Exception raised if command shortcut matches more than one command."""
22
22
23 def bail_if_changed(repo):
23 def bail_if_changed(repo):
24 modified, added, removed, deleted = repo.status()[:4]
24 modified, added, removed, deleted = repo.status()[:4]
25 if modified or added or removed or deleted:
25 if modified or added or removed or deleted:
26 raise util.Abort(_("outstanding uncommitted changes"))
26 raise util.Abort(_("outstanding uncommitted changes"))
27
27
28 def relpath(repo, args):
28 def relpath(repo, args):
29 cwd = repo.getcwd()
29 cwd = repo.getcwd()
30 if cwd:
30 if cwd:
31 return [util.normpath(os.path.join(cwd, x)) for x in args]
31 return [util.normpath(os.path.join(cwd, x)) for x in args]
32 return args
32 return args
33
33
34 def logmessage(opts):
34 def logmessage(opts):
35 """ get the log message according to -m and -l option """
35 """ get the log message according to -m and -l option """
36 message = opts['message']
36 message = opts['message']
37 logfile = opts['logfile']
37 logfile = opts['logfile']
38
38
39 if message and logfile:
39 if message and logfile:
40 raise util.Abort(_('options --message and --logfile are mutually '
40 raise util.Abort(_('options --message and --logfile are mutually '
41 'exclusive'))
41 'exclusive'))
42 if not message and logfile:
42 if not message and logfile:
43 try:
43 try:
44 if logfile == '-':
44 if logfile == '-':
45 message = sys.stdin.read()
45 message = sys.stdin.read()
46 else:
46 else:
47 message = open(logfile).read()
47 message = open(logfile).read()
48 except IOError, inst:
48 except IOError, inst:
49 raise util.Abort(_("can't read commit message '%s': %s") %
49 raise util.Abort(_("can't read commit message '%s': %s") %
50 (logfile, inst.strerror))
50 (logfile, inst.strerror))
51 return message
51 return message
52
52
53 def walkchangerevs(ui, repo, pats, opts):
53 def walkchangerevs(ui, repo, pats, opts):
54 '''Iterate over files and the revs they changed in.
54 '''Iterate over files and the revs they changed in.
55
55
56 Callers most commonly need to iterate backwards over the history
56 Callers most commonly need to iterate backwards over the history
57 it is interested in. Doing so has awful (quadratic-looking)
57 it is interested in. Doing so has awful (quadratic-looking)
58 performance, so we use iterators in a "windowed" way.
58 performance, so we use iterators in a "windowed" way.
59
59
60 We walk a window of revisions in the desired order. Within the
60 We walk a window of revisions in the desired order. Within the
61 window, we first walk forwards to gather data, then in the desired
61 window, we first walk forwards to gather data, then in the desired
62 order (usually backwards) to display it.
62 order (usually backwards) to display it.
63
63
64 This function returns an (iterator, getchange, matchfn) tuple. The
64 This function returns an (iterator, getchange, matchfn) tuple. The
65 getchange function returns the changelog entry for a numeric
65 getchange function returns the changelog entry for a numeric
66 revision. The iterator yields 3-tuples. They will be of one of
66 revision. The iterator yields 3-tuples. They will be of one of
67 the following forms:
67 the following forms:
68
68
69 "window", incrementing, lastrev: stepping through a window,
69 "window", incrementing, lastrev: stepping through a window,
70 positive if walking forwards through revs, last rev in the
70 positive if walking forwards through revs, last rev in the
71 sequence iterated over - use to reset state for the current window
71 sequence iterated over - use to reset state for the current window
72
72
73 "add", rev, fns: out-of-order traversal of the given file names
73 "add", rev, fns: out-of-order traversal of the given file names
74 fns, which changed during revision rev - use to gather data for
74 fns, which changed during revision rev - use to gather data for
75 possible display
75 possible display
76
76
77 "iter", rev, None: in-order traversal of the revs earlier iterated
77 "iter", rev, None: in-order traversal of the revs earlier iterated
78 over with "add" - use to display data'''
78 over with "add" - use to display data'''
79
79
80 def increasing_windows(start, end, windowsize=8, sizelimit=512):
80 def increasing_windows(start, end, windowsize=8, sizelimit=512):
81 if start < end:
81 if start < end:
82 while start < end:
82 while start < end:
83 yield start, min(windowsize, end-start)
83 yield start, min(windowsize, end-start)
84 start += windowsize
84 start += windowsize
85 if windowsize < sizelimit:
85 if windowsize < sizelimit:
86 windowsize *= 2
86 windowsize *= 2
87 else:
87 else:
88 while start > end:
88 while start > end:
89 yield start, min(windowsize, start-end-1)
89 yield start, min(windowsize, start-end-1)
90 start -= windowsize
90 start -= windowsize
91 if windowsize < sizelimit:
91 if windowsize < sizelimit:
92 windowsize *= 2
92 windowsize *= 2
93
93
94
94
95 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
95 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
96 follow = opts.get('follow') or opts.get('follow_first')
96 follow = opts.get('follow') or opts.get('follow_first')
97
97
98 if repo.changelog.count() == 0:
98 if repo.changelog.count() == 0:
99 return [], False, matchfn
99 return [], False, matchfn
100
100
101 if follow:
101 if follow:
102 p = repo.dirstate.parents()[0]
102 p = repo.dirstate.parents()[0]
103 if p == nullid:
103 if p == nullid:
104 ui.warn(_('No working directory revision; defaulting to tip\n'))
104 ui.warn(_('No working directory revision; defaulting to tip\n'))
105 start = 'tip'
105 start = 'tip'
106 else:
106 else:
107 start = repo.changelog.rev(p)
107 start = repo.changelog.rev(p)
108 defrange = '%s:0' % start
108 defrange = '%s:0' % start
109 else:
109 else:
110 defrange = 'tip:0'
110 defrange = 'tip:0'
111 revs = map(int, revrange(ui, repo, opts['rev'] or [defrange]))
111 revs = map(int, revrange(ui, repo, opts['rev'] or [defrange]))
112 wanted = {}
112 wanted = {}
113 slowpath = anypats
113 slowpath = anypats
114 fncache = {}
114 fncache = {}
115
115
116 chcache = {}
116 chcache = {}
117 def getchange(rev):
117 def getchange(rev):
118 ch = chcache.get(rev)
118 ch = chcache.get(rev)
119 if ch is None:
119 if ch is None:
120 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
120 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
121 return ch
121 return ch
122
122
123 if not slowpath and not files:
123 if not slowpath and not files:
124 # No files, no patterns. Display all revs.
124 # No files, no patterns. Display all revs.
125 wanted = dict(zip(revs, revs))
125 wanted = dict(zip(revs, revs))
126 copies = []
126 copies = []
127 if not slowpath:
127 if not slowpath:
128 # Only files, no patterns. Check the history of each file.
128 # Only files, no patterns. Check the history of each file.
129 def filerevgen(filelog, node):
129 def filerevgen(filelog, node):
130 cl_count = repo.changelog.count()
130 cl_count = repo.changelog.count()
131 if node is None:
131 if node is None:
132 last = filelog.count() - 1
132 last = filelog.count() - 1
133 else:
133 else:
134 last = filelog.rev(node)
134 last = filelog.rev(node)
135 for i, window in increasing_windows(last, -1):
135 for i, window in increasing_windows(last, -1):
136 revs = []
136 revs = []
137 for j in xrange(i - window, i + 1):
137 for j in xrange(i - window, i + 1):
138 n = filelog.node(j)
138 n = filelog.node(j)
139 revs.append((filelog.linkrev(n),
139 revs.append((filelog.linkrev(n),
140 follow and filelog.renamed(n)))
140 follow and filelog.renamed(n)))
141 revs.reverse()
141 revs.reverse()
142 for rev in revs:
142 for rev in revs:
143 # only yield rev for which we have the changelog, it can
143 # only yield rev for which we have the changelog, it can
144 # happen while doing "hg log" during a pull or commit
144 # happen while doing "hg log" during a pull or commit
145 if rev[0] < cl_count:
145 if rev[0] < cl_count:
146 yield rev
146 yield rev
147 def iterfiles():
147 def iterfiles():
148 for filename in files:
148 for filename in files:
149 yield filename, None
149 yield filename, None
150 for filename_node in copies:
150 for filename_node in copies:
151 yield filename_node
151 yield filename_node
152 minrev, maxrev = min(revs), max(revs)
152 minrev, maxrev = min(revs), max(revs)
153 for file_, node in iterfiles():
153 for file_, node in iterfiles():
154 filelog = repo.file(file_)
154 filelog = repo.file(file_)
155 # A zero count may be a directory or deleted file, so
155 # A zero count may be a directory or deleted file, so
156 # try to find matching entries on the slow path.
156 # try to find matching entries on the slow path.
157 if filelog.count() == 0:
157 if filelog.count() == 0:
158 slowpath = True
158 slowpath = True
159 break
159 break
160 for rev, copied in filerevgen(filelog, node):
160 for rev, copied in filerevgen(filelog, node):
161 if rev <= maxrev:
161 if rev <= maxrev:
162 if rev < minrev:
162 if rev < minrev:
163 break
163 break
164 fncache.setdefault(rev, [])
164 fncache.setdefault(rev, [])
165 fncache[rev].append(file_)
165 fncache[rev].append(file_)
166 wanted[rev] = 1
166 wanted[rev] = 1
167 if follow and copied:
167 if follow and copied:
168 copies.append(copied)
168 copies.append(copied)
169 if slowpath:
169 if slowpath:
170 if follow:
170 if follow:
171 raise util.Abort(_('can only follow copies/renames for explicit '
171 raise util.Abort(_('can only follow copies/renames for explicit '
172 'file names'))
172 'file names'))
173
173
174 # The slow path checks files modified in every changeset.
174 # The slow path checks files modified in every changeset.
175 def changerevgen():
175 def changerevgen():
176 for i, window in increasing_windows(repo.changelog.count()-1, -1):
176 for i, window in increasing_windows(repo.changelog.count()-1, -1):
177 for j in xrange(i - window, i + 1):
177 for j in xrange(i - window, i + 1):
178 yield j, getchange(j)[3]
178 yield j, getchange(j)[3]
179
179
180 for rev, changefiles in changerevgen():
180 for rev, changefiles in changerevgen():
181 matches = filter(matchfn, changefiles)
181 matches = filter(matchfn, changefiles)
182 if matches:
182 if matches:
183 fncache[rev] = matches
183 fncache[rev] = matches
184 wanted[rev] = 1
184 wanted[rev] = 1
185
185
186 def iterate():
186 def iterate():
187 class followfilter:
187 class followfilter:
188 def __init__(self, onlyfirst=False):
188 def __init__(self, onlyfirst=False):
189 self.startrev = -1
189 self.startrev = -1
190 self.roots = []
190 self.roots = []
191 self.onlyfirst = onlyfirst
191 self.onlyfirst = onlyfirst
192
192
193 def match(self, rev):
193 def match(self, rev):
194 def realparents(rev):
194 def realparents(rev):
195 if self.onlyfirst:
195 if self.onlyfirst:
196 return repo.changelog.parentrevs(rev)[0:1]
196 return repo.changelog.parentrevs(rev)[0:1]
197 else:
197 else:
198 return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))
198 return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))
199
199
200 if self.startrev == -1:
200 if self.startrev == -1:
201 self.startrev = rev
201 self.startrev = rev
202 return True
202 return True
203
203
204 if rev > self.startrev:
204 if rev > self.startrev:
205 # forward: all descendants
205 # forward: all descendants
206 if not self.roots:
206 if not self.roots:
207 self.roots.append(self.startrev)
207 self.roots.append(self.startrev)
208 for parent in realparents(rev):
208 for parent in realparents(rev):
209 if parent in self.roots:
209 if parent in self.roots:
210 self.roots.append(rev)
210 self.roots.append(rev)
211 return True
211 return True
212 else:
212 else:
213 # backwards: all parents
213 # backwards: all parents
214 if not self.roots:
214 if not self.roots:
215 self.roots.extend(realparents(self.startrev))
215 self.roots.extend(realparents(self.startrev))
216 if rev in self.roots:
216 if rev in self.roots:
217 self.roots.remove(rev)
217 self.roots.remove(rev)
218 self.roots.extend(realparents(rev))
218 self.roots.extend(realparents(rev))
219 return True
219 return True
220
220
221 return False
221 return False
222
222
223 if follow and not files:
223 if follow and not files:
224 ff = followfilter(onlyfirst=opts.get('follow_first'))
224 ff = followfilter(onlyfirst=opts.get('follow_first'))
225 def want(rev):
225 def want(rev):
226 if rev not in wanted:
226 if rev not in wanted:
227 return False
227 return False
228 return ff.match(rev)
228 return ff.match(rev)
229 else:
229 else:
230 def want(rev):
230 def want(rev):
231 return rev in wanted
231 return rev in wanted
232
232
233 for i, window in increasing_windows(0, len(revs)):
233 for i, window in increasing_windows(0, len(revs)):
234 yield 'window', revs[0] < revs[-1], revs[-1]
234 yield 'window', revs[0] < revs[-1], revs[-1]
235 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
235 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
236 srevs = list(nrevs)
236 srevs = list(nrevs)
237 srevs.sort()
237 srevs.sort()
238 for rev in srevs:
238 for rev in srevs:
239 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
239 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
240 yield 'add', rev, fns
240 yield 'add', rev, fns
241 for rev in nrevs:
241 for rev in nrevs:
242 yield 'iter', rev, None
242 yield 'iter', rev, None
243 return iterate(), getchange, matchfn
243 return iterate(), getchange, matchfn
244
244
245 revrangesep = ':'
245 revrangesep = ':'
246
246
247 def revfix(repo, val, defval):
247 def revfix(repo, val, defval):
248 '''turn user-level id of changeset into rev number.
248 '''turn user-level id of changeset into rev number.
249 user-level id can be tag, changeset, rev number, or negative rev
249 user-level id can be tag, changeset, rev number, or negative rev
250 number relative to number of revs (-1 is tip, etc).'''
250 number relative to number of revs (-1 is tip, etc).'''
251 if not val:
251 if not val:
252 return defval
252 return defval
253 try:
253 try:
254 num = int(val)
254 num = int(val)
255 if str(num) != val:
255 if str(num) != val:
256 raise ValueError
256 raise ValueError
257 if num < 0:
257 if num < 0:
258 num += repo.changelog.count()
258 num += repo.changelog.count()
259 if num < 0:
259 if num < 0:
260 num = 0
260 num = 0
261 elif num >= repo.changelog.count():
261 elif num >= repo.changelog.count():
262 raise ValueError
262 raise ValueError
263 except ValueError:
263 except ValueError:
264 try:
264 try:
265 num = repo.changelog.rev(repo.lookup(val))
265 num = repo.changelog.rev(repo.lookup(val))
266 except KeyError:
266 except KeyError:
267 raise util.Abort(_('invalid revision identifier %s'), val)
267 raise util.Abort(_('invalid revision identifier %s'), val)
268 return num
268 return num
269
269
270 def revpair(ui, repo, revs):
270 def revpair(ui, repo, revs):
271 '''return pair of nodes, given list of revisions. second item can
271 '''return pair of nodes, given list of revisions. second item can
272 be None, meaning use working dir.'''
272 be None, meaning use working dir.'''
273 if not revs:
273 if not revs:
274 return repo.dirstate.parents()[0], None
274 return repo.dirstate.parents()[0], None
275 end = None
275 end = None
276 if len(revs) == 1:
276 if len(revs) == 1:
277 start = revs[0]
277 start = revs[0]
278 if revrangesep in start:
278 if revrangesep in start:
279 start, end = start.split(revrangesep, 1)
279 start, end = start.split(revrangesep, 1)
280 start = revfix(repo, start, 0)
280 start = revfix(repo, start, 0)
281 end = revfix(repo, end, repo.changelog.count() - 1)
281 end = revfix(repo, end, repo.changelog.count() - 1)
282 else:
282 else:
283 start = revfix(repo, start, None)
283 start = revfix(repo, start, None)
284 elif len(revs) == 2:
284 elif len(revs) == 2:
285 if revrangesep in revs[0] or revrangesep in revs[1]:
285 if revrangesep in revs[0] or revrangesep in revs[1]:
286 raise util.Abort(_('too many revisions specified'))
286 raise util.Abort(_('too many revisions specified'))
287 start = revfix(repo, revs[0], None)
287 start = revfix(repo, revs[0], None)
288 end = revfix(repo, revs[1], None)
288 end = revfix(repo, revs[1], None)
289 else:
289 else:
290 raise util.Abort(_('too many revisions specified'))
290 raise util.Abort(_('too many revisions specified'))
291 if end is not None: end = repo.lookup(str(end))
291 if end is not None: end = repo.lookup(str(end))
292 return repo.lookup(str(start)), end
292 return repo.lookup(str(start)), end
293
293
294 def revrange(ui, repo, revs):
294 def revrange(ui, repo, revs):
295 """Yield revision as strings from a list of revision specifications."""
295 """Yield revision as strings from a list of revision specifications."""
296 seen = {}
296 seen = {}
297 for spec in revs:
297 for spec in revs:
298 if revrangesep in spec:
298 if revrangesep in spec:
299 start, end = spec.split(revrangesep, 1)
299 start, end = spec.split(revrangesep, 1)
300 start = revfix(repo, start, 0)
300 start = revfix(repo, start, 0)
301 end = revfix(repo, end, repo.changelog.count() - 1)
301 end = revfix(repo, end, repo.changelog.count() - 1)
302 step = start > end and -1 or 1
302 step = start > end and -1 or 1
303 for rev in xrange(start, end+step, step):
303 for rev in xrange(start, end+step, step):
304 if rev in seen:
304 if rev in seen:
305 continue
305 continue
306 seen[rev] = 1
306 seen[rev] = 1
307 yield str(rev)
307 yield str(rev)
308 else:
308 else:
309 rev = revfix(repo, spec, None)
309 rev = revfix(repo, spec, None)
310 if rev in seen:
310 if rev in seen:
311 continue
311 continue
312 seen[rev] = 1
312 seen[rev] = 1
313 yield str(rev)
313 yield str(rev)
314
314
315 def write_bundle(cg, filename=None, compress=True):
315 def write_bundle(cg, filename=None, compress=True):
316 """Write a bundle file and return its filename.
316 """Write a bundle file and return its filename.
317
317
318 Existing files will not be overwritten.
318 Existing files will not be overwritten.
319 If no filename is specified, a temporary file is created.
319 If no filename is specified, a temporary file is created.
320 bz2 compression can be turned off.
320 bz2 compression can be turned off.
321 The bundle file will be deleted in case of errors.
321 The bundle file will be deleted in case of errors.
322 """
322 """
323 class nocompress(object):
323 class nocompress(object):
324 def compress(self, x):
324 def compress(self, x):
325 return x
325 return x
326 def flush(self):
326 def flush(self):
327 return ""
327 return ""
328
328
329 fh = None
329 fh = None
330 cleanup = None
330 cleanup = None
331 try:
331 try:
332 if filename:
332 if filename:
333 if os.path.exists(filename):
333 if os.path.exists(filename):
334 raise util.Abort(_("file '%s' already exists"), filename)
334 raise util.Abort(_("file '%s' already exists"), filename)
335 fh = open(filename, "wb")
335 fh = open(filename, "wb")
336 else:
336 else:
337 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
337 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
338 fh = os.fdopen(fd, "wb")
338 fh = os.fdopen(fd, "wb")
339 cleanup = filename
339 cleanup = filename
340
340
341 if compress:
341 if compress:
342 fh.write("HG10")
342 fh.write("HG10")
343 z = bz2.BZ2Compressor(9)
343 z = bz2.BZ2Compressor(9)
344 else:
344 else:
345 fh.write("HG10UN")
345 fh.write("HG10UN")
346 z = nocompress()
346 z = nocompress()
347 # parse the changegroup data, otherwise we will block
347 # parse the changegroup data, otherwise we will block
348 # in case of sshrepo because we don't know the end of the stream
348 # in case of sshrepo because we don't know the end of the stream
349
349
350 # an empty chunkiter is the end of the changegroup
350 # an empty chunkiter is the end of the changegroup
351 empty = False
351 empty = False
352 while not empty:
352 while not empty:
353 empty = True
353 empty = True
354 for chunk in changegroup.chunkiter(cg):
354 for chunk in changegroup.chunkiter(cg):
355 empty = False
355 empty = False
356 fh.write(z.compress(changegroup.genchunk(chunk)))
356 fh.write(z.compress(changegroup.genchunk(chunk)))
357 fh.write(z.compress(changegroup.closechunk()))
357 fh.write(z.compress(changegroup.closechunk()))
358 fh.write(z.flush())
358 fh.write(z.flush())
359 cleanup = None
359 cleanup = None
360 return filename
360 return filename
361 finally:
361 finally:
362 if fh is not None:
362 if fh is not None:
363 fh.close()
363 fh.close()
364 if cleanup is not None:
364 if cleanup is not None:
365 os.unlink(cleanup)
365 os.unlink(cleanup)
366
366
367 def trimuser(ui, name, rev, revcache):
367 def trimuser(ui, name, rev, revcache):
368 """trim the name of the user who committed a change"""
368 """trim the name of the user who committed a change"""
369 user = revcache.get(rev)
369 user = revcache.get(rev)
370 if user is None:
370 if user is None:
371 user = revcache[rev] = ui.shortuser(name)
371 user = revcache[rev] = ui.shortuser(name)
372 return user
372 return user
373
373
374 class changeset_printer(object):
374 class changeset_printer(object):
375 '''show changeset information when templating not requested.'''
375 '''show changeset information when templating not requested.'''
376
376
377 def __init__(self, ui, repo):
377 def __init__(self, ui, repo):
378 self.ui = ui
378 self.ui = ui
379 self.repo = repo
379 self.repo = repo
380
380
381 def show(self, rev=0, changenode=None, brinfo=None):
381 def show(self, rev=0, changenode=None, brinfo=None):
382 '''show a single changeset or file revision'''
382 '''show a single changeset or file revision'''
383 log = self.repo.changelog
383 log = self.repo.changelog
384 if changenode is None:
384 if changenode is None:
385 changenode = log.node(rev)
385 changenode = log.node(rev)
386 elif not rev:
386 elif not rev:
387 rev = log.rev(changenode)
387 rev = log.rev(changenode)
388
388
389 if self.ui.quiet:
389 if self.ui.quiet:
390 self.ui.write("%d:%s\n" % (rev, short(changenode)))
390 self.ui.write("%d:%s\n" % (rev, short(changenode)))
391 return
391 return
392
392
393 changes = log.read(changenode)
393 changes = log.read(changenode)
394 date = util.datestr(changes[2])
394 date = util.datestr(changes[2])
395
395
396 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
396 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
397 for p in log.parents(changenode)
397 for p in log.parents(changenode)
398 if self.ui.debugflag or p != nullid]
398 if self.ui.debugflag or p != nullid]
399 if (not self.ui.debugflag and len(parents) == 1 and
399 if (not self.ui.debugflag and len(parents) == 1 and
400 parents[0][0] == rev-1):
400 parents[0][0] == rev-1):
401 parents = []
401 parents = []
402
402
403 if self.ui.verbose:
403 if self.ui.verbose:
404 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
404 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
405 else:
405 else:
406 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
406 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
407
407
408 for tag in self.repo.nodetags(changenode):
408 for tag in self.repo.nodetags(changenode):
409 self.ui.status(_("tag: %s\n") % tag)
409 self.ui.status(_("tag: %s\n") % tag)
410 for parent in parents:
410 for parent in parents:
411 self.ui.write(_("parent: %d:%s\n") % parent)
411 self.ui.write(_("parent: %d:%s\n") % parent)
412
412
413 if brinfo and changenode in brinfo:
413 if brinfo and changenode in brinfo:
414 br = brinfo[changenode]
414 br = brinfo[changenode]
415 self.ui.write(_("branch: %s\n") % " ".join(br))
415 self.ui.write(_("branch: %s\n") % " ".join(br))
416
416
417 self.ui.debug(_("manifest: %d:%s\n") %
417 self.ui.debug(_("manifest: %d:%s\n") %
418 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
418 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
419 self.ui.status(_("user: %s\n") % changes[1])
419 self.ui.status(_("user: %s\n") % changes[1])
420 self.ui.status(_("date: %s\n") % date)
420 self.ui.status(_("date: %s\n") % date)
421
421
422 if self.ui.debugflag:
422 if self.ui.debugflag:
423 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
423 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
424 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
424 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
425 files):
425 files):
426 if value:
426 if value:
427 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
427 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
428 else:
428 else:
429 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
429 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
430
430
431 description = changes[4].strip()
431 description = changes[4].strip()
432 if description:
432 if description:
433 if self.ui.verbose:
433 if self.ui.verbose:
434 self.ui.status(_("description:\n"))
434 self.ui.status(_("description:\n"))
435 self.ui.status(description)
435 self.ui.status(description)
436 self.ui.status("\n\n")
436 self.ui.status("\n\n")
437 else:
437 else:
438 self.ui.status(_("summary: %s\n") %
438 self.ui.status(_("summary: %s\n") %
439 description.splitlines()[0])
439 description.splitlines()[0])
440 self.ui.status("\n")
440 self.ui.status("\n")
441
441
442 def show_changeset(ui, repo, opts):
442 def show_changeset(ui, repo, opts):
443 '''show one changeset. uses template or regular display. caller
443 '''show one changeset. uses template or regular display. caller
444 can pass in 'style' and 'template' options in opts.'''
444 can pass in 'style' and 'template' options in opts.'''
445
445
446 tmpl = opts.get('template')
446 tmpl = opts.get('template')
447 if tmpl:
447 if tmpl:
448 tmpl = templater.parsestring(tmpl, quoted=False)
448 tmpl = templater.parsestring(tmpl, quoted=False)
449 else:
449 else:
450 tmpl = ui.config('ui', 'logtemplate')
450 tmpl = ui.config('ui', 'logtemplate')
451 if tmpl: tmpl = templater.parsestring(tmpl)
451 if tmpl: tmpl = templater.parsestring(tmpl)
452 mapfile = opts.get('style') or ui.config('ui', 'style')
452 mapfile = opts.get('style') or ui.config('ui', 'style')
453 if tmpl or mapfile:
453 if tmpl or mapfile:
454 if mapfile:
454 if mapfile:
455 if not os.path.isfile(mapfile):
455 if not os.path.isfile(mapfile):
456 mapname = templater.templatepath('map-cmdline.' + mapfile)
456 mapname = templater.templatepath('map-cmdline.' + mapfile)
457 if not mapname: mapname = templater.templatepath(mapfile)
457 if not mapname: mapname = templater.templatepath(mapfile)
458 if mapname: mapfile = mapname
458 if mapname: mapfile = mapname
459 try:
459 try:
460 t = templater.changeset_templater(ui, repo, mapfile)
460 t = templater.changeset_templater(ui, repo, mapfile)
461 except SyntaxError, inst:
461 except SyntaxError, inst:
462 raise util.Abort(inst.args[0])
462 raise util.Abort(inst.args[0])
463 if tmpl: t.use_template(tmpl)
463 if tmpl: t.use_template(tmpl)
464 return t
464 return t
465 return changeset_printer(ui, repo)
465 return changeset_printer(ui, repo)
466
466
467 def setremoteconfig(ui, opts):
467 def setremoteconfig(ui, opts):
468 "copy remote options to ui tree"
468 "copy remote options to ui tree"
469 if opts.get('ssh'):
469 if opts.get('ssh'):
470 ui.setconfig("ui", "ssh", opts['ssh'])
470 ui.setconfig("ui", "ssh", opts['ssh'])
471 if opts.get('remotecmd'):
471 if opts.get('remotecmd'):
472 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
472 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
473
473
474 def show_version(ui):
474 def show_version(ui):
475 """output version and copyright information"""
475 """output version and copyright information"""
476 ui.write(_("Mercurial Distributed SCM (version %s)\n")
476 ui.write(_("Mercurial Distributed SCM (version %s)\n")
477 % version.get_version())
477 % version.get_version())
478 ui.status(_(
478 ui.status(_(
479 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
479 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
480 "This is free software; see the source for copying conditions. "
480 "This is free software; see the source for copying conditions. "
481 "There is NO\nwarranty; "
481 "There is NO\nwarranty; "
482 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
482 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
483 ))
483 ))
484
484
485 def help_(ui, name=None, with_version=False):
485 def help_(ui, name=None, with_version=False):
486 """show help for a command, extension, or list of commands
486 """show help for a command, extension, or list of commands
487
487
488 With no arguments, print a list of commands and short help.
488 With no arguments, print a list of commands and short help.
489
489
490 Given a command name, print help for that command.
490 Given a command name, print help for that command.
491
491
492 Given an extension name, print help for that extension, and the
492 Given an extension name, print help for that extension, and the
493 commands it provides."""
493 commands it provides."""
494 option_lists = []
494 option_lists = []
495
495
496 def helpcmd(name):
496 def helpcmd(name):
497 if with_version:
497 if with_version:
498 show_version(ui)
498 show_version(ui)
499 ui.write('\n')
499 ui.write('\n')
500 aliases, i = findcmd(name)
500 aliases, i = findcmd(name)
501 # synopsis
501 # synopsis
502 ui.write("%s\n\n" % i[2])
502 ui.write("%s\n\n" % i[2])
503
503
504 # description
504 # description
505 doc = i[0].__doc__
505 doc = i[0].__doc__
506 if not doc:
506 if not doc:
507 doc = _("(No help text available)")
507 doc = _("(No help text available)")
508 if ui.quiet:
508 if ui.quiet:
509 doc = doc.splitlines(0)[0]
509 doc = doc.splitlines(0)[0]
510 ui.write("%s\n" % doc.rstrip())
510 ui.write("%s\n" % doc.rstrip())
511
511
512 if not ui.quiet:
512 if not ui.quiet:
513 # aliases
513 # aliases
514 if len(aliases) > 1:
514 if len(aliases) > 1:
515 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
515 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
516
516
517 # options
517 # options
518 if i[1]:
518 if i[1]:
519 option_lists.append(("options", i[1]))
519 option_lists.append(("options", i[1]))
520
520
521 def helplist(select=None):
521 def helplist(select=None):
522 h = {}
522 h = {}
523 cmds = {}
523 cmds = {}
524 for c, e in table.items():
524 for c, e in table.items():
525 f = c.split("|", 1)[0]
525 f = c.split("|", 1)[0]
526 if select and not select(f):
526 if select and not select(f):
527 continue
527 continue
528 if name == "shortlist" and not f.startswith("^"):
528 if name == "shortlist" and not f.startswith("^"):
529 continue
529 continue
530 f = f.lstrip("^")
530 f = f.lstrip("^")
531 if not ui.debugflag and f.startswith("debug"):
531 if not ui.debugflag and f.startswith("debug"):
532 continue
532 continue
533 doc = e[0].__doc__
533 doc = e[0].__doc__
534 if not doc:
534 if not doc:
535 doc = _("(No help text available)")
535 doc = _("(No help text available)")
536 h[f] = doc.splitlines(0)[0].rstrip()
536 h[f] = doc.splitlines(0)[0].rstrip()
537 cmds[f] = c.lstrip("^")
537 cmds[f] = c.lstrip("^")
538
538
539 fns = h.keys()
539 fns = h.keys()
540 fns.sort()
540 fns.sort()
541 m = max(map(len, fns))
541 m = max(map(len, fns))
542 for f in fns:
542 for f in fns:
543 if ui.verbose:
543 if ui.verbose:
544 commands = cmds[f].replace("|",", ")
544 commands = cmds[f].replace("|",", ")
545 ui.write(" %s:\n %s\n"%(commands, h[f]))
545 ui.write(" %s:\n %s\n"%(commands, h[f]))
546 else:
546 else:
547 ui.write(' %-*s %s\n' % (m, f, h[f]))
547 ui.write(' %-*s %s\n' % (m, f, h[f]))
548
548
549 def helpext(name):
549 def helpext(name):
550 try:
550 try:
551 mod = findext(name)
551 mod = findext(name)
552 except KeyError:
552 except KeyError:
553 raise UnknownCommand(name)
553 raise UnknownCommand(name)
554
554
555 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
555 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
556 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
556 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
557 for d in doc[1:]:
557 for d in doc[1:]:
558 ui.write(d, '\n')
558 ui.write(d, '\n')
559
559
560 ui.status('\n')
560 ui.status('\n')
561 if ui.verbose:
561 if ui.verbose:
562 ui.status(_('list of commands:\n\n'))
562 ui.status(_('list of commands:\n\n'))
563 else:
563 else:
564 ui.status(_('list of commands (use "hg help -v %s" '
564 ui.status(_('list of commands (use "hg help -v %s" '
565 'to show aliases and global options):\n\n') % name)
565 'to show aliases and global options):\n\n') % name)
566
566
567 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
567 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
568 helplist(modcmds.has_key)
568 helplist(modcmds.has_key)
569
569
570 if name and name != 'shortlist':
570 if name and name != 'shortlist':
571 try:
571 try:
572 helpcmd(name)
572 helpcmd(name)
573 except UnknownCommand:
573 except UnknownCommand:
574 helpext(name)
574 helpext(name)
575
575
576 else:
576 else:
577 # program name
577 # program name
578 if ui.verbose or with_version:
578 if ui.verbose or with_version:
579 show_version(ui)
579 show_version(ui)
580 else:
580 else:
581 ui.status(_("Mercurial Distributed SCM\n"))
581 ui.status(_("Mercurial Distributed SCM\n"))
582 ui.status('\n')
582 ui.status('\n')
583
583
584 # list of commands
584 # list of commands
585 if name == "shortlist":
585 if name == "shortlist":
586 ui.status(_('basic commands (use "hg help" '
586 ui.status(_('basic commands (use "hg help" '
587 'for the full list or option "-v" for details):\n\n'))
587 'for the full list or option "-v" for details):\n\n'))
588 elif ui.verbose:
588 elif ui.verbose:
589 ui.status(_('list of commands:\n\n'))
589 ui.status(_('list of commands:\n\n'))
590 else:
590 else:
591 ui.status(_('list of commands (use "hg help -v" '
591 ui.status(_('list of commands (use "hg help -v" '
592 'to show aliases and global options):\n\n'))
592 'to show aliases and global options):\n\n'))
593
593
594 helplist()
594 helplist()
595
595
596 # global options
596 # global options
597 if ui.verbose:
597 if ui.verbose:
598 option_lists.append(("global options", globalopts))
598 option_lists.append(("global options", globalopts))
599
599
600 # list all option lists
600 # list all option lists
601 opt_output = []
601 opt_output = []
602 for title, options in option_lists:
602 for title, options in option_lists:
603 opt_output.append(("\n%s:\n" % title, None))
603 opt_output.append(("\n%s:\n" % title, None))
604 for shortopt, longopt, default, desc in options:
604 for shortopt, longopt, default, desc in options:
605 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
605 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
606 longopt and " --%s" % longopt),
606 longopt and " --%s" % longopt),
607 "%s%s" % (desc,
607 "%s%s" % (desc,
608 default
608 default
609 and _(" (default: %s)") % default
609 and _(" (default: %s)") % default
610 or "")))
610 or "")))
611
611
612 if opt_output:
612 if opt_output:
613 opts_len = max([len(line[0]) for line in opt_output if line[1]])
613 opts_len = max([len(line[0]) for line in opt_output if line[1]])
614 for first, second in opt_output:
614 for first, second in opt_output:
615 if second:
615 if second:
616 ui.write(" %-*s %s\n" % (opts_len, first, second))
616 ui.write(" %-*s %s\n" % (opts_len, first, second))
617 else:
617 else:
618 ui.write("%s\n" % first)
618 ui.write("%s\n" % first)
619
619
620 # Commands start here, listed alphabetically
620 # Commands start here, listed alphabetically
621
621
622 def add(ui, repo, *pats, **opts):
622 def add(ui, repo, *pats, **opts):
623 """add the specified files on the next commit
623 """add the specified files on the next commit
624
624
625 Schedule files to be version controlled and added to the repository.
625 Schedule files to be version controlled and added to the repository.
626
626
627 The files will be added to the repository at the next commit.
627 The files will be added to the repository at the next commit.
628
628
629 If no names are given, add all files in the repository.
629 If no names are given, add all files in the repository.
630 """
630 """
631
631
632 names = []
632 names = []
633 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
633 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
634 if exact:
634 if exact:
635 if ui.verbose:
635 if ui.verbose:
636 ui.status(_('adding %s\n') % rel)
636 ui.status(_('adding %s\n') % rel)
637 names.append(abs)
637 names.append(abs)
638 elif repo.dirstate.state(abs) == '?':
638 elif repo.dirstate.state(abs) == '?':
639 ui.status(_('adding %s\n') % rel)
639 ui.status(_('adding %s\n') % rel)
640 names.append(abs)
640 names.append(abs)
641 if not opts.get('dry_run'):
641 if not opts.get('dry_run'):
642 repo.add(names)
642 repo.add(names)
643
643
644 def addremove(ui, repo, *pats, **opts):
644 def addremove(ui, repo, *pats, **opts):
645 """add all new files, delete all missing files (DEPRECATED)
645 """add all new files, delete all missing files (DEPRECATED)
646
646
647 (DEPRECATED)
647 (DEPRECATED)
648 Add all new files and remove all missing files from the repository.
648 Add all new files and remove all missing files from the repository.
649
649
650 New files are ignored if they match any of the patterns in .hgignore. As
650 New files are ignored if they match any of the patterns in .hgignore. As
651 with add, these changes take effect at the next commit.
651 with add, these changes take effect at the next commit.
652
652
653 This command is now deprecated and will be removed in a future
653 This command is now deprecated and will be removed in a future
654 release. Please use add and remove --after instead.
654 release. Please use add and remove --after instead.
655 """
655 """
656 ui.warn(_('(the addremove command is deprecated; use add and remove '
656 ui.warn(_('(the addremove command is deprecated; use add and remove '
657 '--after instead)\n'))
657 '--after instead)\n'))
658 return cmdutil.addremove(repo, pats, opts)
658 return cmdutil.addremove(repo, pats, opts)
659
659
660 def annotate(ui, repo, *pats, **opts):
660 def annotate(ui, repo, *pats, **opts):
661 """show changeset information per file line
661 """show changeset information per file line
662
662
663 List changes in files, showing the revision id responsible for each line
663 List changes in files, showing the revision id responsible for each line
664
664
665 This command is useful to discover who did a change or when a change took
665 This command is useful to discover who did a change or when a change took
666 place.
666 place.
667
667
668 Without the -a option, annotate will avoid processing files it
668 Without the -a option, annotate will avoid processing files it
669 detects as binary. With -a, annotate will generate an annotation
669 detects as binary. With -a, annotate will generate an annotation
670 anyway, probably with undesirable results.
670 anyway, probably with undesirable results.
671 """
671 """
672 def getnode(rev):
672 def getnode(rev):
673 return short(repo.changelog.node(rev))
673 return short(repo.changelog.node(rev))
674
674
675 ucache = {}
675 ucache = {}
676 def getname(rev):
676 def getname(rev):
677 try:
677 try:
678 return ucache[rev]
678 return ucache[rev]
679 except:
679 except:
680 u = trimuser(ui, repo.changectx(rev).user(), rev, ucache)
680 u = trimuser(ui, repo.changectx(rev).user(), rev, ucache)
681 ucache[rev] = u
681 ucache[rev] = u
682 return u
682 return u
683
683
684 dcache = {}
684 dcache = {}
685 def getdate(rev):
685 def getdate(rev):
686 datestr = dcache.get(rev)
686 datestr = dcache.get(rev)
687 if datestr is None:
687 if datestr is None:
688 datestr = dcache[rev] = util.datestr(repo.changectx(rev).date())
688 datestr = dcache[rev] = util.datestr(repo.changectx(rev).date())
689 return datestr
689 return datestr
690
690
691 if not pats:
691 if not pats:
692 raise util.Abort(_('at least one file name or pattern required'))
692 raise util.Abort(_('at least one file name or pattern required'))
693
693
694 opmap = [['user', getname], ['number', str], ['changeset', getnode],
694 opmap = [['user', getname], ['number', str], ['changeset', getnode],
695 ['date', getdate]]
695 ['date', getdate]]
696 if not opts['user'] and not opts['changeset'] and not opts['date']:
696 if not opts['user'] and not opts['changeset'] and not opts['date']:
697 opts['number'] = 1
697 opts['number'] = 1
698
698
699 ctx = repo.changectx(opts['rev'] or repo.dirstate.parents()[0])
699 ctx = repo.changectx(opts['rev'] or repo.dirstate.parents()[0])
700
700
701 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
701 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
702 node=ctx.node()):
702 node=ctx.node()):
703 fctx = ctx.filectx(abs)
703 fctx = ctx.filectx(abs)
704 if not opts['text'] and util.binary(fctx.data()):
704 if not opts['text'] and util.binary(fctx.data()):
705 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
705 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
706 continue
706 continue
707
707
708 lines = fctx.annotate()
708 lines = fctx.annotate()
709 pieces = []
709 pieces = []
710
710
711 for o, f in opmap:
711 for o, f in opmap:
712 if opts[o]:
712 if opts[o]:
713 l = [f(n) for n, dummy in lines]
713 l = [f(n) for n, dummy in lines]
714 if l:
714 if l:
715 m = max(map(len, l))
715 m = max(map(len, l))
716 pieces.append(["%*s" % (m, x) for x in l])
716 pieces.append(["%*s" % (m, x) for x in l])
717
717
718 if pieces:
718 if pieces:
719 for p, l in zip(zip(*pieces), lines):
719 for p, l in zip(zip(*pieces), lines):
720 ui.write("%s: %s" % (" ".join(p), l[1]))
720 ui.write("%s: %s" % (" ".join(p), l[1]))
721
721
722 def archive(ui, repo, dest, **opts):
722 def archive(ui, repo, dest, **opts):
723 '''create unversioned archive of a repository revision
723 '''create unversioned archive of a repository revision
724
724
725 By default, the revision used is the parent of the working
725 By default, the revision used is the parent of the working
726 directory; use "-r" to specify a different revision.
726 directory; use "-r" to specify a different revision.
727
727
728 To specify the type of archive to create, use "-t". Valid
728 To specify the type of archive to create, use "-t". Valid
729 types are:
729 types are:
730
730
731 "files" (default): a directory full of files
731 "files" (default): a directory full of files
732 "tar": tar archive, uncompressed
732 "tar": tar archive, uncompressed
733 "tbz2": tar archive, compressed using bzip2
733 "tbz2": tar archive, compressed using bzip2
734 "tgz": tar archive, compressed using gzip
734 "tgz": tar archive, compressed using gzip
735 "uzip": zip archive, uncompressed
735 "uzip": zip archive, uncompressed
736 "zip": zip archive, compressed using deflate
736 "zip": zip archive, compressed using deflate
737
737
738 The exact name of the destination archive or directory is given
738 The exact name of the destination archive or directory is given
739 using a format string; see "hg help export" for details.
739 using a format string; see "hg help export" for details.
740
740
741 Each member added to an archive file has a directory prefix
741 Each member added to an archive file has a directory prefix
742 prepended. Use "-p" to specify a format string for the prefix.
742 prepended. Use "-p" to specify a format string for the prefix.
743 The default is the basename of the archive, with suffixes removed.
743 The default is the basename of the archive, with suffixes removed.
744 '''
744 '''
745
745
746 if opts['rev']:
746 if opts['rev']:
747 node = repo.lookup(opts['rev'])
747 node = repo.lookup(opts['rev'])
748 else:
748 else:
749 node, p2 = repo.dirstate.parents()
749 node, p2 = repo.dirstate.parents()
750 if p2 != nullid:
750 if p2 != nullid:
751 raise util.Abort(_('uncommitted merge - please provide a '
751 raise util.Abort(_('uncommitted merge - please provide a '
752 'specific revision'))
752 'specific revision'))
753
753
754 dest = cmdutil.make_filename(repo, dest, node)
754 dest = cmdutil.make_filename(repo, dest, node)
755 if os.path.realpath(dest) == repo.root:
755 if os.path.realpath(dest) == repo.root:
756 raise util.Abort(_('repository root cannot be destination'))
756 raise util.Abort(_('repository root cannot be destination'))
757 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
757 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
758 kind = opts.get('type') or 'files'
758 kind = opts.get('type') or 'files'
759 prefix = opts['prefix']
759 prefix = opts['prefix']
760 if dest == '-':
760 if dest == '-':
761 if kind == 'files':
761 if kind == 'files':
762 raise util.Abort(_('cannot archive plain files to stdout'))
762 raise util.Abort(_('cannot archive plain files to stdout'))
763 dest = sys.stdout
763 dest = sys.stdout
764 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
764 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
765 prefix = cmdutil.make_filename(repo, prefix, node)
765 prefix = cmdutil.make_filename(repo, prefix, node)
766 archival.archive(repo, dest, node, kind, not opts['no_decode'],
766 archival.archive(repo, dest, node, kind, not opts['no_decode'],
767 matchfn, prefix)
767 matchfn, prefix)
768
768
769 def backout(ui, repo, rev, **opts):
769 def backout(ui, repo, rev, **opts):
770 '''reverse effect of earlier changeset
770 '''reverse effect of earlier changeset
771
771
772 Commit the backed out changes as a new changeset. The new
772 Commit the backed out changes as a new changeset. The new
773 changeset is a child of the backed out changeset.
773 changeset is a child of the backed out changeset.
774
774
775 If you back out a changeset other than the tip, a new head is
775 If you back out a changeset other than the tip, a new head is
776 created. This head is the parent of the working directory. If
776 created. This head is the parent of the working directory. If
777 you back out an old changeset, your working directory will appear
777 you back out an old changeset, your working directory will appear
778 old after the backout. You should merge the backout changeset
778 old after the backout. You should merge the backout changeset
779 with another head.
779 with another head.
780
780
781 The --merge option remembers the parent of the working directory
781 The --merge option remembers the parent of the working directory
782 before starting the backout, then merges the new head with that
782 before starting the backout, then merges the new head with that
783 changeset afterwards. This saves you from doing the merge by
783 changeset afterwards. This saves you from doing the merge by
784 hand. The result of this merge is not committed, as for a normal
784 hand. The result of this merge is not committed, as for a normal
785 merge.'''
785 merge.'''
786
786
787 bail_if_changed(repo)
787 bail_if_changed(repo)
788 op1, op2 = repo.dirstate.parents()
788 op1, op2 = repo.dirstate.parents()
789 if op2 != nullid:
789 if op2 != nullid:
790 raise util.Abort(_('outstanding uncommitted merge'))
790 raise util.Abort(_('outstanding uncommitted merge'))
791 node = repo.lookup(rev)
791 node = repo.lookup(rev)
792 p1, p2 = repo.changelog.parents(node)
792 p1, p2 = repo.changelog.parents(node)
793 if p1 == nullid:
793 if p1 == nullid:
794 raise util.Abort(_('cannot back out a change with no parents'))
794 raise util.Abort(_('cannot back out a change with no parents'))
795 if p2 != nullid:
795 if p2 != nullid:
796 if not opts['parent']:
796 if not opts['parent']:
797 raise util.Abort(_('cannot back out a merge changeset without '
797 raise util.Abort(_('cannot back out a merge changeset without '
798 '--parent'))
798 '--parent'))
799 p = repo.lookup(opts['parent'])
799 p = repo.lookup(opts['parent'])
800 if p not in (p1, p2):
800 if p not in (p1, p2):
801 raise util.Abort(_('%s is not a parent of %s' %
801 raise util.Abort(_('%s is not a parent of %s' %
802 (short(p), short(node))))
802 (short(p), short(node))))
803 parent = p
803 parent = p
804 else:
804 else:
805 if opts['parent']:
805 if opts['parent']:
806 raise util.Abort(_('cannot use --parent on non-merge changeset'))
806 raise util.Abort(_('cannot use --parent on non-merge changeset'))
807 parent = p1
807 parent = p1
808 hg.clean(repo, node, show_stats=False)
808 hg.clean(repo, node, show_stats=False)
809 revert_opts = opts.copy()
809 revert_opts = opts.copy()
810 revert_opts['rev'] = hex(parent)
810 revert_opts['rev'] = hex(parent)
811 revert(ui, repo, **revert_opts)
811 revert(ui, repo, **revert_opts)
812 commit_opts = opts.copy()
812 commit_opts = opts.copy()
813 commit_opts['addremove'] = False
813 commit_opts['addremove'] = False
814 if not commit_opts['message'] and not commit_opts['logfile']:
814 if not commit_opts['message'] and not commit_opts['logfile']:
815 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
815 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
816 commit_opts['force_editor'] = True
816 commit_opts['force_editor'] = True
817 commit(ui, repo, **commit_opts)
817 commit(ui, repo, **commit_opts)
818 def nice(node):
818 def nice(node):
819 return '%d:%s' % (repo.changelog.rev(node), short(node))
819 return '%d:%s' % (repo.changelog.rev(node), short(node))
820 ui.status(_('changeset %s backs out changeset %s\n') %
820 ui.status(_('changeset %s backs out changeset %s\n') %
821 (nice(repo.changelog.tip()), nice(node)))
821 (nice(repo.changelog.tip()), nice(node)))
822 if op1 != node:
822 if op1 != node:
823 if opts['merge']:
823 if opts['merge']:
824 ui.status(_('merging with changeset %s\n') % nice(op1))
824 ui.status(_('merging with changeset %s\n') % nice(op1))
825 n = _lookup(repo, hex(op1))
825 n = _lookup(repo, hex(op1))
826 hg.merge(repo, n)
826 hg.merge(repo, n)
827 else:
827 else:
828 ui.status(_('the backout changeset is a new head - '
828 ui.status(_('the backout changeset is a new head - '
829 'do not forget to merge\n'))
829 'do not forget to merge\n'))
830 ui.status(_('(use "backout --merge" '
830 ui.status(_('(use "backout --merge" '
831 'if you want to auto-merge)\n'))
831 'if you want to auto-merge)\n'))
832
832
833 def bundle(ui, repo, fname, dest=None, **opts):
833 def bundle(ui, repo, fname, dest=None, **opts):
834 """create a changegroup file
834 """create a changegroup file
835
835
836 Generate a compressed changegroup file collecting all changesets
836 Generate a compressed changegroup file collecting all changesets
837 not found in the other repository.
837 not found in the other repository.
838
838
839 This file can then be transferred using conventional means and
839 This file can then be transferred using conventional means and
840 applied to another repository with the unbundle command. This is
840 applied to another repository with the unbundle command. This is
841 useful when native push and pull are not available or when
841 useful when native push and pull are not available or when
842 exporting an entire repository is undesirable. The standard file
842 exporting an entire repository is undesirable. The standard file
843 extension is ".hg".
843 extension is ".hg".
844
844
845 Unlike import/export, this exactly preserves all changeset
845 Unlike import/export, this exactly preserves all changeset
846 contents including permissions, rename data, and revision history.
846 contents including permissions, rename data, and revision history.
847 """
847 """
848 dest = ui.expandpath(dest or 'default-push', dest or 'default')
848 dest = ui.expandpath(dest or 'default-push', dest or 'default')
849 other = hg.repository(ui, dest)
849 other = hg.repository(ui, dest)
850 o = repo.findoutgoing(other, force=opts['force'])
850 o = repo.findoutgoing(other, force=opts['force'])
851 cg = repo.changegroup(o, 'bundle')
851 cg = repo.changegroup(o, 'bundle')
852 write_bundle(cg, fname)
852 write_bundle(cg, fname)
853
853
854 def cat(ui, repo, file1, *pats, **opts):
854 def cat(ui, repo, file1, *pats, **opts):
855 """output the latest or given revisions of files
855 """output the latest or given revisions of files
856
856
857 Print the specified files as they were at the given revision.
857 Print the specified files as they were at the given revision.
858 If no revision is given then the tip is used.
858 If no revision is given then the tip is used.
859
859
860 Output may be to a file, in which case the name of the file is
860 Output may be to a file, in which case the name of the file is
861 given using a format string. The formatting rules are the same as
861 given using a format string. The formatting rules are the same as
862 for the export command, with the following additions:
862 for the export command, with the following additions:
863
863
864 %s basename of file being printed
864 %s basename of file being printed
865 %d dirname of file being printed, or '.' if in repo root
865 %d dirname of file being printed, or '.' if in repo root
866 %p root-relative path name of file being printed
866 %p root-relative path name of file being printed
867 """
867 """
868 ctx = repo.changectx(opts['rev'] or "-1")
868 ctx = repo.changectx(opts['rev'] or "-1")
869 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
869 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
870 ctx.node()):
870 ctx.node()):
871 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
871 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
872 fp.write(ctx.filectx(abs).data())
872 fp.write(ctx.filectx(abs).data())
873
873
874 def clone(ui, source, dest=None, **opts):
874 def clone(ui, source, dest=None, **opts):
875 """make a copy of an existing repository
875 """make a copy of an existing repository
876
876
877 Create a copy of an existing repository in a new directory.
877 Create a copy of an existing repository in a new directory.
878
878
879 If no destination directory name is specified, it defaults to the
879 If no destination directory name is specified, it defaults to the
880 basename of the source.
880 basename of the source.
881
881
882 The location of the source is added to the new repository's
882 The location of the source is added to the new repository's
883 .hg/hgrc file, as the default to be used for future pulls.
883 .hg/hgrc file, as the default to be used for future pulls.
884
884
885 For efficiency, hardlinks are used for cloning whenever the source
885 For efficiency, hardlinks are used for cloning whenever the source
886 and destination are on the same filesystem (note this applies only
886 and destination are on the same filesystem (note this applies only
887 to the repository data, not to the checked out files). Some
887 to the repository data, not to the checked out files). Some
888 filesystems, such as AFS, implement hardlinking incorrectly, but
888 filesystems, such as AFS, implement hardlinking incorrectly, but
889 do not report errors. In these cases, use the --pull option to
889 do not report errors. In these cases, use the --pull option to
890 avoid hardlinking.
890 avoid hardlinking.
891
891
892 You can safely clone repositories and checked out files using full
892 You can safely clone repositories and checked out files using full
893 hardlinks with
893 hardlinks with
894
894
895 $ cp -al REPO REPOCLONE
895 $ cp -al REPO REPOCLONE
896
896
897 which is the fastest way to clone. However, the operation is not
897 which is the fastest way to clone. However, the operation is not
898 atomic (making sure REPO is not modified during the operation is
898 atomic (making sure REPO is not modified during the operation is
899 up to you) and you have to make sure your editor breaks hardlinks
899 up to you) and you have to make sure your editor breaks hardlinks
900 (Emacs and most Linux Kernel tools do so).
900 (Emacs and most Linux Kernel tools do so).
901
901
902 If you use the -r option to clone up to a specific revision, no
902 If you use the -r option to clone up to a specific revision, no
903 subsequent revisions will be present in the cloned repository.
903 subsequent revisions will be present in the cloned repository.
904 This option implies --pull, even on local repositories.
904 This option implies --pull, even on local repositories.
905
905
906 See pull for valid source format details.
906 See pull for valid source format details.
907
907
908 It is possible to specify an ssh:// URL as the destination, but no
908 It is possible to specify an ssh:// URL as the destination, but no
909 .hg/hgrc will be created on the remote side. Look at the help text
909 .hg/hgrc will be created on the remote side. Look at the help text
910 for the pull command for important details about ssh:// URLs.
910 for the pull command for important details about ssh:// URLs.
911 """
911 """
912 setremoteconfig(ui, opts)
912 setremoteconfig(ui, opts)
913 hg.clone(ui, ui.expandpath(source), dest,
913 hg.clone(ui, ui.expandpath(source), dest,
914 pull=opts['pull'],
914 pull=opts['pull'],
915 stream=opts['uncompressed'],
915 stream=opts['uncompressed'],
916 rev=opts['rev'],
916 rev=opts['rev'],
917 update=not opts['noupdate'])
917 update=not opts['noupdate'])
918
918
919 def commit(ui, repo, *pats, **opts):
919 def commit(ui, repo, *pats, **opts):
920 """commit the specified files or all outstanding changes
920 """commit the specified files or all outstanding changes
921
921
922 Commit changes to the given files into the repository.
922 Commit changes to the given files into the repository.
923
923
924 If a list of files is omitted, all changes reported by "hg status"
924 If a list of files is omitted, all changes reported by "hg status"
925 will be committed.
925 will be committed.
926
926
927 If no commit message is specified, the editor configured in your hgrc
927 If no commit message is specified, the editor configured in your hgrc
928 or in the EDITOR environment variable is started to enter a message.
928 or in the EDITOR environment variable is started to enter a message.
929 """
929 """
930 message = logmessage(opts)
930 message = logmessage(opts)
931
931
932 if opts['addremove']:
932 if opts['addremove']:
933 cmdutil.addremove(repo, pats, opts)
933 cmdutil.addremove(repo, pats, opts)
934 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
934 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
935 if pats:
935 if pats:
936 modified, added, removed = repo.status(files=fns, match=match)[:3]
936 modified, added, removed = repo.status(files=fns, match=match)[:3]
937 files = modified + added + removed
937 files = modified + added + removed
938 else:
938 else:
939 files = []
939 files = []
940 try:
940 try:
941 repo.commit(files, message, opts['user'], opts['date'], match,
941 repo.commit(files, message, opts['user'], opts['date'], match,
942 force_editor=opts.get('force_editor'))
942 force_editor=opts.get('force_editor'))
943 except ValueError, inst:
943 except ValueError, inst:
944 raise util.Abort(str(inst))
944 raise util.Abort(str(inst))
945
945
946 def docopy(ui, repo, pats, opts, wlock):
946 def docopy(ui, repo, pats, opts, wlock):
947 # called with the repo lock held
947 # called with the repo lock held
948 cwd = repo.getcwd()
948 cwd = repo.getcwd()
949 errors = 0
949 errors = 0
950 copied = []
950 copied = []
951 targets = {}
951 targets = {}
952
952
953 def okaytocopy(abs, rel, exact):
953 def okaytocopy(abs, rel, exact):
954 reasons = {'?': _('is not managed'),
954 reasons = {'?': _('is not managed'),
955 'a': _('has been marked for add'),
955 'a': _('has been marked for add'),
956 'r': _('has been marked for remove')}
956 'r': _('has been marked for remove')}
957 state = repo.dirstate.state(abs)
957 state = repo.dirstate.state(abs)
958 reason = reasons.get(state)
958 reason = reasons.get(state)
959 if reason:
959 if reason:
960 if state == 'a':
960 if state == 'a':
961 origsrc = repo.dirstate.copied(abs)
961 origsrc = repo.dirstate.copied(abs)
962 if origsrc is not None:
962 if origsrc is not None:
963 return origsrc
963 return origsrc
964 if exact:
964 if exact:
965 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
965 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
966 else:
966 else:
967 return abs
967 return abs
968
968
969 def copy(origsrc, abssrc, relsrc, target, exact):
969 def copy(origsrc, abssrc, relsrc, target, exact):
970 abstarget = util.canonpath(repo.root, cwd, target)
970 abstarget = util.canonpath(repo.root, cwd, target)
971 reltarget = util.pathto(cwd, abstarget)
971 reltarget = util.pathto(cwd, abstarget)
972 prevsrc = targets.get(abstarget)
972 prevsrc = targets.get(abstarget)
973 if prevsrc is not None:
973 if prevsrc is not None:
974 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
974 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
975 (reltarget, abssrc, prevsrc))
975 (reltarget, abssrc, prevsrc))
976 return
976 return
977 if (not opts['after'] and os.path.exists(reltarget) or
977 if (not opts['after'] and os.path.exists(reltarget) or
978 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
978 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
979 if not opts['force']:
979 if not opts['force']:
980 ui.warn(_('%s: not overwriting - file exists\n') %
980 ui.warn(_('%s: not overwriting - file exists\n') %
981 reltarget)
981 reltarget)
982 return
982 return
983 if not opts['after'] and not opts.get('dry_run'):
983 if not opts['after'] and not opts.get('dry_run'):
984 os.unlink(reltarget)
984 os.unlink(reltarget)
985 if opts['after']:
985 if opts['after']:
986 if not os.path.exists(reltarget):
986 if not os.path.exists(reltarget):
987 return
987 return
988 else:
988 else:
989 targetdir = os.path.dirname(reltarget) or '.'
989 targetdir = os.path.dirname(reltarget) or '.'
990 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
990 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
991 os.makedirs(targetdir)
991 os.makedirs(targetdir)
992 try:
992 try:
993 restore = repo.dirstate.state(abstarget) == 'r'
993 restore = repo.dirstate.state(abstarget) == 'r'
994 if restore and not opts.get('dry_run'):
994 if restore and not opts.get('dry_run'):
995 repo.undelete([abstarget], wlock)
995 repo.undelete([abstarget], wlock)
996 try:
996 try:
997 if not opts.get('dry_run'):
997 if not opts.get('dry_run'):
998 shutil.copyfile(relsrc, reltarget)
998 shutil.copyfile(relsrc, reltarget)
999 shutil.copymode(relsrc, reltarget)
999 shutil.copymode(relsrc, reltarget)
1000 restore = False
1000 restore = False
1001 finally:
1001 finally:
1002 if restore:
1002 if restore:
1003 repo.remove([abstarget], wlock)
1003 repo.remove([abstarget], wlock)
1004 except shutil.Error, inst:
1004 except shutil.Error, inst:
1005 raise util.Abort(str(inst))
1005 raise util.Abort(str(inst))
1006 except IOError, inst:
1006 except IOError, inst:
1007 if inst.errno == errno.ENOENT:
1007 if inst.errno == errno.ENOENT:
1008 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1008 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1009 else:
1009 else:
1010 ui.warn(_('%s: cannot copy - %s\n') %
1010 ui.warn(_('%s: cannot copy - %s\n') %
1011 (relsrc, inst.strerror))
1011 (relsrc, inst.strerror))
1012 errors += 1
1012 errors += 1
1013 return
1013 return
1014 if ui.verbose or not exact:
1014 if ui.verbose or not exact:
1015 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1015 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1016 targets[abstarget] = abssrc
1016 targets[abstarget] = abssrc
1017 if abstarget != origsrc and not opts.get('dry_run'):
1017 if abstarget != origsrc and not opts.get('dry_run'):
1018 repo.copy(origsrc, abstarget, wlock)
1018 repo.copy(origsrc, abstarget, wlock)
1019 copied.append((abssrc, relsrc, exact))
1019 copied.append((abssrc, relsrc, exact))
1020
1020
1021 def targetpathfn(pat, dest, srcs):
1021 def targetpathfn(pat, dest, srcs):
1022 if os.path.isdir(pat):
1022 if os.path.isdir(pat):
1023 abspfx = util.canonpath(repo.root, cwd, pat)
1023 abspfx = util.canonpath(repo.root, cwd, pat)
1024 if destdirexists:
1024 if destdirexists:
1025 striplen = len(os.path.split(abspfx)[0])
1025 striplen = len(os.path.split(abspfx)[0])
1026 else:
1026 else:
1027 striplen = len(abspfx)
1027 striplen = len(abspfx)
1028 if striplen:
1028 if striplen:
1029 striplen += len(os.sep)
1029 striplen += len(os.sep)
1030 res = lambda p: os.path.join(dest, p[striplen:])
1030 res = lambda p: os.path.join(dest, p[striplen:])
1031 elif destdirexists:
1031 elif destdirexists:
1032 res = lambda p: os.path.join(dest, os.path.basename(p))
1032 res = lambda p: os.path.join(dest, os.path.basename(p))
1033 else:
1033 else:
1034 res = lambda p: dest
1034 res = lambda p: dest
1035 return res
1035 return res
1036
1036
1037 def targetpathafterfn(pat, dest, srcs):
1037 def targetpathafterfn(pat, dest, srcs):
1038 if util.patkind(pat, None)[0]:
1038 if util.patkind(pat, None)[0]:
1039 # a mercurial pattern
1039 # a mercurial pattern
1040 res = lambda p: os.path.join(dest, os.path.basename(p))
1040 res = lambda p: os.path.join(dest, os.path.basename(p))
1041 else:
1041 else:
1042 abspfx = util.canonpath(repo.root, cwd, pat)
1042 abspfx = util.canonpath(repo.root, cwd, pat)
1043 if len(abspfx) < len(srcs[0][0]):
1043 if len(abspfx) < len(srcs[0][0]):
1044 # A directory. Either the target path contains the last
1044 # A directory. Either the target path contains the last
1045 # component of the source path or it does not.
1045 # component of the source path or it does not.
1046 def evalpath(striplen):
1046 def evalpath(striplen):
1047 score = 0
1047 score = 0
1048 for s in srcs:
1048 for s in srcs:
1049 t = os.path.join(dest, s[0][striplen:])
1049 t = os.path.join(dest, s[0][striplen:])
1050 if os.path.exists(t):
1050 if os.path.exists(t):
1051 score += 1
1051 score += 1
1052 return score
1052 return score
1053
1053
1054 striplen = len(abspfx)
1054 striplen = len(abspfx)
1055 if striplen:
1055 if striplen:
1056 striplen += len(os.sep)
1056 striplen += len(os.sep)
1057 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1057 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1058 score = evalpath(striplen)
1058 score = evalpath(striplen)
1059 striplen1 = len(os.path.split(abspfx)[0])
1059 striplen1 = len(os.path.split(abspfx)[0])
1060 if striplen1:
1060 if striplen1:
1061 striplen1 += len(os.sep)
1061 striplen1 += len(os.sep)
1062 if evalpath(striplen1) > score:
1062 if evalpath(striplen1) > score:
1063 striplen = striplen1
1063 striplen = striplen1
1064 res = lambda p: os.path.join(dest, p[striplen:])
1064 res = lambda p: os.path.join(dest, p[striplen:])
1065 else:
1065 else:
1066 # a file
1066 # a file
1067 if destdirexists:
1067 if destdirexists:
1068 res = lambda p: os.path.join(dest, os.path.basename(p))
1068 res = lambda p: os.path.join(dest, os.path.basename(p))
1069 else:
1069 else:
1070 res = lambda p: dest
1070 res = lambda p: dest
1071 return res
1071 return res
1072
1072
1073
1073
1074 pats = list(pats)
1074 pats = list(pats)
1075 if not pats:
1075 if not pats:
1076 raise util.Abort(_('no source or destination specified'))
1076 raise util.Abort(_('no source or destination specified'))
1077 if len(pats) == 1:
1077 if len(pats) == 1:
1078 raise util.Abort(_('no destination specified'))
1078 raise util.Abort(_('no destination specified'))
1079 dest = pats.pop()
1079 dest = pats.pop()
1080 destdirexists = os.path.isdir(dest)
1080 destdirexists = os.path.isdir(dest)
1081 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1081 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1082 raise util.Abort(_('with multiple sources, destination must be an '
1082 raise util.Abort(_('with multiple sources, destination must be an '
1083 'existing directory'))
1083 'existing directory'))
1084 if opts['after']:
1084 if opts['after']:
1085 tfn = targetpathafterfn
1085 tfn = targetpathafterfn
1086 else:
1086 else:
1087 tfn = targetpathfn
1087 tfn = targetpathfn
1088 copylist = []
1088 copylist = []
1089 for pat in pats:
1089 for pat in pats:
1090 srcs = []
1090 srcs = []
1091 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts):
1091 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts):
1092 origsrc = okaytocopy(abssrc, relsrc, exact)
1092 origsrc = okaytocopy(abssrc, relsrc, exact)
1093 if origsrc:
1093 if origsrc:
1094 srcs.append((origsrc, abssrc, relsrc, exact))
1094 srcs.append((origsrc, abssrc, relsrc, exact))
1095 if not srcs:
1095 if not srcs:
1096 continue
1096 continue
1097 copylist.append((tfn(pat, dest, srcs), srcs))
1097 copylist.append((tfn(pat, dest, srcs), srcs))
1098 if not copylist:
1098 if not copylist:
1099 raise util.Abort(_('no files to copy'))
1099 raise util.Abort(_('no files to copy'))
1100
1100
1101 for targetpath, srcs in copylist:
1101 for targetpath, srcs in copylist:
1102 for origsrc, abssrc, relsrc, exact in srcs:
1102 for origsrc, abssrc, relsrc, exact in srcs:
1103 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1103 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1104
1104
1105 if errors:
1105 if errors:
1106 ui.warn(_('(consider using --after)\n'))
1106 ui.warn(_('(consider using --after)\n'))
1107 return errors, copied
1107 return errors, copied
1108
1108
1109 def copy(ui, repo, *pats, **opts):
1109 def copy(ui, repo, *pats, **opts):
1110 """mark files as copied for the next commit
1110 """mark files as copied for the next commit
1111
1111
1112 Mark dest as having copies of source files. If dest is a
1112 Mark dest as having copies of source files. If dest is a
1113 directory, copies are put in that directory. If dest is a file,
1113 directory, copies are put in that directory. If dest is a file,
1114 there can only be one source.
1114 there can only be one source.
1115
1115
1116 By default, this command copies the contents of files as they
1116 By default, this command copies the contents of files as they
1117 stand in the working directory. If invoked with --after, the
1117 stand in the working directory. If invoked with --after, the
1118 operation is recorded, but no copying is performed.
1118 operation is recorded, but no copying is performed.
1119
1119
1120 This command takes effect in the next commit.
1120 This command takes effect in the next commit.
1121
1121
1122 NOTE: This command should be treated as experimental. While it
1122 NOTE: This command should be treated as experimental. While it
1123 should properly record copied files, this information is not yet
1123 should properly record copied files, this information is not yet
1124 fully used by merge, nor fully reported by log.
1124 fully used by merge, nor fully reported by log.
1125 """
1125 """
1126 wlock = repo.wlock(0)
1126 wlock = repo.wlock(0)
1127 errs, copied = docopy(ui, repo, pats, opts, wlock)
1127 errs, copied = docopy(ui, repo, pats, opts, wlock)
1128 return errs
1128 return errs
1129
1129
1130 def debugancestor(ui, index, rev1, rev2):
1130 def debugancestor(ui, index, rev1, rev2):
1131 """find the ancestor revision of two revisions in a given index"""
1131 """find the ancestor revision of two revisions in a given index"""
1132 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1132 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1133 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1133 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1134 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1134 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1135
1135
1136 def debugcomplete(ui, cmd='', **opts):
1136 def debugcomplete(ui, cmd='', **opts):
1137 """returns the completion list associated with the given command"""
1137 """returns the completion list associated with the given command"""
1138
1138
1139 if opts['options']:
1139 if opts['options']:
1140 options = []
1140 options = []
1141 otables = [globalopts]
1141 otables = [globalopts]
1142 if cmd:
1142 if cmd:
1143 aliases, entry = findcmd(cmd)
1143 aliases, entry = findcmd(cmd)
1144 otables.append(entry[1])
1144 otables.append(entry[1])
1145 for t in otables:
1145 for t in otables:
1146 for o in t:
1146 for o in t:
1147 if o[0]:
1147 if o[0]:
1148 options.append('-%s' % o[0])
1148 options.append('-%s' % o[0])
1149 options.append('--%s' % o[1])
1149 options.append('--%s' % o[1])
1150 ui.write("%s\n" % "\n".join(options))
1150 ui.write("%s\n" % "\n".join(options))
1151 return
1151 return
1152
1152
1153 clist = findpossible(cmd).keys()
1153 clist = findpossible(cmd).keys()
1154 clist.sort()
1154 clist.sort()
1155 ui.write("%s\n" % "\n".join(clist))
1155 ui.write("%s\n" % "\n".join(clist))
1156
1156
1157 def debugrebuildstate(ui, repo, rev=None):
1157 def debugrebuildstate(ui, repo, rev=None):
1158 """rebuild the dirstate as it would look like for the given revision"""
1158 """rebuild the dirstate as it would look like for the given revision"""
1159 if not rev:
1159 if not rev:
1160 rev = repo.changelog.tip()
1160 rev = repo.changelog.tip()
1161 else:
1161 else:
1162 rev = repo.lookup(rev)
1162 rev = repo.lookup(rev)
1163 change = repo.changelog.read(rev)
1163 change = repo.changelog.read(rev)
1164 n = change[0]
1164 n = change[0]
1165 files = repo.manifest.read(n)
1165 files = repo.manifest.read(n)
1166 wlock = repo.wlock()
1166 wlock = repo.wlock()
1167 repo.dirstate.rebuild(rev, files)
1167 repo.dirstate.rebuild(rev, files)
1168
1168
1169 def debugcheckstate(ui, repo):
1169 def debugcheckstate(ui, repo):
1170 """validate the correctness of the current dirstate"""
1170 """validate the correctness of the current dirstate"""
1171 parent1, parent2 = repo.dirstate.parents()
1171 parent1, parent2 = repo.dirstate.parents()
1172 repo.dirstate.read()
1172 repo.dirstate.read()
1173 dc = repo.dirstate.map
1173 dc = repo.dirstate.map
1174 keys = dc.keys()
1174 keys = dc.keys()
1175 keys.sort()
1175 keys.sort()
1176 m1n = repo.changelog.read(parent1)[0]
1176 m1n = repo.changelog.read(parent1)[0]
1177 m2n = repo.changelog.read(parent2)[0]
1177 m2n = repo.changelog.read(parent2)[0]
1178 m1 = repo.manifest.read(m1n)
1178 m1 = repo.manifest.read(m1n)
1179 m2 = repo.manifest.read(m2n)
1179 m2 = repo.manifest.read(m2n)
1180 errors = 0
1180 errors = 0
1181 for f in dc:
1181 for f in dc:
1182 state = repo.dirstate.state(f)
1182 state = repo.dirstate.state(f)
1183 if state in "nr" and f not in m1:
1183 if state in "nr" and f not in m1:
1184 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1184 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1185 errors += 1
1185 errors += 1
1186 if state in "a" and f in m1:
1186 if state in "a" and f in m1:
1187 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1187 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1188 errors += 1
1188 errors += 1
1189 if state in "m" and f not in m1 and f not in m2:
1189 if state in "m" and f not in m1 and f not in m2:
1190 ui.warn(_("%s in state %s, but not in either manifest\n") %
1190 ui.warn(_("%s in state %s, but not in either manifest\n") %
1191 (f, state))
1191 (f, state))
1192 errors += 1
1192 errors += 1
1193 for f in m1:
1193 for f in m1:
1194 state = repo.dirstate.state(f)
1194 state = repo.dirstate.state(f)
1195 if state not in "nrm":
1195 if state not in "nrm":
1196 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1196 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1197 errors += 1
1197 errors += 1
1198 if errors:
1198 if errors:
1199 error = _(".hg/dirstate inconsistent with current parent's manifest")
1199 error = _(".hg/dirstate inconsistent with current parent's manifest")
1200 raise util.Abort(error)
1200 raise util.Abort(error)
1201
1201
1202 def debugconfig(ui, repo, *values):
1202 def debugconfig(ui, repo, *values):
1203 """show combined config settings from all hgrc files
1203 """show combined config settings from all hgrc files
1204
1204
1205 With no args, print names and values of all config items.
1205 With no args, print names and values of all config items.
1206
1206
1207 With one arg of the form section.name, print just the value of
1207 With one arg of the form section.name, print just the value of
1208 that config item.
1208 that config item.
1209
1209
1210 With multiple args, print names and values of all config items
1210 With multiple args, print names and values of all config items
1211 with matching section names."""
1211 with matching section names."""
1212
1212
1213 if values:
1213 if values:
1214 if len([v for v in values if '.' in v]) > 1:
1214 if len([v for v in values if '.' in v]) > 1:
1215 raise util.Abort(_('only one config item permitted'))
1215 raise util.Abort(_('only one config item permitted'))
1216 for section, name, value in ui.walkconfig():
1216 for section, name, value in ui.walkconfig():
1217 sectname = section + '.' + name
1217 sectname = section + '.' + name
1218 if values:
1218 if values:
1219 for v in values:
1219 for v in values:
1220 if v == section:
1220 if v == section:
1221 ui.write('%s=%s\n' % (sectname, value))
1221 ui.write('%s=%s\n' % (sectname, value))
1222 elif v == sectname:
1222 elif v == sectname:
1223 ui.write(value, '\n')
1223 ui.write(value, '\n')
1224 else:
1224 else:
1225 ui.write('%s=%s\n' % (sectname, value))
1225 ui.write('%s=%s\n' % (sectname, value))
1226
1226
1227 def debugsetparents(ui, repo, rev1, rev2=None):
1227 def debugsetparents(ui, repo, rev1, rev2=None):
1228 """manually set the parents of the current working directory
1228 """manually set the parents of the current working directory
1229
1229
1230 This is useful for writing repository conversion tools, but should
1230 This is useful for writing repository conversion tools, but should
1231 be used with care.
1231 be used with care.
1232 """
1232 """
1233
1233
1234 if not rev2:
1234 if not rev2:
1235 rev2 = hex(nullid)
1235 rev2 = hex(nullid)
1236
1236
1237 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1237 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1238
1238
1239 def debugstate(ui, repo):
1239 def debugstate(ui, repo):
1240 """show the contents of the current dirstate"""
1240 """show the contents of the current dirstate"""
1241 repo.dirstate.read()
1241 repo.dirstate.read()
1242 dc = repo.dirstate.map
1242 dc = repo.dirstate.map
1243 keys = dc.keys()
1243 keys = dc.keys()
1244 keys.sort()
1244 keys.sort()
1245 for file_ in keys:
1245 for file_ in keys:
1246 ui.write("%c %3o %10d %s %s\n"
1246 ui.write("%c %3o %10d %s %s\n"
1247 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1247 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1248 time.strftime("%x %X",
1248 time.strftime("%x %X",
1249 time.localtime(dc[file_][3])), file_))
1249 time.localtime(dc[file_][3])), file_))
1250 for f in repo.dirstate.copies:
1250 for f in repo.dirstate.copies:
1251 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1251 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1252
1252
1253 def debugdata(ui, file_, rev):
1253 def debugdata(ui, file_, rev):
1254 """dump the contents of an data file revision"""
1254 """dump the contents of an data file revision"""
1255 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1255 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1256 file_[:-2] + ".i", file_, 0)
1256 file_[:-2] + ".i", file_, 0)
1257 try:
1257 try:
1258 ui.write(r.revision(r.lookup(rev)))
1258 ui.write(r.revision(r.lookup(rev)))
1259 except KeyError:
1259 except KeyError:
1260 raise util.Abort(_('invalid revision identifier %s'), rev)
1260 raise util.Abort(_('invalid revision identifier %s'), rev)
1261
1261
1262 def debugindex(ui, file_):
1262 def debugindex(ui, file_):
1263 """dump the contents of an index file"""
1263 """dump the contents of an index file"""
1264 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1264 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1265 ui.write(" rev offset length base linkrev" +
1265 ui.write(" rev offset length base linkrev" +
1266 " nodeid p1 p2\n")
1266 " nodeid p1 p2\n")
1267 for i in range(r.count()):
1267 for i in range(r.count()):
1268 node = r.node(i)
1268 node = r.node(i)
1269 pp = r.parents(node)
1269 pp = r.parents(node)
1270 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1270 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1271 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1271 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1272 short(node), short(pp[0]), short(pp[1])))
1272 short(node), short(pp[0]), short(pp[1])))
1273
1273
1274 def debugindexdot(ui, file_):
1274 def debugindexdot(ui, file_):
1275 """dump an index DAG as a .dot file"""
1275 """dump an index DAG as a .dot file"""
1276 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1276 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1277 ui.write("digraph G {\n")
1277 ui.write("digraph G {\n")
1278 for i in range(r.count()):
1278 for i in range(r.count()):
1279 node = r.node(i)
1279 node = r.node(i)
1280 pp = r.parents(node)
1280 pp = r.parents(node)
1281 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1281 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1282 if pp[1] != nullid:
1282 if pp[1] != nullid:
1283 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1283 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1284 ui.write("}\n")
1284 ui.write("}\n")
1285
1285
1286 def debugrename(ui, repo, file, rev=None):
1286 def debugrename(ui, repo, file, rev=None):
1287 """dump rename information"""
1287 """dump rename information"""
1288 r = repo.file(relpath(repo, [file])[0])
1288 r = repo.file(relpath(repo, [file])[0])
1289 if rev:
1289 if rev:
1290 try:
1290 try:
1291 # assume all revision numbers are for changesets
1291 # assume all revision numbers are for changesets
1292 n = repo.lookup(rev)
1292 n = repo.lookup(rev)
1293 change = repo.changelog.read(n)
1293 change = repo.changelog.read(n)
1294 m = repo.manifest.read(change[0])
1294 m = repo.manifest.read(change[0])
1295 n = m[relpath(repo, [file])[0]]
1295 n = m[relpath(repo, [file])[0]]
1296 except (hg.RepoError, KeyError):
1296 except (hg.RepoError, KeyError):
1297 n = r.lookup(rev)
1297 n = r.lookup(rev)
1298 else:
1298 else:
1299 n = r.tip()
1299 n = r.tip()
1300 m = r.renamed(n)
1300 m = r.renamed(n)
1301 if m:
1301 if m:
1302 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1302 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1303 else:
1303 else:
1304 ui.write(_("not renamed\n"))
1304 ui.write(_("not renamed\n"))
1305
1305
1306 def debugwalk(ui, repo, *pats, **opts):
1306 def debugwalk(ui, repo, *pats, **opts):
1307 """show how files match on given patterns"""
1307 """show how files match on given patterns"""
1308 items = list(cmdutil.walk(repo, pats, opts))
1308 items = list(cmdutil.walk(repo, pats, opts))
1309 if not items:
1309 if not items:
1310 return
1310 return
1311 fmt = '%%s %%-%ds %%-%ds %%s' % (
1311 fmt = '%%s %%-%ds %%-%ds %%s' % (
1312 max([len(abs) for (src, abs, rel, exact) in items]),
1312 max([len(abs) for (src, abs, rel, exact) in items]),
1313 max([len(rel) for (src, abs, rel, exact) in items]))
1313 max([len(rel) for (src, abs, rel, exact) in items]))
1314 for src, abs, rel, exact in items:
1314 for src, abs, rel, exact in items:
1315 line = fmt % (src, abs, rel, exact and 'exact' or '')
1315 line = fmt % (src, abs, rel, exact and 'exact' or '')
1316 ui.write("%s\n" % line.rstrip())
1316 ui.write("%s\n" % line.rstrip())
1317
1317
1318 def diff(ui, repo, *pats, **opts):
1318 def diff(ui, repo, *pats, **opts):
1319 """diff repository (or selected files)
1319 """diff repository (or selected files)
1320
1320
1321 Show differences between revisions for the specified files.
1321 Show differences between revisions for the specified files.
1322
1322
1323 Differences between files are shown using the unified diff format.
1323 Differences between files are shown using the unified diff format.
1324
1324
1325 When two revision arguments are given, then changes are shown
1325 When two revision arguments are given, then changes are shown
1326 between those revisions. If only one revision is specified then
1326 between those revisions. If only one revision is specified then
1327 that revision is compared to the working directory, and, when no
1327 that revision is compared to the working directory, and, when no
1328 revisions are specified, the working directory files are compared
1328 revisions are specified, the working directory files are compared
1329 to its parent.
1329 to its parent.
1330
1330
1331 Without the -a option, diff will avoid generating diffs of files
1331 Without the -a option, diff will avoid generating diffs of files
1332 it detects as binary. With -a, diff will generate a diff anyway,
1332 it detects as binary. With -a, diff will generate a diff anyway,
1333 probably with undesirable results.
1333 probably with undesirable results.
1334 """
1334 """
1335 node1, node2 = revpair(ui, repo, opts['rev'])
1335 node1, node2 = revpair(ui, repo, opts['rev'])
1336
1336
1337 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1337 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1338
1338
1339 patch.diff(repo, node1, node2, fns, match=matchfn,
1339 patch.diff(repo, node1, node2, fns, match=matchfn,
1340 opts=ui.diffopts(opts))
1340 opts=patch.diffopts(ui, opts))
1341
1341
1342 def export(ui, repo, *changesets, **opts):
1342 def export(ui, repo, *changesets, **opts):
1343 """dump the header and diffs for one or more changesets
1343 """dump the header and diffs for one or more changesets
1344
1344
1345 Print the changeset header and diffs for one or more revisions.
1345 Print the changeset header and diffs for one or more revisions.
1346
1346
1347 The information shown in the changeset header is: author,
1347 The information shown in the changeset header is: author,
1348 changeset hash, parent and commit comment.
1348 changeset hash, parent and commit comment.
1349
1349
1350 Output may be to a file, in which case the name of the file is
1350 Output may be to a file, in which case the name of the file is
1351 given using a format string. The formatting rules are as follows:
1351 given using a format string. The formatting rules are as follows:
1352
1352
1353 %% literal "%" character
1353 %% literal "%" character
1354 %H changeset hash (40 bytes of hexadecimal)
1354 %H changeset hash (40 bytes of hexadecimal)
1355 %N number of patches being generated
1355 %N number of patches being generated
1356 %R changeset revision number
1356 %R changeset revision number
1357 %b basename of the exporting repository
1357 %b basename of the exporting repository
1358 %h short-form changeset hash (12 bytes of hexadecimal)
1358 %h short-form changeset hash (12 bytes of hexadecimal)
1359 %n zero-padded sequence number, starting at 1
1359 %n zero-padded sequence number, starting at 1
1360 %r zero-padded changeset revision number
1360 %r zero-padded changeset revision number
1361
1361
1362 Without the -a option, export will avoid generating diffs of files
1362 Without the -a option, export will avoid generating diffs of files
1363 it detects as binary. With -a, export will generate a diff anyway,
1363 it detects as binary. With -a, export will generate a diff anyway,
1364 probably with undesirable results.
1364 probably with undesirable results.
1365
1365
1366 With the --switch-parent option, the diff will be against the second
1366 With the --switch-parent option, the diff will be against the second
1367 parent. It can be useful to review a merge.
1367 parent. It can be useful to review a merge.
1368 """
1368 """
1369 if not changesets:
1369 if not changesets:
1370 raise util.Abort(_("export requires at least one changeset"))
1370 raise util.Abort(_("export requires at least one changeset"))
1371 revs = list(revrange(ui, repo, changesets))
1371 revs = list(revrange(ui, repo, changesets))
1372 if len(revs) > 1:
1372 if len(revs) > 1:
1373 ui.note(_('exporting patches:\n'))
1373 ui.note(_('exporting patches:\n'))
1374 else:
1374 else:
1375 ui.note(_('exporting patch:\n'))
1375 ui.note(_('exporting patch:\n'))
1376 patch.export(repo, map(repo.lookup, revs), template=opts['output'],
1376 patch.export(repo, map(repo.lookup, revs), template=opts['output'],
1377 switch_parent=opts['switch_parent'], opts=ui.diffopts(opts))
1377 switch_parent=opts['switch_parent'],
1378 opts=patch.diffopts(ui, opts))
1378
1379
1379 def forget(ui, repo, *pats, **opts):
1380 def forget(ui, repo, *pats, **opts):
1380 """don't add the specified files on the next commit (DEPRECATED)
1381 """don't add the specified files on the next commit (DEPRECATED)
1381
1382
1382 (DEPRECATED)
1383 (DEPRECATED)
1383 Undo an 'hg add' scheduled for the next commit.
1384 Undo an 'hg add' scheduled for the next commit.
1384
1385
1385 This command is now deprecated and will be removed in a future
1386 This command is now deprecated and will be removed in a future
1386 release. Please use revert instead.
1387 release. Please use revert instead.
1387 """
1388 """
1388 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1389 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1389 forget = []
1390 forget = []
1390 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
1391 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
1391 if repo.dirstate.state(abs) == 'a':
1392 if repo.dirstate.state(abs) == 'a':
1392 forget.append(abs)
1393 forget.append(abs)
1393 if ui.verbose or not exact:
1394 if ui.verbose or not exact:
1394 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1395 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1395 repo.forget(forget)
1396 repo.forget(forget)
1396
1397
1397 def grep(ui, repo, pattern, *pats, **opts):
1398 def grep(ui, repo, pattern, *pats, **opts):
1398 """search for a pattern in specified files and revisions
1399 """search for a pattern in specified files and revisions
1399
1400
1400 Search revisions of files for a regular expression.
1401 Search revisions of files for a regular expression.
1401
1402
1402 This command behaves differently than Unix grep. It only accepts
1403 This command behaves differently than Unix grep. It only accepts
1403 Python/Perl regexps. It searches repository history, not the
1404 Python/Perl regexps. It searches repository history, not the
1404 working directory. It always prints the revision number in which
1405 working directory. It always prints the revision number in which
1405 a match appears.
1406 a match appears.
1406
1407
1407 By default, grep only prints output for the first revision of a
1408 By default, grep only prints output for the first revision of a
1408 file in which it finds a match. To get it to print every revision
1409 file in which it finds a match. To get it to print every revision
1409 that contains a change in match status ("-" for a match that
1410 that contains a change in match status ("-" for a match that
1410 becomes a non-match, or "+" for a non-match that becomes a match),
1411 becomes a non-match, or "+" for a non-match that becomes a match),
1411 use the --all flag.
1412 use the --all flag.
1412 """
1413 """
1413 reflags = 0
1414 reflags = 0
1414 if opts['ignore_case']:
1415 if opts['ignore_case']:
1415 reflags |= re.I
1416 reflags |= re.I
1416 regexp = re.compile(pattern, reflags)
1417 regexp = re.compile(pattern, reflags)
1417 sep, eol = ':', '\n'
1418 sep, eol = ':', '\n'
1418 if opts['print0']:
1419 if opts['print0']:
1419 sep = eol = '\0'
1420 sep = eol = '\0'
1420
1421
1421 fcache = {}
1422 fcache = {}
1422 def getfile(fn):
1423 def getfile(fn):
1423 if fn not in fcache:
1424 if fn not in fcache:
1424 fcache[fn] = repo.file(fn)
1425 fcache[fn] = repo.file(fn)
1425 return fcache[fn]
1426 return fcache[fn]
1426
1427
1427 def matchlines(body):
1428 def matchlines(body):
1428 begin = 0
1429 begin = 0
1429 linenum = 0
1430 linenum = 0
1430 while True:
1431 while True:
1431 match = regexp.search(body, begin)
1432 match = regexp.search(body, begin)
1432 if not match:
1433 if not match:
1433 break
1434 break
1434 mstart, mend = match.span()
1435 mstart, mend = match.span()
1435 linenum += body.count('\n', begin, mstart) + 1
1436 linenum += body.count('\n', begin, mstart) + 1
1436 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1437 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1437 lend = body.find('\n', mend)
1438 lend = body.find('\n', mend)
1438 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1439 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1439 begin = lend + 1
1440 begin = lend + 1
1440
1441
1441 class linestate(object):
1442 class linestate(object):
1442 def __init__(self, line, linenum, colstart, colend):
1443 def __init__(self, line, linenum, colstart, colend):
1443 self.line = line
1444 self.line = line
1444 self.linenum = linenum
1445 self.linenum = linenum
1445 self.colstart = colstart
1446 self.colstart = colstart
1446 self.colend = colend
1447 self.colend = colend
1447
1448
1448 def __eq__(self, other):
1449 def __eq__(self, other):
1449 return self.line == other.line
1450 return self.line == other.line
1450
1451
1451 matches = {}
1452 matches = {}
1452 copies = {}
1453 copies = {}
1453 def grepbody(fn, rev, body):
1454 def grepbody(fn, rev, body):
1454 matches[rev].setdefault(fn, [])
1455 matches[rev].setdefault(fn, [])
1455 m = matches[rev][fn]
1456 m = matches[rev][fn]
1456 for lnum, cstart, cend, line in matchlines(body):
1457 for lnum, cstart, cend, line in matchlines(body):
1457 s = linestate(line, lnum, cstart, cend)
1458 s = linestate(line, lnum, cstart, cend)
1458 m.append(s)
1459 m.append(s)
1459
1460
1460 def difflinestates(a, b):
1461 def difflinestates(a, b):
1461 sm = difflib.SequenceMatcher(None, a, b)
1462 sm = difflib.SequenceMatcher(None, a, b)
1462 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1463 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1463 if tag == 'insert':
1464 if tag == 'insert':
1464 for i in range(blo, bhi):
1465 for i in range(blo, bhi):
1465 yield ('+', b[i])
1466 yield ('+', b[i])
1466 elif tag == 'delete':
1467 elif tag == 'delete':
1467 for i in range(alo, ahi):
1468 for i in range(alo, ahi):
1468 yield ('-', a[i])
1469 yield ('-', a[i])
1469 elif tag == 'replace':
1470 elif tag == 'replace':
1470 for i in range(alo, ahi):
1471 for i in range(alo, ahi):
1471 yield ('-', a[i])
1472 yield ('-', a[i])
1472 for i in range(blo, bhi):
1473 for i in range(blo, bhi):
1473 yield ('+', b[i])
1474 yield ('+', b[i])
1474
1475
1475 prev = {}
1476 prev = {}
1476 ucache = {}
1477 ucache = {}
1477 def display(fn, rev, states, prevstates):
1478 def display(fn, rev, states, prevstates):
1478 counts = {'-': 0, '+': 0}
1479 counts = {'-': 0, '+': 0}
1479 filerevmatches = {}
1480 filerevmatches = {}
1480 if incrementing or not opts['all']:
1481 if incrementing or not opts['all']:
1481 a, b = prevstates, states
1482 a, b = prevstates, states
1482 else:
1483 else:
1483 a, b = states, prevstates
1484 a, b = states, prevstates
1484 for change, l in difflinestates(a, b):
1485 for change, l in difflinestates(a, b):
1485 if incrementing or not opts['all']:
1486 if incrementing or not opts['all']:
1486 r = rev
1487 r = rev
1487 else:
1488 else:
1488 r = prev[fn]
1489 r = prev[fn]
1489 cols = [fn, str(r)]
1490 cols = [fn, str(r)]
1490 if opts['line_number']:
1491 if opts['line_number']:
1491 cols.append(str(l.linenum))
1492 cols.append(str(l.linenum))
1492 if opts['all']:
1493 if opts['all']:
1493 cols.append(change)
1494 cols.append(change)
1494 if opts['user']:
1495 if opts['user']:
1495 cols.append(trimuser(ui, getchange(r)[1], rev,
1496 cols.append(trimuser(ui, getchange(r)[1], rev,
1496 ucache))
1497 ucache))
1497 if opts['files_with_matches']:
1498 if opts['files_with_matches']:
1498 c = (fn, rev)
1499 c = (fn, rev)
1499 if c in filerevmatches:
1500 if c in filerevmatches:
1500 continue
1501 continue
1501 filerevmatches[c] = 1
1502 filerevmatches[c] = 1
1502 else:
1503 else:
1503 cols.append(l.line)
1504 cols.append(l.line)
1504 ui.write(sep.join(cols), eol)
1505 ui.write(sep.join(cols), eol)
1505 counts[change] += 1
1506 counts[change] += 1
1506 return counts['+'], counts['-']
1507 return counts['+'], counts['-']
1507
1508
1508 fstate = {}
1509 fstate = {}
1509 skip = {}
1510 skip = {}
1510 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1511 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1511 count = 0
1512 count = 0
1512 incrementing = False
1513 incrementing = False
1513 follow = opts.get('follow')
1514 follow = opts.get('follow')
1514 for st, rev, fns in changeiter:
1515 for st, rev, fns in changeiter:
1515 if st == 'window':
1516 if st == 'window':
1516 incrementing = rev
1517 incrementing = rev
1517 matches.clear()
1518 matches.clear()
1518 copies.clear()
1519 copies.clear()
1519 elif st == 'add':
1520 elif st == 'add':
1520 change = repo.changelog.read(repo.lookup(str(rev)))
1521 change = repo.changelog.read(repo.lookup(str(rev)))
1521 mf = repo.manifest.read(change[0])
1522 mf = repo.manifest.read(change[0])
1522 matches[rev] = {}
1523 matches[rev] = {}
1523 for fn in fns:
1524 for fn in fns:
1524 if fn in skip:
1525 if fn in skip:
1525 continue
1526 continue
1526 fstate.setdefault(fn, {})
1527 fstate.setdefault(fn, {})
1527 copies.setdefault(rev, {})
1528 copies.setdefault(rev, {})
1528 try:
1529 try:
1529 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1530 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1530 if follow:
1531 if follow:
1531 copied = getfile(fn).renamed(mf[fn])
1532 copied = getfile(fn).renamed(mf[fn])
1532 if copied:
1533 if copied:
1533 copies[rev][fn] = copied[0]
1534 copies[rev][fn] = copied[0]
1534 except KeyError:
1535 except KeyError:
1535 pass
1536 pass
1536 elif st == 'iter':
1537 elif st == 'iter':
1537 states = matches[rev].items()
1538 states = matches[rev].items()
1538 states.sort()
1539 states.sort()
1539 for fn, m in states:
1540 for fn, m in states:
1540 copy = copies[rev].get(fn)
1541 copy = copies[rev].get(fn)
1541 if fn in skip:
1542 if fn in skip:
1542 if copy:
1543 if copy:
1543 skip[copy] = True
1544 skip[copy] = True
1544 continue
1545 continue
1545 if incrementing or not opts['all'] or fstate[fn]:
1546 if incrementing or not opts['all'] or fstate[fn]:
1546 pos, neg = display(fn, rev, m, fstate[fn])
1547 pos, neg = display(fn, rev, m, fstate[fn])
1547 count += pos + neg
1548 count += pos + neg
1548 if pos and not opts['all']:
1549 if pos and not opts['all']:
1549 skip[fn] = True
1550 skip[fn] = True
1550 if copy:
1551 if copy:
1551 skip[copy] = True
1552 skip[copy] = True
1552 fstate[fn] = m
1553 fstate[fn] = m
1553 if copy:
1554 if copy:
1554 fstate[copy] = m
1555 fstate[copy] = m
1555 prev[fn] = rev
1556 prev[fn] = rev
1556
1557
1557 if not incrementing:
1558 if not incrementing:
1558 fstate = fstate.items()
1559 fstate = fstate.items()
1559 fstate.sort()
1560 fstate.sort()
1560 for fn, state in fstate:
1561 for fn, state in fstate:
1561 if fn in skip:
1562 if fn in skip:
1562 continue
1563 continue
1563 if fn not in copies[prev[fn]]:
1564 if fn not in copies[prev[fn]]:
1564 display(fn, rev, {}, state)
1565 display(fn, rev, {}, state)
1565 return (count == 0 and 1) or 0
1566 return (count == 0 and 1) or 0
1566
1567
1567 def heads(ui, repo, **opts):
1568 def heads(ui, repo, **opts):
1568 """show current repository heads
1569 """show current repository heads
1569
1570
1570 Show all repository head changesets.
1571 Show all repository head changesets.
1571
1572
1572 Repository "heads" are changesets that don't have children
1573 Repository "heads" are changesets that don't have children
1573 changesets. They are where development generally takes place and
1574 changesets. They are where development generally takes place and
1574 are the usual targets for update and merge operations.
1575 are the usual targets for update and merge operations.
1575 """
1576 """
1576 if opts['rev']:
1577 if opts['rev']:
1577 heads = repo.heads(repo.lookup(opts['rev']))
1578 heads = repo.heads(repo.lookup(opts['rev']))
1578 else:
1579 else:
1579 heads = repo.heads()
1580 heads = repo.heads()
1580 br = None
1581 br = None
1581 if opts['branches']:
1582 if opts['branches']:
1582 br = repo.branchlookup(heads)
1583 br = repo.branchlookup(heads)
1583 displayer = show_changeset(ui, repo, opts)
1584 displayer = show_changeset(ui, repo, opts)
1584 for n in heads:
1585 for n in heads:
1585 displayer.show(changenode=n, brinfo=br)
1586 displayer.show(changenode=n, brinfo=br)
1586
1587
1587 def identify(ui, repo):
1588 def identify(ui, repo):
1588 """print information about the working copy
1589 """print information about the working copy
1589
1590
1590 Print a short summary of the current state of the repo.
1591 Print a short summary of the current state of the repo.
1591
1592
1592 This summary identifies the repository state using one or two parent
1593 This summary identifies the repository state using one or two parent
1593 hash identifiers, followed by a "+" if there are uncommitted changes
1594 hash identifiers, followed by a "+" if there are uncommitted changes
1594 in the working directory, followed by a list of tags for this revision.
1595 in the working directory, followed by a list of tags for this revision.
1595 """
1596 """
1596 parents = [p for p in repo.dirstate.parents() if p != nullid]
1597 parents = [p for p in repo.dirstate.parents() if p != nullid]
1597 if not parents:
1598 if not parents:
1598 ui.write(_("unknown\n"))
1599 ui.write(_("unknown\n"))
1599 return
1600 return
1600
1601
1601 hexfunc = ui.verbose and hex or short
1602 hexfunc = ui.verbose and hex or short
1602 modified, added, removed, deleted = repo.status()[:4]
1603 modified, added, removed, deleted = repo.status()[:4]
1603 output = ["%s%s" %
1604 output = ["%s%s" %
1604 ('+'.join([hexfunc(parent) for parent in parents]),
1605 ('+'.join([hexfunc(parent) for parent in parents]),
1605 (modified or added or removed or deleted) and "+" or "")]
1606 (modified or added or removed or deleted) and "+" or "")]
1606
1607
1607 if not ui.quiet:
1608 if not ui.quiet:
1608 # multiple tags for a single parent separated by '/'
1609 # multiple tags for a single parent separated by '/'
1609 parenttags = ['/'.join(tags)
1610 parenttags = ['/'.join(tags)
1610 for tags in map(repo.nodetags, parents) if tags]
1611 for tags in map(repo.nodetags, parents) if tags]
1611 # tags for multiple parents separated by ' + '
1612 # tags for multiple parents separated by ' + '
1612 if parenttags:
1613 if parenttags:
1613 output.append(' + '.join(parenttags))
1614 output.append(' + '.join(parenttags))
1614
1615
1615 ui.write("%s\n" % ' '.join(output))
1616 ui.write("%s\n" % ' '.join(output))
1616
1617
1617 def import_(ui, repo, patch1, *patches, **opts):
1618 def import_(ui, repo, patch1, *patches, **opts):
1618 """import an ordered set of patches
1619 """import an ordered set of patches
1619
1620
1620 Import a list of patches and commit them individually.
1621 Import a list of patches and commit them individually.
1621
1622
1622 If there are outstanding changes in the working directory, import
1623 If there are outstanding changes in the working directory, import
1623 will abort unless given the -f flag.
1624 will abort unless given the -f flag.
1624
1625
1625 You can import a patch straight from a mail message. Even patches
1626 You can import a patch straight from a mail message. Even patches
1626 as attachments work (body part must be type text/plain or
1627 as attachments work (body part must be type text/plain or
1627 text/x-patch to be used). From and Subject headers of email
1628 text/x-patch to be used). From and Subject headers of email
1628 message are used as default committer and commit message. All
1629 message are used as default committer and commit message. All
1629 text/plain body parts before first diff are added to commit
1630 text/plain body parts before first diff are added to commit
1630 message.
1631 message.
1631
1632
1632 If imported patch was generated by hg export, user and description
1633 If imported patch was generated by hg export, user and description
1633 from patch override values from message headers and body. Values
1634 from patch override values from message headers and body. Values
1634 given on command line with -m and -u override these.
1635 given on command line with -m and -u override these.
1635
1636
1636 To read a patch from standard input, use patch name "-".
1637 To read a patch from standard input, use patch name "-".
1637 """
1638 """
1638 patches = (patch1,) + patches
1639 patches = (patch1,) + patches
1639
1640
1640 if not opts['force']:
1641 if not opts['force']:
1641 bail_if_changed(repo)
1642 bail_if_changed(repo)
1642
1643
1643 d = opts["base"]
1644 d = opts["base"]
1644 strip = opts["strip"]
1645 strip = opts["strip"]
1645
1646
1646 wlock = repo.wlock()
1647 wlock = repo.wlock()
1647 lock = repo.lock()
1648 lock = repo.lock()
1648
1649
1649 for p in patches:
1650 for p in patches:
1650 pf = os.path.join(d, p)
1651 pf = os.path.join(d, p)
1651
1652
1652 if pf == '-':
1653 if pf == '-':
1653 ui.status(_("applying patch from stdin\n"))
1654 ui.status(_("applying patch from stdin\n"))
1654 tmpname, message, user, date = patch.extract(ui, sys.stdin)
1655 tmpname, message, user, date = patch.extract(ui, sys.stdin)
1655 else:
1656 else:
1656 ui.status(_("applying %s\n") % p)
1657 ui.status(_("applying %s\n") % p)
1657 tmpname, message, user, date = patch.extract(ui, file(pf))
1658 tmpname, message, user, date = patch.extract(ui, file(pf))
1658
1659
1659 if tmpname is None:
1660 if tmpname is None:
1660 raise util.Abort(_('no diffs found'))
1661 raise util.Abort(_('no diffs found'))
1661
1662
1662 try:
1663 try:
1663 if opts['message']:
1664 if opts['message']:
1664 # pickup the cmdline msg
1665 # pickup the cmdline msg
1665 message = opts['message']
1666 message = opts['message']
1666 elif message:
1667 elif message:
1667 # pickup the patch msg
1668 # pickup the patch msg
1668 message = message.strip()
1669 message = message.strip()
1669 else:
1670 else:
1670 # launch the editor
1671 # launch the editor
1671 message = None
1672 message = None
1672 ui.debug(_('message:\n%s\n') % message)
1673 ui.debug(_('message:\n%s\n') % message)
1673
1674
1674 files = patch.patch(strip, tmpname, ui, cwd=repo.root)
1675 files = patch.patch(strip, tmpname, ui, cwd=repo.root)
1675 removes = []
1676 removes = []
1676 if len(files) > 0:
1677 if len(files) > 0:
1677 cfiles = files.keys()
1678 cfiles = files.keys()
1678 copies = []
1679 copies = []
1679 copts = {'after': False, 'force': False}
1680 copts = {'after': False, 'force': False}
1680 cwd = repo.getcwd()
1681 cwd = repo.getcwd()
1681 if cwd:
1682 if cwd:
1682 cfiles = [util.pathto(cwd, f) for f in files.keys()]
1683 cfiles = [util.pathto(cwd, f) for f in files.keys()]
1683 for f in files:
1684 for f in files:
1684 ctype, gp = files[f]
1685 ctype, gp = files[f]
1685 if ctype == 'RENAME':
1686 if ctype == 'RENAME':
1686 copies.append((gp.oldpath, gp.path, gp.copymod))
1687 copies.append((gp.oldpath, gp.path, gp.copymod))
1687 removes.append(gp.oldpath)
1688 removes.append(gp.oldpath)
1688 elif ctype == 'COPY':
1689 elif ctype == 'COPY':
1689 copies.append((gp.oldpath, gp.path, gp.copymod))
1690 copies.append((gp.oldpath, gp.path, gp.copymod))
1690 elif ctype == 'DELETE':
1691 elif ctype == 'DELETE':
1691 removes.append(gp.path)
1692 removes.append(gp.path)
1692 for src, dst, after in copies:
1693 for src, dst, after in copies:
1693 absdst = os.path.join(repo.root, dst)
1694 absdst = os.path.join(repo.root, dst)
1694 if not after and os.path.exists(absdst):
1695 if not after and os.path.exists(absdst):
1695 raise util.Abort(_('patch creates existing file %s') % dst)
1696 raise util.Abort(_('patch creates existing file %s') % dst)
1696 if cwd:
1697 if cwd:
1697 src, dst = [util.pathto(cwd, f) for f in (src, dst)]
1698 src, dst = [util.pathto(cwd, f) for f in (src, dst)]
1698 copts['after'] = after
1699 copts['after'] = after
1699 errs, copied = docopy(ui, repo, (src, dst), copts, wlock=wlock)
1700 errs, copied = docopy(ui, repo, (src, dst), copts, wlock=wlock)
1700 if errs:
1701 if errs:
1701 raise util.Abort(errs)
1702 raise util.Abort(errs)
1702 if removes:
1703 if removes:
1703 repo.remove(removes, True, wlock=wlock)
1704 repo.remove(removes, True, wlock=wlock)
1704 for f in files:
1705 for f in files:
1705 ctype, gp = files[f]
1706 ctype, gp = files[f]
1706 if gp and gp.mode:
1707 if gp and gp.mode:
1707 x = gp.mode & 0100 != 0
1708 x = gp.mode & 0100 != 0
1708 dst = os.path.join(repo.root, gp.path)
1709 dst = os.path.join(repo.root, gp.path)
1709 util.set_exec(dst, x)
1710 util.set_exec(dst, x)
1710 cmdutil.addremove(repo, cfiles, wlock=wlock)
1711 cmdutil.addremove(repo, cfiles, wlock=wlock)
1711 files = files.keys()
1712 files = files.keys()
1712 files.extend([r for r in removes if r not in files])
1713 files.extend([r for r in removes if r not in files])
1713 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1714 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1714 finally:
1715 finally:
1715 os.unlink(tmpname)
1716 os.unlink(tmpname)
1716
1717
1717 def incoming(ui, repo, source="default", **opts):
1718 def incoming(ui, repo, source="default", **opts):
1718 """show new changesets found in source
1719 """show new changesets found in source
1719
1720
1720 Show new changesets found in the specified path/URL or the default
1721 Show new changesets found in the specified path/URL or the default
1721 pull location. These are the changesets that would be pulled if a pull
1722 pull location. These are the changesets that would be pulled if a pull
1722 was requested.
1723 was requested.
1723
1724
1724 For remote repository, using --bundle avoids downloading the changesets
1725 For remote repository, using --bundle avoids downloading the changesets
1725 twice if the incoming is followed by a pull.
1726 twice if the incoming is followed by a pull.
1726
1727
1727 See pull for valid source format details.
1728 See pull for valid source format details.
1728 """
1729 """
1729 source = ui.expandpath(source)
1730 source = ui.expandpath(source)
1730 setremoteconfig(ui, opts)
1731 setremoteconfig(ui, opts)
1731
1732
1732 other = hg.repository(ui, source)
1733 other = hg.repository(ui, source)
1733 incoming = repo.findincoming(other, force=opts["force"])
1734 incoming = repo.findincoming(other, force=opts["force"])
1734 if not incoming:
1735 if not incoming:
1735 ui.status(_("no changes found\n"))
1736 ui.status(_("no changes found\n"))
1736 return
1737 return
1737
1738
1738 cleanup = None
1739 cleanup = None
1739 try:
1740 try:
1740 fname = opts["bundle"]
1741 fname = opts["bundle"]
1741 if fname or not other.local():
1742 if fname or not other.local():
1742 # create a bundle (uncompressed if other repo is not local)
1743 # create a bundle (uncompressed if other repo is not local)
1743 cg = other.changegroup(incoming, "incoming")
1744 cg = other.changegroup(incoming, "incoming")
1744 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1745 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1745 # keep written bundle?
1746 # keep written bundle?
1746 if opts["bundle"]:
1747 if opts["bundle"]:
1747 cleanup = None
1748 cleanup = None
1748 if not other.local():
1749 if not other.local():
1749 # use the created uncompressed bundlerepo
1750 # use the created uncompressed bundlerepo
1750 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1751 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1751
1752
1752 revs = None
1753 revs = None
1753 if opts['rev']:
1754 if opts['rev']:
1754 revs = [other.lookup(rev) for rev in opts['rev']]
1755 revs = [other.lookup(rev) for rev in opts['rev']]
1755 o = other.changelog.nodesbetween(incoming, revs)[0]
1756 o = other.changelog.nodesbetween(incoming, revs)[0]
1756 if opts['newest_first']:
1757 if opts['newest_first']:
1757 o.reverse()
1758 o.reverse()
1758 displayer = show_changeset(ui, other, opts)
1759 displayer = show_changeset(ui, other, opts)
1759 for n in o:
1760 for n in o:
1760 parents = [p for p in other.changelog.parents(n) if p != nullid]
1761 parents = [p for p in other.changelog.parents(n) if p != nullid]
1761 if opts['no_merges'] and len(parents) == 2:
1762 if opts['no_merges'] and len(parents) == 2:
1762 continue
1763 continue
1763 displayer.show(changenode=n)
1764 displayer.show(changenode=n)
1764 if opts['patch']:
1765 if opts['patch']:
1765 prev = (parents and parents[0]) or nullid
1766 prev = (parents and parents[0]) or nullid
1766 patch.diff(repo, other, prev, n)
1767 patch.diff(repo, other, prev, n)
1767 ui.write("\n")
1768 ui.write("\n")
1768 finally:
1769 finally:
1769 if hasattr(other, 'close'):
1770 if hasattr(other, 'close'):
1770 other.close()
1771 other.close()
1771 if cleanup:
1772 if cleanup:
1772 os.unlink(cleanup)
1773 os.unlink(cleanup)
1773
1774
1774 def init(ui, dest=".", **opts):
1775 def init(ui, dest=".", **opts):
1775 """create a new repository in the given directory
1776 """create a new repository in the given directory
1776
1777
1777 Initialize a new repository in the given directory. If the given
1778 Initialize a new repository in the given directory. If the given
1778 directory does not exist, it is created.
1779 directory does not exist, it is created.
1779
1780
1780 If no directory is given, the current directory is used.
1781 If no directory is given, the current directory is used.
1781
1782
1782 It is possible to specify an ssh:// URL as the destination.
1783 It is possible to specify an ssh:// URL as the destination.
1783 Look at the help text for the pull command for important details
1784 Look at the help text for the pull command for important details
1784 about ssh:// URLs.
1785 about ssh:// URLs.
1785 """
1786 """
1786 setremoteconfig(ui, opts)
1787 setremoteconfig(ui, opts)
1787 hg.repository(ui, dest, create=1)
1788 hg.repository(ui, dest, create=1)
1788
1789
1789 def locate(ui, repo, *pats, **opts):
1790 def locate(ui, repo, *pats, **opts):
1790 """locate files matching specific patterns
1791 """locate files matching specific patterns
1791
1792
1792 Print all files under Mercurial control whose names match the
1793 Print all files under Mercurial control whose names match the
1793 given patterns.
1794 given patterns.
1794
1795
1795 This command searches the current directory and its
1796 This command searches the current directory and its
1796 subdirectories. To search an entire repository, move to the root
1797 subdirectories. To search an entire repository, move to the root
1797 of the repository.
1798 of the repository.
1798
1799
1799 If no patterns are given to match, this command prints all file
1800 If no patterns are given to match, this command prints all file
1800 names.
1801 names.
1801
1802
1802 If you want to feed the output of this command into the "xargs"
1803 If you want to feed the output of this command into the "xargs"
1803 command, use the "-0" option to both this command and "xargs".
1804 command, use the "-0" option to both this command and "xargs".
1804 This will avoid the problem of "xargs" treating single filenames
1805 This will avoid the problem of "xargs" treating single filenames
1805 that contain white space as multiple filenames.
1806 that contain white space as multiple filenames.
1806 """
1807 """
1807 end = opts['print0'] and '\0' or '\n'
1808 end = opts['print0'] and '\0' or '\n'
1808 rev = opts['rev']
1809 rev = opts['rev']
1809 if rev:
1810 if rev:
1810 node = repo.lookup(rev)
1811 node = repo.lookup(rev)
1811 else:
1812 else:
1812 node = None
1813 node = None
1813
1814
1814 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1815 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1815 head='(?:.*/|)'):
1816 head='(?:.*/|)'):
1816 if not node and repo.dirstate.state(abs) == '?':
1817 if not node and repo.dirstate.state(abs) == '?':
1817 continue
1818 continue
1818 if opts['fullpath']:
1819 if opts['fullpath']:
1819 ui.write(os.path.join(repo.root, abs), end)
1820 ui.write(os.path.join(repo.root, abs), end)
1820 else:
1821 else:
1821 ui.write(((pats and rel) or abs), end)
1822 ui.write(((pats and rel) or abs), end)
1822
1823
1823 def log(ui, repo, *pats, **opts):
1824 def log(ui, repo, *pats, **opts):
1824 """show revision history of entire repository or files
1825 """show revision history of entire repository or files
1825
1826
1826 Print the revision history of the specified files or the entire
1827 Print the revision history of the specified files or the entire
1827 project.
1828 project.
1828
1829
1829 File history is shown without following rename or copy history of
1830 File history is shown without following rename or copy history of
1830 files. Use -f/--follow with a file name to follow history across
1831 files. Use -f/--follow with a file name to follow history across
1831 renames and copies. --follow without a file name will only show
1832 renames and copies. --follow without a file name will only show
1832 ancestors or descendants of the starting revision. --follow-first
1833 ancestors or descendants of the starting revision. --follow-first
1833 only follows the first parent of merge revisions.
1834 only follows the first parent of merge revisions.
1834
1835
1835 If no revision range is specified, the default is tip:0 unless
1836 If no revision range is specified, the default is tip:0 unless
1836 --follow is set, in which case the working directory parent is
1837 --follow is set, in which case the working directory parent is
1837 used as the starting revision.
1838 used as the starting revision.
1838
1839
1839 By default this command outputs: changeset id and hash, tags,
1840 By default this command outputs: changeset id and hash, tags,
1840 non-trivial parents, user, date and time, and a summary for each
1841 non-trivial parents, user, date and time, and a summary for each
1841 commit. When the -v/--verbose switch is used, the list of changed
1842 commit. When the -v/--verbose switch is used, the list of changed
1842 files and full commit message is shown.
1843 files and full commit message is shown.
1843 """
1844 """
1844 class dui(object):
1845 class dui(object):
1845 # Implement and delegate some ui protocol. Save hunks of
1846 # Implement and delegate some ui protocol. Save hunks of
1846 # output for later display in the desired order.
1847 # output for later display in the desired order.
1847 def __init__(self, ui):
1848 def __init__(self, ui):
1848 self.ui = ui
1849 self.ui = ui
1849 self.hunk = {}
1850 self.hunk = {}
1850 self.header = {}
1851 self.header = {}
1851 def bump(self, rev):
1852 def bump(self, rev):
1852 self.rev = rev
1853 self.rev = rev
1853 self.hunk[rev] = []
1854 self.hunk[rev] = []
1854 self.header[rev] = []
1855 self.header[rev] = []
1855 def note(self, *args):
1856 def note(self, *args):
1856 if self.verbose:
1857 if self.verbose:
1857 self.write(*args)
1858 self.write(*args)
1858 def status(self, *args):
1859 def status(self, *args):
1859 if not self.quiet:
1860 if not self.quiet:
1860 self.write(*args)
1861 self.write(*args)
1861 def write(self, *args):
1862 def write(self, *args):
1862 self.hunk[self.rev].append(args)
1863 self.hunk[self.rev].append(args)
1863 def write_header(self, *args):
1864 def write_header(self, *args):
1864 self.header[self.rev].append(args)
1865 self.header[self.rev].append(args)
1865 def debug(self, *args):
1866 def debug(self, *args):
1866 if self.debugflag:
1867 if self.debugflag:
1867 self.write(*args)
1868 self.write(*args)
1868 def __getattr__(self, key):
1869 def __getattr__(self, key):
1869 return getattr(self.ui, key)
1870 return getattr(self.ui, key)
1870
1871
1871 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1872 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1872
1873
1873 if opts['limit']:
1874 if opts['limit']:
1874 try:
1875 try:
1875 limit = int(opts['limit'])
1876 limit = int(opts['limit'])
1876 except ValueError:
1877 except ValueError:
1877 raise util.Abort(_('limit must be a positive integer'))
1878 raise util.Abort(_('limit must be a positive integer'))
1878 if limit <= 0: raise util.Abort(_('limit must be positive'))
1879 if limit <= 0: raise util.Abort(_('limit must be positive'))
1879 else:
1880 else:
1880 limit = sys.maxint
1881 limit = sys.maxint
1881 count = 0
1882 count = 0
1882
1883
1883 displayer = show_changeset(ui, repo, opts)
1884 displayer = show_changeset(ui, repo, opts)
1884 for st, rev, fns in changeiter:
1885 for st, rev, fns in changeiter:
1885 if st == 'window':
1886 if st == 'window':
1886 du = dui(ui)
1887 du = dui(ui)
1887 displayer.ui = du
1888 displayer.ui = du
1888 elif st == 'add':
1889 elif st == 'add':
1889 du.bump(rev)
1890 du.bump(rev)
1890 changenode = repo.changelog.node(rev)
1891 changenode = repo.changelog.node(rev)
1891 parents = [p for p in repo.changelog.parents(changenode)
1892 parents = [p for p in repo.changelog.parents(changenode)
1892 if p != nullid]
1893 if p != nullid]
1893 if opts['no_merges'] and len(parents) == 2:
1894 if opts['no_merges'] and len(parents) == 2:
1894 continue
1895 continue
1895 if opts['only_merges'] and len(parents) != 2:
1896 if opts['only_merges'] and len(parents) != 2:
1896 continue
1897 continue
1897
1898
1898 if opts['keyword']:
1899 if opts['keyword']:
1899 changes = getchange(rev)
1900 changes = getchange(rev)
1900 miss = 0
1901 miss = 0
1901 for k in [kw.lower() for kw in opts['keyword']]:
1902 for k in [kw.lower() for kw in opts['keyword']]:
1902 if not (k in changes[1].lower() or
1903 if not (k in changes[1].lower() or
1903 k in changes[4].lower() or
1904 k in changes[4].lower() or
1904 k in " ".join(changes[3][:20]).lower()):
1905 k in " ".join(changes[3][:20]).lower()):
1905 miss = 1
1906 miss = 1
1906 break
1907 break
1907 if miss:
1908 if miss:
1908 continue
1909 continue
1909
1910
1910 br = None
1911 br = None
1911 if opts['branches']:
1912 if opts['branches']:
1912 br = repo.branchlookup([repo.changelog.node(rev)])
1913 br = repo.branchlookup([repo.changelog.node(rev)])
1913
1914
1914 displayer.show(rev, brinfo=br)
1915 displayer.show(rev, brinfo=br)
1915 if opts['patch']:
1916 if opts['patch']:
1916 prev = (parents and parents[0]) or nullid
1917 prev = (parents and parents[0]) or nullid
1917 patch.diff(repo, prev, changenode, match=matchfn, fp=du)
1918 patch.diff(repo, prev, changenode, match=matchfn, fp=du)
1918 du.write("\n\n")
1919 du.write("\n\n")
1919 elif st == 'iter':
1920 elif st == 'iter':
1920 if count == limit: break
1921 if count == limit: break
1921 if du.header[rev]:
1922 if du.header[rev]:
1922 for args in du.header[rev]:
1923 for args in du.header[rev]:
1923 ui.write_header(*args)
1924 ui.write_header(*args)
1924 if du.hunk[rev]:
1925 if du.hunk[rev]:
1925 count += 1
1926 count += 1
1926 for args in du.hunk[rev]:
1927 for args in du.hunk[rev]:
1927 ui.write(*args)
1928 ui.write(*args)
1928
1929
1929 def manifest(ui, repo, rev=None):
1930 def manifest(ui, repo, rev=None):
1930 """output the latest or given revision of the project manifest
1931 """output the latest or given revision of the project manifest
1931
1932
1932 Print a list of version controlled files for the given revision.
1933 Print a list of version controlled files for the given revision.
1933
1934
1934 The manifest is the list of files being version controlled. If no revision
1935 The manifest is the list of files being version controlled. If no revision
1935 is given then the tip is used.
1936 is given then the tip is used.
1936 """
1937 """
1937 if rev:
1938 if rev:
1938 try:
1939 try:
1939 # assume all revision numbers are for changesets
1940 # assume all revision numbers are for changesets
1940 n = repo.lookup(rev)
1941 n = repo.lookup(rev)
1941 change = repo.changelog.read(n)
1942 change = repo.changelog.read(n)
1942 n = change[0]
1943 n = change[0]
1943 except hg.RepoError:
1944 except hg.RepoError:
1944 n = repo.manifest.lookup(rev)
1945 n = repo.manifest.lookup(rev)
1945 else:
1946 else:
1946 n = repo.manifest.tip()
1947 n = repo.manifest.tip()
1947 m = repo.manifest.read(n)
1948 m = repo.manifest.read(n)
1948 files = m.keys()
1949 files = m.keys()
1949 files.sort()
1950 files.sort()
1950
1951
1951 for f in files:
1952 for f in files:
1952 ui.write("%40s %3s %s\n" % (hex(m[f]),
1953 ui.write("%40s %3s %s\n" % (hex(m[f]),
1953 m.execf(f) and "755" or "644", f))
1954 m.execf(f) and "755" or "644", f))
1954
1955
1955 def merge(ui, repo, node=None, force=None, branch=None):
1956 def merge(ui, repo, node=None, force=None, branch=None):
1956 """Merge working directory with another revision
1957 """Merge working directory with another revision
1957
1958
1958 Merge the contents of the current working directory and the
1959 Merge the contents of the current working directory and the
1959 requested revision. Files that changed between either parent are
1960 requested revision. Files that changed between either parent are
1960 marked as changed for the next commit and a commit must be
1961 marked as changed for the next commit and a commit must be
1961 performed before any further updates are allowed.
1962 performed before any further updates are allowed.
1962 """
1963 """
1963
1964
1964 node = _lookup(repo, node, branch)
1965 node = _lookup(repo, node, branch)
1965 return hg.merge(repo, node, force=force)
1966 return hg.merge(repo, node, force=force)
1966
1967
1967 def outgoing(ui, repo, dest=None, **opts):
1968 def outgoing(ui, repo, dest=None, **opts):
1968 """show changesets not found in destination
1969 """show changesets not found in destination
1969
1970
1970 Show changesets not found in the specified destination repository or
1971 Show changesets not found in the specified destination repository or
1971 the default push location. These are the changesets that would be pushed
1972 the default push location. These are the changesets that would be pushed
1972 if a push was requested.
1973 if a push was requested.
1973
1974
1974 See pull for valid destination format details.
1975 See pull for valid destination format details.
1975 """
1976 """
1976 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1977 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1977 setremoteconfig(ui, opts)
1978 setremoteconfig(ui, opts)
1978 revs = None
1979 revs = None
1979 if opts['rev']:
1980 if opts['rev']:
1980 revs = [repo.lookup(rev) for rev in opts['rev']]
1981 revs = [repo.lookup(rev) for rev in opts['rev']]
1981
1982
1982 other = hg.repository(ui, dest)
1983 other = hg.repository(ui, dest)
1983 o = repo.findoutgoing(other, force=opts['force'])
1984 o = repo.findoutgoing(other, force=opts['force'])
1984 if not o:
1985 if not o:
1985 ui.status(_("no changes found\n"))
1986 ui.status(_("no changes found\n"))
1986 return
1987 return
1987 o = repo.changelog.nodesbetween(o, revs)[0]
1988 o = repo.changelog.nodesbetween(o, revs)[0]
1988 if opts['newest_first']:
1989 if opts['newest_first']:
1989 o.reverse()
1990 o.reverse()
1990 displayer = show_changeset(ui, repo, opts)
1991 displayer = show_changeset(ui, repo, opts)
1991 for n in o:
1992 for n in o:
1992 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1993 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1993 if opts['no_merges'] and len(parents) == 2:
1994 if opts['no_merges'] and len(parents) == 2:
1994 continue
1995 continue
1995 displayer.show(changenode=n)
1996 displayer.show(changenode=n)
1996 if opts['patch']:
1997 if opts['patch']:
1997 prev = (parents and parents[0]) or nullid
1998 prev = (parents and parents[0]) or nullid
1998 patch.diff(repo, prev, n)
1999 patch.diff(repo, prev, n)
1999 ui.write("\n")
2000 ui.write("\n")
2000
2001
2001 def parents(ui, repo, file_=None, rev=None, branches=None, **opts):
2002 def parents(ui, repo, file_=None, rev=None, branches=None, **opts):
2002 """show the parents of the working dir or revision
2003 """show the parents of the working dir or revision
2003
2004
2004 Print the working directory's parent revisions.
2005 Print the working directory's parent revisions.
2005 """
2006 """
2006 # legacy
2007 # legacy
2007 if file_ and not rev:
2008 if file_ and not rev:
2008 try:
2009 try:
2009 rev = repo.lookup(file_)
2010 rev = repo.lookup(file_)
2010 file_ = None
2011 file_ = None
2011 except hg.RepoError:
2012 except hg.RepoError:
2012 pass
2013 pass
2013 else:
2014 else:
2014 ui.warn(_("'hg parent REV' is deprecated, "
2015 ui.warn(_("'hg parent REV' is deprecated, "
2015 "please use 'hg parents -r REV instead\n"))
2016 "please use 'hg parents -r REV instead\n"))
2016
2017
2017 if rev:
2018 if rev:
2018 if file_:
2019 if file_:
2019 ctx = repo.filectx(file_, changeid=rev)
2020 ctx = repo.filectx(file_, changeid=rev)
2020 else:
2021 else:
2021 ctx = repo.changectx(rev)
2022 ctx = repo.changectx(rev)
2022 p = [cp.node() for cp in ctx.parents()]
2023 p = [cp.node() for cp in ctx.parents()]
2023 else:
2024 else:
2024 p = repo.dirstate.parents()
2025 p = repo.dirstate.parents()
2025
2026
2026 br = None
2027 br = None
2027 if branches is not None:
2028 if branches is not None:
2028 br = repo.branchlookup(p)
2029 br = repo.branchlookup(p)
2029 displayer = show_changeset(ui, repo, opts)
2030 displayer = show_changeset(ui, repo, opts)
2030 for n in p:
2031 for n in p:
2031 if n != nullid:
2032 if n != nullid:
2032 displayer.show(changenode=n, brinfo=br)
2033 displayer.show(changenode=n, brinfo=br)
2033
2034
2034 def paths(ui, repo, search=None):
2035 def paths(ui, repo, search=None):
2035 """show definition of symbolic path names
2036 """show definition of symbolic path names
2036
2037
2037 Show definition of symbolic path name NAME. If no name is given, show
2038 Show definition of symbolic path name NAME. If no name is given, show
2038 definition of available names.
2039 definition of available names.
2039
2040
2040 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2041 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2041 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2042 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2042 """
2043 """
2043 if search:
2044 if search:
2044 for name, path in ui.configitems("paths"):
2045 for name, path in ui.configitems("paths"):
2045 if name == search:
2046 if name == search:
2046 ui.write("%s\n" % path)
2047 ui.write("%s\n" % path)
2047 return
2048 return
2048 ui.warn(_("not found!\n"))
2049 ui.warn(_("not found!\n"))
2049 return 1
2050 return 1
2050 else:
2051 else:
2051 for name, path in ui.configitems("paths"):
2052 for name, path in ui.configitems("paths"):
2052 ui.write("%s = %s\n" % (name, path))
2053 ui.write("%s = %s\n" % (name, path))
2053
2054
2054 def postincoming(ui, repo, modheads, optupdate):
2055 def postincoming(ui, repo, modheads, optupdate):
2055 if modheads == 0:
2056 if modheads == 0:
2056 return
2057 return
2057 if optupdate:
2058 if optupdate:
2058 if modheads == 1:
2059 if modheads == 1:
2059 return hg.update(repo, repo.changelog.tip()) # update
2060 return hg.update(repo, repo.changelog.tip()) # update
2060 else:
2061 else:
2061 ui.status(_("not updating, since new heads added\n"))
2062 ui.status(_("not updating, since new heads added\n"))
2062 if modheads > 1:
2063 if modheads > 1:
2063 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2064 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2064 else:
2065 else:
2065 ui.status(_("(run 'hg update' to get a working copy)\n"))
2066 ui.status(_("(run 'hg update' to get a working copy)\n"))
2066
2067
2067 def pull(ui, repo, source="default", **opts):
2068 def pull(ui, repo, source="default", **opts):
2068 """pull changes from the specified source
2069 """pull changes from the specified source
2069
2070
2070 Pull changes from a remote repository to a local one.
2071 Pull changes from a remote repository to a local one.
2071
2072
2072 This finds all changes from the repository at the specified path
2073 This finds all changes from the repository at the specified path
2073 or URL and adds them to the local repository. By default, this
2074 or URL and adds them to the local repository. By default, this
2074 does not update the copy of the project in the working directory.
2075 does not update the copy of the project in the working directory.
2075
2076
2076 Valid URLs are of the form:
2077 Valid URLs are of the form:
2077
2078
2078 local/filesystem/path
2079 local/filesystem/path
2079 http://[user@]host[:port]/[path]
2080 http://[user@]host[:port]/[path]
2080 https://[user@]host[:port]/[path]
2081 https://[user@]host[:port]/[path]
2081 ssh://[user@]host[:port]/[path]
2082 ssh://[user@]host[:port]/[path]
2082
2083
2083 Some notes about using SSH with Mercurial:
2084 Some notes about using SSH with Mercurial:
2084 - SSH requires an accessible shell account on the destination machine
2085 - SSH requires an accessible shell account on the destination machine
2085 and a copy of hg in the remote path or specified with as remotecmd.
2086 and a copy of hg in the remote path or specified with as remotecmd.
2086 - path is relative to the remote user's home directory by default.
2087 - path is relative to the remote user's home directory by default.
2087 Use an extra slash at the start of a path to specify an absolute path:
2088 Use an extra slash at the start of a path to specify an absolute path:
2088 ssh://example.com//tmp/repository
2089 ssh://example.com//tmp/repository
2089 - Mercurial doesn't use its own compression via SSH; the right thing
2090 - Mercurial doesn't use its own compression via SSH; the right thing
2090 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2091 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2091 Host *.mylocalnetwork.example.com
2092 Host *.mylocalnetwork.example.com
2092 Compression off
2093 Compression off
2093 Host *
2094 Host *
2094 Compression on
2095 Compression on
2095 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2096 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2096 with the --ssh command line option.
2097 with the --ssh command line option.
2097 """
2098 """
2098 source = ui.expandpath(source)
2099 source = ui.expandpath(source)
2099 setremoteconfig(ui, opts)
2100 setremoteconfig(ui, opts)
2100
2101
2101 other = hg.repository(ui, source)
2102 other = hg.repository(ui, source)
2102 ui.status(_('pulling from %s\n') % (source))
2103 ui.status(_('pulling from %s\n') % (source))
2103 revs = None
2104 revs = None
2104 if opts['rev'] and not other.local():
2105 if opts['rev'] and not other.local():
2105 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2106 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2106 elif opts['rev']:
2107 elif opts['rev']:
2107 revs = [other.lookup(rev) for rev in opts['rev']]
2108 revs = [other.lookup(rev) for rev in opts['rev']]
2108 modheads = repo.pull(other, heads=revs, force=opts['force'])
2109 modheads = repo.pull(other, heads=revs, force=opts['force'])
2109 return postincoming(ui, repo, modheads, opts['update'])
2110 return postincoming(ui, repo, modheads, opts['update'])
2110
2111
2111 def push(ui, repo, dest=None, **opts):
2112 def push(ui, repo, dest=None, **opts):
2112 """push changes to the specified destination
2113 """push changes to the specified destination
2113
2114
2114 Push changes from the local repository to the given destination.
2115 Push changes from the local repository to the given destination.
2115
2116
2116 This is the symmetrical operation for pull. It helps to move
2117 This is the symmetrical operation for pull. It helps to move
2117 changes from the current repository to a different one. If the
2118 changes from the current repository to a different one. If the
2118 destination is local this is identical to a pull in that directory
2119 destination is local this is identical to a pull in that directory
2119 from the current one.
2120 from the current one.
2120
2121
2121 By default, push will refuse to run if it detects the result would
2122 By default, push will refuse to run if it detects the result would
2122 increase the number of remote heads. This generally indicates the
2123 increase the number of remote heads. This generally indicates the
2123 the client has forgotten to sync and merge before pushing.
2124 the client has forgotten to sync and merge before pushing.
2124
2125
2125 Valid URLs are of the form:
2126 Valid URLs are of the form:
2126
2127
2127 local/filesystem/path
2128 local/filesystem/path
2128 ssh://[user@]host[:port]/[path]
2129 ssh://[user@]host[:port]/[path]
2129
2130
2130 Look at the help text for the pull command for important details
2131 Look at the help text for the pull command for important details
2131 about ssh:// URLs.
2132 about ssh:// URLs.
2132
2133
2133 Pushing to http:// and https:// URLs is possible, too, if this
2134 Pushing to http:// and https:// URLs is possible, too, if this
2134 feature is enabled on the remote Mercurial server.
2135 feature is enabled on the remote Mercurial server.
2135 """
2136 """
2136 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2137 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2137 setremoteconfig(ui, opts)
2138 setremoteconfig(ui, opts)
2138
2139
2139 other = hg.repository(ui, dest)
2140 other = hg.repository(ui, dest)
2140 ui.status('pushing to %s\n' % (dest))
2141 ui.status('pushing to %s\n' % (dest))
2141 revs = None
2142 revs = None
2142 if opts['rev']:
2143 if opts['rev']:
2143 revs = [repo.lookup(rev) for rev in opts['rev']]
2144 revs = [repo.lookup(rev) for rev in opts['rev']]
2144 r = repo.push(other, opts['force'], revs=revs)
2145 r = repo.push(other, opts['force'], revs=revs)
2145 return r == 0
2146 return r == 0
2146
2147
2147 def rawcommit(ui, repo, *flist, **rc):
2148 def rawcommit(ui, repo, *flist, **rc):
2148 """raw commit interface (DEPRECATED)
2149 """raw commit interface (DEPRECATED)
2149
2150
2150 (DEPRECATED)
2151 (DEPRECATED)
2151 Lowlevel commit, for use in helper scripts.
2152 Lowlevel commit, for use in helper scripts.
2152
2153
2153 This command is not intended to be used by normal users, as it is
2154 This command is not intended to be used by normal users, as it is
2154 primarily useful for importing from other SCMs.
2155 primarily useful for importing from other SCMs.
2155
2156
2156 This command is now deprecated and will be removed in a future
2157 This command is now deprecated and will be removed in a future
2157 release, please use debugsetparents and commit instead.
2158 release, please use debugsetparents and commit instead.
2158 """
2159 """
2159
2160
2160 ui.warn(_("(the rawcommit command is deprecated)\n"))
2161 ui.warn(_("(the rawcommit command is deprecated)\n"))
2161
2162
2162 message = rc['message']
2163 message = rc['message']
2163 if not message and rc['logfile']:
2164 if not message and rc['logfile']:
2164 try:
2165 try:
2165 message = open(rc['logfile']).read()
2166 message = open(rc['logfile']).read()
2166 except IOError:
2167 except IOError:
2167 pass
2168 pass
2168 if not message and not rc['logfile']:
2169 if not message and not rc['logfile']:
2169 raise util.Abort(_("missing commit message"))
2170 raise util.Abort(_("missing commit message"))
2170
2171
2171 files = relpath(repo, list(flist))
2172 files = relpath(repo, list(flist))
2172 if rc['files']:
2173 if rc['files']:
2173 files += open(rc['files']).read().splitlines()
2174 files += open(rc['files']).read().splitlines()
2174
2175
2175 rc['parent'] = map(repo.lookup, rc['parent'])
2176 rc['parent'] = map(repo.lookup, rc['parent'])
2176
2177
2177 try:
2178 try:
2178 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2179 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2179 except ValueError, inst:
2180 except ValueError, inst:
2180 raise util.Abort(str(inst))
2181 raise util.Abort(str(inst))
2181
2182
2182 def recover(ui, repo):
2183 def recover(ui, repo):
2183 """roll back an interrupted transaction
2184 """roll back an interrupted transaction
2184
2185
2185 Recover from an interrupted commit or pull.
2186 Recover from an interrupted commit or pull.
2186
2187
2187 This command tries to fix the repository status after an interrupted
2188 This command tries to fix the repository status after an interrupted
2188 operation. It should only be necessary when Mercurial suggests it.
2189 operation. It should only be necessary when Mercurial suggests it.
2189 """
2190 """
2190 if repo.recover():
2191 if repo.recover():
2191 return hg.verify(repo)
2192 return hg.verify(repo)
2192 return 1
2193 return 1
2193
2194
2194 def remove(ui, repo, *pats, **opts):
2195 def remove(ui, repo, *pats, **opts):
2195 """remove the specified files on the next commit
2196 """remove the specified files on the next commit
2196
2197
2197 Schedule the indicated files for removal from the repository.
2198 Schedule the indicated files for removal from the repository.
2198
2199
2199 This command schedules the files to be removed at the next commit.
2200 This command schedules the files to be removed at the next commit.
2200 This only removes files from the current branch, not from the
2201 This only removes files from the current branch, not from the
2201 entire project history. If the files still exist in the working
2202 entire project history. If the files still exist in the working
2202 directory, they will be deleted from it. If invoked with --after,
2203 directory, they will be deleted from it. If invoked with --after,
2203 files that have been manually deleted are marked as removed.
2204 files that have been manually deleted are marked as removed.
2204
2205
2205 Modified files and added files are not removed by default. To
2206 Modified files and added files are not removed by default. To
2206 remove them, use the -f/--force option.
2207 remove them, use the -f/--force option.
2207 """
2208 """
2208 names = []
2209 names = []
2209 if not opts['after'] and not pats:
2210 if not opts['after'] and not pats:
2210 raise util.Abort(_('no files specified'))
2211 raise util.Abort(_('no files specified'))
2211 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2212 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2212 exact = dict.fromkeys(files)
2213 exact = dict.fromkeys(files)
2213 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2214 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2214 modified, added, removed, deleted, unknown = mardu
2215 modified, added, removed, deleted, unknown = mardu
2215 remove, forget = [], []
2216 remove, forget = [], []
2216 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2217 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2217 reason = None
2218 reason = None
2218 if abs not in deleted and opts['after']:
2219 if abs not in deleted and opts['after']:
2219 reason = _('is still present')
2220 reason = _('is still present')
2220 elif abs in modified and not opts['force']:
2221 elif abs in modified and not opts['force']:
2221 reason = _('is modified (use -f to force removal)')
2222 reason = _('is modified (use -f to force removal)')
2222 elif abs in added:
2223 elif abs in added:
2223 if opts['force']:
2224 if opts['force']:
2224 forget.append(abs)
2225 forget.append(abs)
2225 continue
2226 continue
2226 reason = _('has been marked for add (use -f to force removal)')
2227 reason = _('has been marked for add (use -f to force removal)')
2227 elif abs in unknown:
2228 elif abs in unknown:
2228 reason = _('is not managed')
2229 reason = _('is not managed')
2229 elif abs in removed:
2230 elif abs in removed:
2230 continue
2231 continue
2231 if reason:
2232 if reason:
2232 if exact:
2233 if exact:
2233 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2234 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2234 else:
2235 else:
2235 if ui.verbose or not exact:
2236 if ui.verbose or not exact:
2236 ui.status(_('removing %s\n') % rel)
2237 ui.status(_('removing %s\n') % rel)
2237 remove.append(abs)
2238 remove.append(abs)
2238 repo.forget(forget)
2239 repo.forget(forget)
2239 repo.remove(remove, unlink=not opts['after'])
2240 repo.remove(remove, unlink=not opts['after'])
2240
2241
2241 def rename(ui, repo, *pats, **opts):
2242 def rename(ui, repo, *pats, **opts):
2242 """rename files; equivalent of copy + remove
2243 """rename files; equivalent of copy + remove
2243
2244
2244 Mark dest as copies of sources; mark sources for deletion. If
2245 Mark dest as copies of sources; mark sources for deletion. If
2245 dest is a directory, copies are put in that directory. If dest is
2246 dest is a directory, copies are put in that directory. If dest is
2246 a file, there can only be one source.
2247 a file, there can only be one source.
2247
2248
2248 By default, this command copies the contents of files as they
2249 By default, this command copies the contents of files as they
2249 stand in the working directory. If invoked with --after, the
2250 stand in the working directory. If invoked with --after, the
2250 operation is recorded, but no copying is performed.
2251 operation is recorded, but no copying is performed.
2251
2252
2252 This command takes effect in the next commit.
2253 This command takes effect in the next commit.
2253
2254
2254 NOTE: This command should be treated as experimental. While it
2255 NOTE: This command should be treated as experimental. While it
2255 should properly record rename files, this information is not yet
2256 should properly record rename files, this information is not yet
2256 fully used by merge, nor fully reported by log.
2257 fully used by merge, nor fully reported by log.
2257 """
2258 """
2258 wlock = repo.wlock(0)
2259 wlock = repo.wlock(0)
2259 errs, copied = docopy(ui, repo, pats, opts, wlock)
2260 errs, copied = docopy(ui, repo, pats, opts, wlock)
2260 names = []
2261 names = []
2261 for abs, rel, exact in copied:
2262 for abs, rel, exact in copied:
2262 if ui.verbose or not exact:
2263 if ui.verbose or not exact:
2263 ui.status(_('removing %s\n') % rel)
2264 ui.status(_('removing %s\n') % rel)
2264 names.append(abs)
2265 names.append(abs)
2265 if not opts.get('dry_run'):
2266 if not opts.get('dry_run'):
2266 repo.remove(names, True, wlock)
2267 repo.remove(names, True, wlock)
2267 return errs
2268 return errs
2268
2269
2269 def revert(ui, repo, *pats, **opts):
2270 def revert(ui, repo, *pats, **opts):
2270 """revert files or dirs to their states as of some revision
2271 """revert files or dirs to their states as of some revision
2271
2272
2272 With no revision specified, revert the named files or directories
2273 With no revision specified, revert the named files or directories
2273 to the contents they had in the parent of the working directory.
2274 to the contents they had in the parent of the working directory.
2274 This restores the contents of the affected files to an unmodified
2275 This restores the contents of the affected files to an unmodified
2275 state. If the working directory has two parents, you must
2276 state. If the working directory has two parents, you must
2276 explicitly specify the revision to revert to.
2277 explicitly specify the revision to revert to.
2277
2278
2278 Modified files are saved with a .orig suffix before reverting.
2279 Modified files are saved with a .orig suffix before reverting.
2279 To disable these backups, use --no-backup.
2280 To disable these backups, use --no-backup.
2280
2281
2281 Using the -r option, revert the given files or directories to
2282 Using the -r option, revert the given files or directories to
2282 their contents as of a specific revision. This can be helpful to"roll
2283 their contents as of a specific revision. This can be helpful to"roll
2283 back" some or all of a change that should not have been committed.
2284 back" some or all of a change that should not have been committed.
2284
2285
2285 Revert modifies the working directory. It does not commit any
2286 Revert modifies the working directory. It does not commit any
2286 changes, or change the parent of the working directory. If you
2287 changes, or change the parent of the working directory. If you
2287 revert to a revision other than the parent of the working
2288 revert to a revision other than the parent of the working
2288 directory, the reverted files will thus appear modified
2289 directory, the reverted files will thus appear modified
2289 afterwards.
2290 afterwards.
2290
2291
2291 If a file has been deleted, it is recreated. If the executable
2292 If a file has been deleted, it is recreated. If the executable
2292 mode of a file was changed, it is reset.
2293 mode of a file was changed, it is reset.
2293
2294
2294 If names are given, all files matching the names are reverted.
2295 If names are given, all files matching the names are reverted.
2295
2296
2296 If no arguments are given, all files in the repository are reverted.
2297 If no arguments are given, all files in the repository are reverted.
2297 """
2298 """
2298 parent, p2 = repo.dirstate.parents()
2299 parent, p2 = repo.dirstate.parents()
2299 if opts['rev']:
2300 if opts['rev']:
2300 node = repo.lookup(opts['rev'])
2301 node = repo.lookup(opts['rev'])
2301 elif p2 != nullid:
2302 elif p2 != nullid:
2302 raise util.Abort(_('working dir has two parents; '
2303 raise util.Abort(_('working dir has two parents; '
2303 'you must specify the revision to revert to'))
2304 'you must specify the revision to revert to'))
2304 else:
2305 else:
2305 node = parent
2306 node = parent
2306 mf = repo.manifest.read(repo.changelog.read(node)[0])
2307 mf = repo.manifest.read(repo.changelog.read(node)[0])
2307 if node == parent:
2308 if node == parent:
2308 pmf = mf
2309 pmf = mf
2309 else:
2310 else:
2310 pmf = None
2311 pmf = None
2311
2312
2312 wlock = repo.wlock()
2313 wlock = repo.wlock()
2313
2314
2314 # need all matching names in dirstate and manifest of target rev,
2315 # need all matching names in dirstate and manifest of target rev,
2315 # so have to walk both. do not print errors if files exist in one
2316 # so have to walk both. do not print errors if files exist in one
2316 # but not other.
2317 # but not other.
2317
2318
2318 names = {}
2319 names = {}
2319 target_only = {}
2320 target_only = {}
2320
2321
2321 # walk dirstate.
2322 # walk dirstate.
2322
2323
2323 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2324 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2324 badmatch=mf.has_key):
2325 badmatch=mf.has_key):
2325 names[abs] = (rel, exact)
2326 names[abs] = (rel, exact)
2326 if src == 'b':
2327 if src == 'b':
2327 target_only[abs] = True
2328 target_only[abs] = True
2328
2329
2329 # walk target manifest.
2330 # walk target manifest.
2330
2331
2331 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2332 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2332 badmatch=names.has_key):
2333 badmatch=names.has_key):
2333 if abs in names: continue
2334 if abs in names: continue
2334 names[abs] = (rel, exact)
2335 names[abs] = (rel, exact)
2335 target_only[abs] = True
2336 target_only[abs] = True
2336
2337
2337 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2338 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2338 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2339 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2339
2340
2340 revert = ([], _('reverting %s\n'))
2341 revert = ([], _('reverting %s\n'))
2341 add = ([], _('adding %s\n'))
2342 add = ([], _('adding %s\n'))
2342 remove = ([], _('removing %s\n'))
2343 remove = ([], _('removing %s\n'))
2343 forget = ([], _('forgetting %s\n'))
2344 forget = ([], _('forgetting %s\n'))
2344 undelete = ([], _('undeleting %s\n'))
2345 undelete = ([], _('undeleting %s\n'))
2345 update = {}
2346 update = {}
2346
2347
2347 disptable = (
2348 disptable = (
2348 # dispatch table:
2349 # dispatch table:
2349 # file state
2350 # file state
2350 # action if in target manifest
2351 # action if in target manifest
2351 # action if not in target manifest
2352 # action if not in target manifest
2352 # make backup if in target manifest
2353 # make backup if in target manifest
2353 # make backup if not in target manifest
2354 # make backup if not in target manifest
2354 (modified, revert, remove, True, True),
2355 (modified, revert, remove, True, True),
2355 (added, revert, forget, True, False),
2356 (added, revert, forget, True, False),
2356 (removed, undelete, None, False, False),
2357 (removed, undelete, None, False, False),
2357 (deleted, revert, remove, False, False),
2358 (deleted, revert, remove, False, False),
2358 (unknown, add, None, True, False),
2359 (unknown, add, None, True, False),
2359 (target_only, add, None, False, False),
2360 (target_only, add, None, False, False),
2360 )
2361 )
2361
2362
2362 entries = names.items()
2363 entries = names.items()
2363 entries.sort()
2364 entries.sort()
2364
2365
2365 for abs, (rel, exact) in entries:
2366 for abs, (rel, exact) in entries:
2366 mfentry = mf.get(abs)
2367 mfentry = mf.get(abs)
2367 def handle(xlist, dobackup):
2368 def handle(xlist, dobackup):
2368 xlist[0].append(abs)
2369 xlist[0].append(abs)
2369 update[abs] = 1
2370 update[abs] = 1
2370 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2371 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2371 bakname = "%s.orig" % rel
2372 bakname = "%s.orig" % rel
2372 ui.note(_('saving current version of %s as %s\n') %
2373 ui.note(_('saving current version of %s as %s\n') %
2373 (rel, bakname))
2374 (rel, bakname))
2374 if not opts.get('dry_run'):
2375 if not opts.get('dry_run'):
2375 shutil.copyfile(rel, bakname)
2376 shutil.copyfile(rel, bakname)
2376 shutil.copymode(rel, bakname)
2377 shutil.copymode(rel, bakname)
2377 if ui.verbose or not exact:
2378 if ui.verbose or not exact:
2378 ui.status(xlist[1] % rel)
2379 ui.status(xlist[1] % rel)
2379 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2380 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2380 if abs not in table: continue
2381 if abs not in table: continue
2381 # file has changed in dirstate
2382 # file has changed in dirstate
2382 if mfentry:
2383 if mfentry:
2383 handle(hitlist, backuphit)
2384 handle(hitlist, backuphit)
2384 elif misslist is not None:
2385 elif misslist is not None:
2385 handle(misslist, backupmiss)
2386 handle(misslist, backupmiss)
2386 else:
2387 else:
2387 if exact: ui.warn(_('file not managed: %s\n' % rel))
2388 if exact: ui.warn(_('file not managed: %s\n' % rel))
2388 break
2389 break
2389 else:
2390 else:
2390 # file has not changed in dirstate
2391 # file has not changed in dirstate
2391 if node == parent:
2392 if node == parent:
2392 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2393 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2393 continue
2394 continue
2394 if pmf is None:
2395 if pmf is None:
2395 # only need parent manifest in this unlikely case,
2396 # only need parent manifest in this unlikely case,
2396 # so do not read by default
2397 # so do not read by default
2397 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2398 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2398 if abs in pmf:
2399 if abs in pmf:
2399 if mfentry:
2400 if mfentry:
2400 # if version of file is same in parent and target
2401 # if version of file is same in parent and target
2401 # manifests, do nothing
2402 # manifests, do nothing
2402 if pmf[abs] != mfentry:
2403 if pmf[abs] != mfentry:
2403 handle(revert, False)
2404 handle(revert, False)
2404 else:
2405 else:
2405 handle(remove, False)
2406 handle(remove, False)
2406
2407
2407 if not opts.get('dry_run'):
2408 if not opts.get('dry_run'):
2408 repo.dirstate.forget(forget[0])
2409 repo.dirstate.forget(forget[0])
2409 r = hg.revert(repo, node, update.has_key, wlock)
2410 r = hg.revert(repo, node, update.has_key, wlock)
2410 repo.dirstate.update(add[0], 'a')
2411 repo.dirstate.update(add[0], 'a')
2411 repo.dirstate.update(undelete[0], 'n')
2412 repo.dirstate.update(undelete[0], 'n')
2412 repo.dirstate.update(remove[0], 'r')
2413 repo.dirstate.update(remove[0], 'r')
2413 return r
2414 return r
2414
2415
2415 def rollback(ui, repo):
2416 def rollback(ui, repo):
2416 """roll back the last transaction in this repository
2417 """roll back the last transaction in this repository
2417
2418
2418 Roll back the last transaction in this repository, restoring the
2419 Roll back the last transaction in this repository, restoring the
2419 project to its state prior to the transaction.
2420 project to its state prior to the transaction.
2420
2421
2421 Transactions are used to encapsulate the effects of all commands
2422 Transactions are used to encapsulate the effects of all commands
2422 that create new changesets or propagate existing changesets into a
2423 that create new changesets or propagate existing changesets into a
2423 repository. For example, the following commands are transactional,
2424 repository. For example, the following commands are transactional,
2424 and their effects can be rolled back:
2425 and their effects can be rolled back:
2425
2426
2426 commit
2427 commit
2427 import
2428 import
2428 pull
2429 pull
2429 push (with this repository as destination)
2430 push (with this repository as destination)
2430 unbundle
2431 unbundle
2431
2432
2432 This command should be used with care. There is only one level of
2433 This command should be used with care. There is only one level of
2433 rollback, and there is no way to undo a rollback.
2434 rollback, and there is no way to undo a rollback.
2434
2435
2435 This command is not intended for use on public repositories. Once
2436 This command is not intended for use on public repositories. Once
2436 changes are visible for pull by other users, rolling a transaction
2437 changes are visible for pull by other users, rolling a transaction
2437 back locally is ineffective (someone else may already have pulled
2438 back locally is ineffective (someone else may already have pulled
2438 the changes). Furthermore, a race is possible with readers of the
2439 the changes). Furthermore, a race is possible with readers of the
2439 repository; for example an in-progress pull from the repository
2440 repository; for example an in-progress pull from the repository
2440 may fail if a rollback is performed.
2441 may fail if a rollback is performed.
2441 """
2442 """
2442 repo.rollback()
2443 repo.rollback()
2443
2444
2444 def root(ui, repo):
2445 def root(ui, repo):
2445 """print the root (top) of the current working dir
2446 """print the root (top) of the current working dir
2446
2447
2447 Print the root directory of the current repository.
2448 Print the root directory of the current repository.
2448 """
2449 """
2449 ui.write(repo.root + "\n")
2450 ui.write(repo.root + "\n")
2450
2451
2451 def serve(ui, repo, **opts):
2452 def serve(ui, repo, **opts):
2452 """export the repository via HTTP
2453 """export the repository via HTTP
2453
2454
2454 Start a local HTTP repository browser and pull server.
2455 Start a local HTTP repository browser and pull server.
2455
2456
2456 By default, the server logs accesses to stdout and errors to
2457 By default, the server logs accesses to stdout and errors to
2457 stderr. Use the "-A" and "-E" options to log to files.
2458 stderr. Use the "-A" and "-E" options to log to files.
2458 """
2459 """
2459
2460
2460 if opts["stdio"]:
2461 if opts["stdio"]:
2461 if repo is None:
2462 if repo is None:
2462 raise hg.RepoError(_('no repo found'))
2463 raise hg.RepoError(_('no repo found'))
2463 s = sshserver.sshserver(ui, repo)
2464 s = sshserver.sshserver(ui, repo)
2464 s.serve_forever()
2465 s.serve_forever()
2465
2466
2466 optlist = ("name templates style address port ipv6"
2467 optlist = ("name templates style address port ipv6"
2467 " accesslog errorlog webdir_conf")
2468 " accesslog errorlog webdir_conf")
2468 for o in optlist.split():
2469 for o in optlist.split():
2469 if opts[o]:
2470 if opts[o]:
2470 ui.setconfig("web", o, opts[o])
2471 ui.setconfig("web", o, opts[o])
2471
2472
2472 if repo is None and not ui.config("web", "webdir_conf"):
2473 if repo is None and not ui.config("web", "webdir_conf"):
2473 raise hg.RepoError(_('no repo found'))
2474 raise hg.RepoError(_('no repo found'))
2474
2475
2475 if opts['daemon'] and not opts['daemon_pipefds']:
2476 if opts['daemon'] and not opts['daemon_pipefds']:
2476 rfd, wfd = os.pipe()
2477 rfd, wfd = os.pipe()
2477 args = sys.argv[:]
2478 args = sys.argv[:]
2478 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2479 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2479 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2480 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2480 args[0], args)
2481 args[0], args)
2481 os.close(wfd)
2482 os.close(wfd)
2482 os.read(rfd, 1)
2483 os.read(rfd, 1)
2483 os._exit(0)
2484 os._exit(0)
2484
2485
2485 try:
2486 try:
2486 httpd = hgweb.server.create_server(ui, repo)
2487 httpd = hgweb.server.create_server(ui, repo)
2487 except socket.error, inst:
2488 except socket.error, inst:
2488 raise util.Abort(_('cannot start server: ') + inst.args[1])
2489 raise util.Abort(_('cannot start server: ') + inst.args[1])
2489
2490
2490 if ui.verbose:
2491 if ui.verbose:
2491 addr, port = httpd.socket.getsockname()
2492 addr, port = httpd.socket.getsockname()
2492 if addr == '0.0.0.0':
2493 if addr == '0.0.0.0':
2493 addr = socket.gethostname()
2494 addr = socket.gethostname()
2494 else:
2495 else:
2495 try:
2496 try:
2496 addr = socket.gethostbyaddr(addr)[0]
2497 addr = socket.gethostbyaddr(addr)[0]
2497 except socket.error:
2498 except socket.error:
2498 pass
2499 pass
2499 if port != 80:
2500 if port != 80:
2500 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2501 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2501 else:
2502 else:
2502 ui.status(_('listening at http://%s/\n') % addr)
2503 ui.status(_('listening at http://%s/\n') % addr)
2503
2504
2504 if opts['pid_file']:
2505 if opts['pid_file']:
2505 fp = open(opts['pid_file'], 'w')
2506 fp = open(opts['pid_file'], 'w')
2506 fp.write(str(os.getpid()) + '\n')
2507 fp.write(str(os.getpid()) + '\n')
2507 fp.close()
2508 fp.close()
2508
2509
2509 if opts['daemon_pipefds']:
2510 if opts['daemon_pipefds']:
2510 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2511 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2511 os.close(rfd)
2512 os.close(rfd)
2512 os.write(wfd, 'y')
2513 os.write(wfd, 'y')
2513 os.close(wfd)
2514 os.close(wfd)
2514 sys.stdout.flush()
2515 sys.stdout.flush()
2515 sys.stderr.flush()
2516 sys.stderr.flush()
2516 fd = os.open(util.nulldev, os.O_RDWR)
2517 fd = os.open(util.nulldev, os.O_RDWR)
2517 if fd != 0: os.dup2(fd, 0)
2518 if fd != 0: os.dup2(fd, 0)
2518 if fd != 1: os.dup2(fd, 1)
2519 if fd != 1: os.dup2(fd, 1)
2519 if fd != 2: os.dup2(fd, 2)
2520 if fd != 2: os.dup2(fd, 2)
2520 if fd not in (0, 1, 2): os.close(fd)
2521 if fd not in (0, 1, 2): os.close(fd)
2521
2522
2522 httpd.serve_forever()
2523 httpd.serve_forever()
2523
2524
2524 def status(ui, repo, *pats, **opts):
2525 def status(ui, repo, *pats, **opts):
2525 """show changed files in the working directory
2526 """show changed files in the working directory
2526
2527
2527 Show status of files in the repository. If names are given, only
2528 Show status of files in the repository. If names are given, only
2528 files that match are shown. Files that are clean or ignored, are
2529 files that match are shown. Files that are clean or ignored, are
2529 not listed unless -c (clean), -i (ignored) or -A is given.
2530 not listed unless -c (clean), -i (ignored) or -A is given.
2530
2531
2531 The codes used to show the status of files are:
2532 The codes used to show the status of files are:
2532 M = modified
2533 M = modified
2533 A = added
2534 A = added
2534 R = removed
2535 R = removed
2535 C = clean
2536 C = clean
2536 ! = deleted, but still tracked
2537 ! = deleted, but still tracked
2537 ? = not tracked
2538 ? = not tracked
2538 I = ignored (not shown by default)
2539 I = ignored (not shown by default)
2539 = the previous added file was copied from here
2540 = the previous added file was copied from here
2540 """
2541 """
2541
2542
2542 all = opts['all']
2543 all = opts['all']
2543
2544
2544 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2545 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2545 cwd = (pats and repo.getcwd()) or ''
2546 cwd = (pats and repo.getcwd()) or ''
2546 modified, added, removed, deleted, unknown, ignored, clean = [
2547 modified, added, removed, deleted, unknown, ignored, clean = [
2547 [util.pathto(cwd, x) for x in n]
2548 [util.pathto(cwd, x) for x in n]
2548 for n in repo.status(files=files, match=matchfn,
2549 for n in repo.status(files=files, match=matchfn,
2549 list_ignored=all or opts['ignored'],
2550 list_ignored=all or opts['ignored'],
2550 list_clean=all or opts['clean'])]
2551 list_clean=all or opts['clean'])]
2551
2552
2552 changetypes = (('modified', 'M', modified),
2553 changetypes = (('modified', 'M', modified),
2553 ('added', 'A', added),
2554 ('added', 'A', added),
2554 ('removed', 'R', removed),
2555 ('removed', 'R', removed),
2555 ('deleted', '!', deleted),
2556 ('deleted', '!', deleted),
2556 ('unknown', '?', unknown),
2557 ('unknown', '?', unknown),
2557 ('ignored', 'I', ignored))
2558 ('ignored', 'I', ignored))
2558
2559
2559 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2560 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2560
2561
2561 end = opts['print0'] and '\0' or '\n'
2562 end = opts['print0'] and '\0' or '\n'
2562
2563
2563 for opt, char, changes in ([ct for ct in explicit_changetypes
2564 for opt, char, changes in ([ct for ct in explicit_changetypes
2564 if all or opts[ct[0]]]
2565 if all or opts[ct[0]]]
2565 or changetypes):
2566 or changetypes):
2566 if opts['no_status']:
2567 if opts['no_status']:
2567 format = "%%s%s" % end
2568 format = "%%s%s" % end
2568 else:
2569 else:
2569 format = "%s %%s%s" % (char, end)
2570 format = "%s %%s%s" % (char, end)
2570
2571
2571 for f in changes:
2572 for f in changes:
2572 ui.write(format % f)
2573 ui.write(format % f)
2573 if ((all or opts.get('copies')) and not opts.get('no_status')
2574 if ((all or opts.get('copies')) and not opts.get('no_status')
2574 and opt == 'added' and repo.dirstate.copies.has_key(f)):
2575 and opt == 'added' and repo.dirstate.copies.has_key(f)):
2575 ui.write(' %s%s' % (repo.dirstate.copies[f], end))
2576 ui.write(' %s%s' % (repo.dirstate.copies[f], end))
2576
2577
2577 def tag(ui, repo, name, rev_=None, **opts):
2578 def tag(ui, repo, name, rev_=None, **opts):
2578 """add a tag for the current tip or a given revision
2579 """add a tag for the current tip or a given revision
2579
2580
2580 Name a particular revision using <name>.
2581 Name a particular revision using <name>.
2581
2582
2582 Tags are used to name particular revisions of the repository and are
2583 Tags are used to name particular revisions of the repository and are
2583 very useful to compare different revision, to go back to significant
2584 very useful to compare different revision, to go back to significant
2584 earlier versions or to mark branch points as releases, etc.
2585 earlier versions or to mark branch points as releases, etc.
2585
2586
2586 If no revision is given, the parent of the working directory is used.
2587 If no revision is given, the parent of the working directory is used.
2587
2588
2588 To facilitate version control, distribution, and merging of tags,
2589 To facilitate version control, distribution, and merging of tags,
2589 they are stored as a file named ".hgtags" which is managed
2590 they are stored as a file named ".hgtags" which is managed
2590 similarly to other project files and can be hand-edited if
2591 similarly to other project files and can be hand-edited if
2591 necessary. The file '.hg/localtags' is used for local tags (not
2592 necessary. The file '.hg/localtags' is used for local tags (not
2592 shared among repositories).
2593 shared among repositories).
2593 """
2594 """
2594 if name in ['tip', '.']:
2595 if name in ['tip', '.']:
2595 raise util.Abort(_("the name '%s' is reserved") % name)
2596 raise util.Abort(_("the name '%s' is reserved") % name)
2596 if rev_ is not None:
2597 if rev_ is not None:
2597 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2598 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2598 "please use 'hg tag [-r REV] NAME' instead\n"))
2599 "please use 'hg tag [-r REV] NAME' instead\n"))
2599 if opts['rev']:
2600 if opts['rev']:
2600 raise util.Abort(_("use only one form to specify the revision"))
2601 raise util.Abort(_("use only one form to specify the revision"))
2601 if opts['rev']:
2602 if opts['rev']:
2602 rev_ = opts['rev']
2603 rev_ = opts['rev']
2603 if rev_:
2604 if rev_:
2604 r = hex(repo.lookup(rev_))
2605 r = hex(repo.lookup(rev_))
2605 else:
2606 else:
2606 p1, p2 = repo.dirstate.parents()
2607 p1, p2 = repo.dirstate.parents()
2607 if p1 == nullid:
2608 if p1 == nullid:
2608 raise util.Abort(_('no revision to tag'))
2609 raise util.Abort(_('no revision to tag'))
2609 if p2 != nullid:
2610 if p2 != nullid:
2610 raise util.Abort(_('outstanding uncommitted merges'))
2611 raise util.Abort(_('outstanding uncommitted merges'))
2611 r = hex(p1)
2612 r = hex(p1)
2612
2613
2613 repo.tag(name, r, opts['local'], opts['message'], opts['user'],
2614 repo.tag(name, r, opts['local'], opts['message'], opts['user'],
2614 opts['date'])
2615 opts['date'])
2615
2616
2616 def tags(ui, repo):
2617 def tags(ui, repo):
2617 """list repository tags
2618 """list repository tags
2618
2619
2619 List the repository tags.
2620 List the repository tags.
2620
2621
2621 This lists both regular and local tags.
2622 This lists both regular and local tags.
2622 """
2623 """
2623
2624
2624 l = repo.tagslist()
2625 l = repo.tagslist()
2625 l.reverse()
2626 l.reverse()
2626 for t, n in l:
2627 for t, n in l:
2627 try:
2628 try:
2628 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2629 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2629 except KeyError:
2630 except KeyError:
2630 r = " ?:?"
2631 r = " ?:?"
2631 if ui.quiet:
2632 if ui.quiet:
2632 ui.write("%s\n" % t)
2633 ui.write("%s\n" % t)
2633 else:
2634 else:
2634 ui.write("%-30s %s\n" % (t, r))
2635 ui.write("%-30s %s\n" % (t, r))
2635
2636
2636 def tip(ui, repo, **opts):
2637 def tip(ui, repo, **opts):
2637 """show the tip revision
2638 """show the tip revision
2638
2639
2639 Show the tip revision.
2640 Show the tip revision.
2640 """
2641 """
2641 n = repo.changelog.tip()
2642 n = repo.changelog.tip()
2642 br = None
2643 br = None
2643 if opts['branches']:
2644 if opts['branches']:
2644 br = repo.branchlookup([n])
2645 br = repo.branchlookup([n])
2645 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2646 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2646 if opts['patch']:
2647 if opts['patch']:
2647 patch.diff(repo, repo.changelog.parents(n)[0], n)
2648 patch.diff(repo, repo.changelog.parents(n)[0], n)
2648
2649
2649 def unbundle(ui, repo, fname, **opts):
2650 def unbundle(ui, repo, fname, **opts):
2650 """apply a changegroup file
2651 """apply a changegroup file
2651
2652
2652 Apply a compressed changegroup file generated by the bundle
2653 Apply a compressed changegroup file generated by the bundle
2653 command.
2654 command.
2654 """
2655 """
2655 f = urllib.urlopen(fname)
2656 f = urllib.urlopen(fname)
2656
2657
2657 header = f.read(6)
2658 header = f.read(6)
2658 if not header.startswith("HG"):
2659 if not header.startswith("HG"):
2659 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2660 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2660 elif not header.startswith("HG10"):
2661 elif not header.startswith("HG10"):
2661 raise util.Abort(_("%s: unknown bundle version") % fname)
2662 raise util.Abort(_("%s: unknown bundle version") % fname)
2662 elif header == "HG10BZ":
2663 elif header == "HG10BZ":
2663 def generator(f):
2664 def generator(f):
2664 zd = bz2.BZ2Decompressor()
2665 zd = bz2.BZ2Decompressor()
2665 zd.decompress("BZ")
2666 zd.decompress("BZ")
2666 for chunk in f:
2667 for chunk in f:
2667 yield zd.decompress(chunk)
2668 yield zd.decompress(chunk)
2668 elif header == "HG10UN":
2669 elif header == "HG10UN":
2669 def generator(f):
2670 def generator(f):
2670 for chunk in f:
2671 for chunk in f:
2671 yield chunk
2672 yield chunk
2672 else:
2673 else:
2673 raise util.Abort(_("%s: unknown bundle compression type")
2674 raise util.Abort(_("%s: unknown bundle compression type")
2674 % fname)
2675 % fname)
2675 gen = generator(util.filechunkiter(f, 4096))
2676 gen = generator(util.filechunkiter(f, 4096))
2676 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle',
2677 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle',
2677 'bundle:' + fname)
2678 'bundle:' + fname)
2678 return postincoming(ui, repo, modheads, opts['update'])
2679 return postincoming(ui, repo, modheads, opts['update'])
2679
2680
2680 def undo(ui, repo):
2681 def undo(ui, repo):
2681 """undo the last commit or pull (DEPRECATED)
2682 """undo the last commit or pull (DEPRECATED)
2682
2683
2683 (DEPRECATED)
2684 (DEPRECATED)
2684 This command is now deprecated and will be removed in a future
2685 This command is now deprecated and will be removed in a future
2685 release. Please use the rollback command instead. For usage
2686 release. Please use the rollback command instead. For usage
2686 instructions, see the rollback command.
2687 instructions, see the rollback command.
2687 """
2688 """
2688 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2689 ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
2689 repo.rollback()
2690 repo.rollback()
2690
2691
2691 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2692 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2692 branch=None):
2693 branch=None):
2693 """update or merge working directory
2694 """update or merge working directory
2694
2695
2695 Update the working directory to the specified revision.
2696 Update the working directory to the specified revision.
2696
2697
2697 If there are no outstanding changes in the working directory and
2698 If there are no outstanding changes in the working directory and
2698 there is a linear relationship between the current version and the
2699 there is a linear relationship between the current version and the
2699 requested version, the result is the requested version.
2700 requested version, the result is the requested version.
2700
2701
2701 To merge the working directory with another revision, use the
2702 To merge the working directory with another revision, use the
2702 merge command.
2703 merge command.
2703
2704
2704 By default, update will refuse to run if doing so would require
2705 By default, update will refuse to run if doing so would require
2705 merging or discarding local changes.
2706 merging or discarding local changes.
2706 """
2707 """
2707 node = _lookup(repo, node, branch)
2708 node = _lookup(repo, node, branch)
2708 if merge:
2709 if merge:
2709 ui.warn(_('(the -m/--merge option is deprecated; '
2710 ui.warn(_('(the -m/--merge option is deprecated; '
2710 'use the merge command instead)\n'))
2711 'use the merge command instead)\n'))
2711 return hg.merge(repo, node, force=force)
2712 return hg.merge(repo, node, force=force)
2712 elif clean:
2713 elif clean:
2713 return hg.clean(repo, node)
2714 return hg.clean(repo, node)
2714 else:
2715 else:
2715 return hg.update(repo, node)
2716 return hg.update(repo, node)
2716
2717
2717 def _lookup(repo, node, branch=None):
2718 def _lookup(repo, node, branch=None):
2718 if branch:
2719 if branch:
2719 br = repo.branchlookup(branch=branch)
2720 br = repo.branchlookup(branch=branch)
2720 found = []
2721 found = []
2721 for x in br:
2722 for x in br:
2722 if branch in br[x]:
2723 if branch in br[x]:
2723 found.append(x)
2724 found.append(x)
2724 if len(found) > 1:
2725 if len(found) > 1:
2725 repo.ui.warn(_("Found multiple heads for %s\n") % branch)
2726 repo.ui.warn(_("Found multiple heads for %s\n") % branch)
2726 for x in found:
2727 for x in found:
2727 show_changeset(ui, repo, {}).show(changenode=x, brinfo=br)
2728 show_changeset(ui, repo, {}).show(changenode=x, brinfo=br)
2728 raise util.Abort("")
2729 raise util.Abort("")
2729 if len(found) == 1:
2730 if len(found) == 1:
2730 node = found[0]
2731 node = found[0]
2731 repo.ui.warn(_("Using head %s for branch %s\n")
2732 repo.ui.warn(_("Using head %s for branch %s\n")
2732 % (short(node), branch))
2733 % (short(node), branch))
2733 else:
2734 else:
2734 raise util.Abort(_("branch %s not found\n") % (branch))
2735 raise util.Abort(_("branch %s not found\n") % (branch))
2735 else:
2736 else:
2736 node = node and repo.lookup(node) or repo.changelog.tip()
2737 node = node and repo.lookup(node) or repo.changelog.tip()
2737 return node
2738 return node
2738
2739
2739 def verify(ui, repo):
2740 def verify(ui, repo):
2740 """verify the integrity of the repository
2741 """verify the integrity of the repository
2741
2742
2742 Verify the integrity of the current repository.
2743 Verify the integrity of the current repository.
2743
2744
2744 This will perform an extensive check of the repository's
2745 This will perform an extensive check of the repository's
2745 integrity, validating the hashes and checksums of each entry in
2746 integrity, validating the hashes and checksums of each entry in
2746 the changelog, manifest, and tracked files, as well as the
2747 the changelog, manifest, and tracked files, as well as the
2747 integrity of their crosslinks and indices.
2748 integrity of their crosslinks and indices.
2748 """
2749 """
2749 return hg.verify(repo)
2750 return hg.verify(repo)
2750
2751
2751 # Command options and aliases are listed here, alphabetically
2752 # Command options and aliases are listed here, alphabetically
2752
2753
2753 table = {
2754 table = {
2754 "^add":
2755 "^add":
2755 (add,
2756 (add,
2756 [('I', 'include', [], _('include names matching the given patterns')),
2757 [('I', 'include', [], _('include names matching the given patterns')),
2757 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2758 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2758 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2759 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2759 _('hg add [OPTION]... [FILE]...')),
2760 _('hg add [OPTION]... [FILE]...')),
2760 "debugaddremove|addremove":
2761 "debugaddremove|addremove":
2761 (addremove,
2762 (addremove,
2762 [('I', 'include', [], _('include names matching the given patterns')),
2763 [('I', 'include', [], _('include names matching the given patterns')),
2763 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2764 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2764 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2765 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2765 _('hg addremove [OPTION]... [FILE]...')),
2766 _('hg addremove [OPTION]... [FILE]...')),
2766 "^annotate":
2767 "^annotate":
2767 (annotate,
2768 (annotate,
2768 [('r', 'rev', '', _('annotate the specified revision')),
2769 [('r', 'rev', '', _('annotate the specified revision')),
2769 ('a', 'text', None, _('treat all files as text')),
2770 ('a', 'text', None, _('treat all files as text')),
2770 ('u', 'user', None, _('list the author')),
2771 ('u', 'user', None, _('list the author')),
2771 ('d', 'date', None, _('list the date')),
2772 ('d', 'date', None, _('list the date')),
2772 ('n', 'number', None, _('list the revision number (default)')),
2773 ('n', 'number', None, _('list the revision number (default)')),
2773 ('c', 'changeset', None, _('list the changeset')),
2774 ('c', 'changeset', None, _('list the changeset')),
2774 ('I', 'include', [], _('include names matching the given patterns')),
2775 ('I', 'include', [], _('include names matching the given patterns')),
2775 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2776 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2776 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2777 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2777 "archive":
2778 "archive":
2778 (archive,
2779 (archive,
2779 [('', 'no-decode', None, _('do not pass files through decoders')),
2780 [('', 'no-decode', None, _('do not pass files through decoders')),
2780 ('p', 'prefix', '', _('directory prefix for files in archive')),
2781 ('p', 'prefix', '', _('directory prefix for files in archive')),
2781 ('r', 'rev', '', _('revision to distribute')),
2782 ('r', 'rev', '', _('revision to distribute')),
2782 ('t', 'type', '', _('type of distribution to create')),
2783 ('t', 'type', '', _('type of distribution to create')),
2783 ('I', 'include', [], _('include names matching the given patterns')),
2784 ('I', 'include', [], _('include names matching the given patterns')),
2784 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2785 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2785 _('hg archive [OPTION]... DEST')),
2786 _('hg archive [OPTION]... DEST')),
2786 "backout":
2787 "backout":
2787 (backout,
2788 (backout,
2788 [('', 'merge', None,
2789 [('', 'merge', None,
2789 _('merge with old dirstate parent after backout')),
2790 _('merge with old dirstate parent after backout')),
2790 ('m', 'message', '', _('use <text> as commit message')),
2791 ('m', 'message', '', _('use <text> as commit message')),
2791 ('l', 'logfile', '', _('read commit message from <file>')),
2792 ('l', 'logfile', '', _('read commit message from <file>')),
2792 ('d', 'date', '', _('record datecode as commit date')),
2793 ('d', 'date', '', _('record datecode as commit date')),
2793 ('', 'parent', '', _('parent to choose when backing out merge')),
2794 ('', 'parent', '', _('parent to choose when backing out merge')),
2794 ('u', 'user', '', _('record user as committer')),
2795 ('u', 'user', '', _('record user as committer')),
2795 ('I', 'include', [], _('include names matching the given patterns')),
2796 ('I', 'include', [], _('include names matching the given patterns')),
2796 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2797 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2797 _('hg backout [OPTION]... REV')),
2798 _('hg backout [OPTION]... REV')),
2798 "bundle":
2799 "bundle":
2799 (bundle,
2800 (bundle,
2800 [('f', 'force', None,
2801 [('f', 'force', None,
2801 _('run even when remote repository is unrelated'))],
2802 _('run even when remote repository is unrelated'))],
2802 _('hg bundle FILE DEST')),
2803 _('hg bundle FILE DEST')),
2803 "cat":
2804 "cat":
2804 (cat,
2805 (cat,
2805 [('o', 'output', '', _('print output to file with formatted name')),
2806 [('o', 'output', '', _('print output to file with formatted name')),
2806 ('r', 'rev', '', _('print the given revision')),
2807 ('r', 'rev', '', _('print the given revision')),
2807 ('I', 'include', [], _('include names matching the given patterns')),
2808 ('I', 'include', [], _('include names matching the given patterns')),
2808 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2809 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2809 _('hg cat [OPTION]... FILE...')),
2810 _('hg cat [OPTION]... FILE...')),
2810 "^clone":
2811 "^clone":
2811 (clone,
2812 (clone,
2812 [('U', 'noupdate', None, _('do not update the new working directory')),
2813 [('U', 'noupdate', None, _('do not update the new working directory')),
2813 ('r', 'rev', [],
2814 ('r', 'rev', [],
2814 _('a changeset you would like to have after cloning')),
2815 _('a changeset you would like to have after cloning')),
2815 ('', 'pull', None, _('use pull protocol to copy metadata')),
2816 ('', 'pull', None, _('use pull protocol to copy metadata')),
2816 ('', 'uncompressed', None,
2817 ('', 'uncompressed', None,
2817 _('use uncompressed transfer (fast over LAN)')),
2818 _('use uncompressed transfer (fast over LAN)')),
2818 ('e', 'ssh', '', _('specify ssh command to use')),
2819 ('e', 'ssh', '', _('specify ssh command to use')),
2819 ('', 'remotecmd', '',
2820 ('', 'remotecmd', '',
2820 _('specify hg command to run on the remote side'))],
2821 _('specify hg command to run on the remote side'))],
2821 _('hg clone [OPTION]... SOURCE [DEST]')),
2822 _('hg clone [OPTION]... SOURCE [DEST]')),
2822 "^commit|ci":
2823 "^commit|ci":
2823 (commit,
2824 (commit,
2824 [('A', 'addremove', None,
2825 [('A', 'addremove', None,
2825 _('mark new/missing files as added/removed before committing')),
2826 _('mark new/missing files as added/removed before committing')),
2826 ('m', 'message', '', _('use <text> as commit message')),
2827 ('m', 'message', '', _('use <text> as commit message')),
2827 ('l', 'logfile', '', _('read the commit message from <file>')),
2828 ('l', 'logfile', '', _('read the commit message from <file>')),
2828 ('d', 'date', '', _('record datecode as commit date')),
2829 ('d', 'date', '', _('record datecode as commit date')),
2829 ('u', 'user', '', _('record user as commiter')),
2830 ('u', 'user', '', _('record user as commiter')),
2830 ('I', 'include', [], _('include names matching the given patterns')),
2831 ('I', 'include', [], _('include names matching the given patterns')),
2831 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2832 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2832 _('hg commit [OPTION]... [FILE]...')),
2833 _('hg commit [OPTION]... [FILE]...')),
2833 "copy|cp":
2834 "copy|cp":
2834 (copy,
2835 (copy,
2835 [('A', 'after', None, _('record a copy that has already occurred')),
2836 [('A', 'after', None, _('record a copy that has already occurred')),
2836 ('f', 'force', None,
2837 ('f', 'force', None,
2837 _('forcibly copy over an existing managed file')),
2838 _('forcibly copy over an existing managed file')),
2838 ('I', 'include', [], _('include names matching the given patterns')),
2839 ('I', 'include', [], _('include names matching the given patterns')),
2839 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2840 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2840 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2841 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2841 _('hg copy [OPTION]... [SOURCE]... DEST')),
2842 _('hg copy [OPTION]... [SOURCE]... DEST')),
2842 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2843 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2843 "debugcomplete":
2844 "debugcomplete":
2844 (debugcomplete,
2845 (debugcomplete,
2845 [('o', 'options', None, _('show the command options'))],
2846 [('o', 'options', None, _('show the command options'))],
2846 _('debugcomplete [-o] CMD')),
2847 _('debugcomplete [-o] CMD')),
2847 "debugrebuildstate":
2848 "debugrebuildstate":
2848 (debugrebuildstate,
2849 (debugrebuildstate,
2849 [('r', 'rev', '', _('revision to rebuild to'))],
2850 [('r', 'rev', '', _('revision to rebuild to'))],
2850 _('debugrebuildstate [-r REV] [REV]')),
2851 _('debugrebuildstate [-r REV] [REV]')),
2851 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2852 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2852 "debugconfig": (debugconfig, [], _('debugconfig [NAME]...')),
2853 "debugconfig": (debugconfig, [], _('debugconfig [NAME]...')),
2853 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2854 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2854 "debugstate": (debugstate, [], _('debugstate')),
2855 "debugstate": (debugstate, [], _('debugstate')),
2855 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2856 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2856 "debugindex": (debugindex, [], _('debugindex FILE')),
2857 "debugindex": (debugindex, [], _('debugindex FILE')),
2857 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2858 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2858 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2859 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2859 "debugwalk":
2860 "debugwalk":
2860 (debugwalk,
2861 (debugwalk,
2861 [('I', 'include', [], _('include names matching the given patterns')),
2862 [('I', 'include', [], _('include names matching the given patterns')),
2862 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2863 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2863 _('debugwalk [OPTION]... [FILE]...')),
2864 _('debugwalk [OPTION]... [FILE]...')),
2864 "^diff":
2865 "^diff":
2865 (diff,
2866 (diff,
2866 [('r', 'rev', [], _('revision')),
2867 [('r', 'rev', [], _('revision')),
2867 ('a', 'text', None, _('treat all files as text')),
2868 ('a', 'text', None, _('treat all files as text')),
2868 ('p', 'show-function', None,
2869 ('p', 'show-function', None,
2869 _('show which function each change is in')),
2870 _('show which function each change is in')),
2870 ('w', 'ignore-all-space', None,
2871 ('w', 'ignore-all-space', None,
2871 _('ignore white space when comparing lines')),
2872 _('ignore white space when comparing lines')),
2872 ('b', 'ignore-space-change', None,
2873 ('b', 'ignore-space-change', None,
2873 _('ignore changes in the amount of white space')),
2874 _('ignore changes in the amount of white space')),
2874 ('B', 'ignore-blank-lines', None,
2875 ('B', 'ignore-blank-lines', None,
2875 _('ignore changes whose lines are all blank')),
2876 _('ignore changes whose lines are all blank')),
2876 ('I', 'include', [], _('include names matching the given patterns')),
2877 ('I', 'include', [], _('include names matching the given patterns')),
2877 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2878 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2878 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2879 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2879 "^export":
2880 "^export":
2880 (export,
2881 (export,
2881 [('o', 'output', '', _('print output to file with formatted name')),
2882 [('o', 'output', '', _('print output to file with formatted name')),
2882 ('a', 'text', None, _('treat all files as text')),
2883 ('a', 'text', None, _('treat all files as text')),
2883 ('', 'switch-parent', None, _('diff against the second parent'))],
2884 ('', 'switch-parent', None, _('diff against the second parent'))],
2884 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2885 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2885 "debugforget|forget":
2886 "debugforget|forget":
2886 (forget,
2887 (forget,
2887 [('I', 'include', [], _('include names matching the given patterns')),
2888 [('I', 'include', [], _('include names matching the given patterns')),
2888 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2889 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2889 _('hg forget [OPTION]... FILE...')),
2890 _('hg forget [OPTION]... FILE...')),
2890 "grep":
2891 "grep":
2891 (grep,
2892 (grep,
2892 [('0', 'print0', None, _('end fields with NUL')),
2893 [('0', 'print0', None, _('end fields with NUL')),
2893 ('', 'all', None, _('print all revisions that match')),
2894 ('', 'all', None, _('print all revisions that match')),
2894 ('f', 'follow', None,
2895 ('f', 'follow', None,
2895 _('follow changeset history, or file history across copies and renames')),
2896 _('follow changeset history, or file history across copies and renames')),
2896 ('i', 'ignore-case', None, _('ignore case when matching')),
2897 ('i', 'ignore-case', None, _('ignore case when matching')),
2897 ('l', 'files-with-matches', None,
2898 ('l', 'files-with-matches', None,
2898 _('print only filenames and revs that match')),
2899 _('print only filenames and revs that match')),
2899 ('n', 'line-number', None, _('print matching line numbers')),
2900 ('n', 'line-number', None, _('print matching line numbers')),
2900 ('r', 'rev', [], _('search in given revision range')),
2901 ('r', 'rev', [], _('search in given revision range')),
2901 ('u', 'user', None, _('print user who committed change')),
2902 ('u', 'user', None, _('print user who committed change')),
2902 ('I', 'include', [], _('include names matching the given patterns')),
2903 ('I', 'include', [], _('include names matching the given patterns')),
2903 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2904 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2904 _('hg grep [OPTION]... PATTERN [FILE]...')),
2905 _('hg grep [OPTION]... PATTERN [FILE]...')),
2905 "heads":
2906 "heads":
2906 (heads,
2907 (heads,
2907 [('b', 'branches', None, _('show branches')),
2908 [('b', 'branches', None, _('show branches')),
2908 ('', 'style', '', _('display using template map file')),
2909 ('', 'style', '', _('display using template map file')),
2909 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2910 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2910 ('', 'template', '', _('display with template'))],
2911 ('', 'template', '', _('display with template'))],
2911 _('hg heads [-b] [-r <rev>]')),
2912 _('hg heads [-b] [-r <rev>]')),
2912 "help": (help_, [], _('hg help [COMMAND]')),
2913 "help": (help_, [], _('hg help [COMMAND]')),
2913 "identify|id": (identify, [], _('hg identify')),
2914 "identify|id": (identify, [], _('hg identify')),
2914 "import|patch":
2915 "import|patch":
2915 (import_,
2916 (import_,
2916 [('p', 'strip', 1,
2917 [('p', 'strip', 1,
2917 _('directory strip option for patch. This has the same\n'
2918 _('directory strip option for patch. This has the same\n'
2918 'meaning as the corresponding patch option')),
2919 'meaning as the corresponding patch option')),
2919 ('m', 'message', '', _('use <text> as commit message')),
2920 ('m', 'message', '', _('use <text> as commit message')),
2920 ('b', 'base', '', _('base path')),
2921 ('b', 'base', '', _('base path')),
2921 ('f', 'force', None,
2922 ('f', 'force', None,
2922 _('skip check for outstanding uncommitted changes'))],
2923 _('skip check for outstanding uncommitted changes'))],
2923 _('hg import [-p NUM] [-b BASE] [-m MESSAGE] [-f] PATCH...')),
2924 _('hg import [-p NUM] [-b BASE] [-m MESSAGE] [-f] PATCH...')),
2924 "incoming|in": (incoming,
2925 "incoming|in": (incoming,
2925 [('M', 'no-merges', None, _('do not show merges')),
2926 [('M', 'no-merges', None, _('do not show merges')),
2926 ('f', 'force', None,
2927 ('f', 'force', None,
2927 _('run even when remote repository is unrelated')),
2928 _('run even when remote repository is unrelated')),
2928 ('', 'style', '', _('display using template map file')),
2929 ('', 'style', '', _('display using template map file')),
2929 ('n', 'newest-first', None, _('show newest record first')),
2930 ('n', 'newest-first', None, _('show newest record first')),
2930 ('', 'bundle', '', _('file to store the bundles into')),
2931 ('', 'bundle', '', _('file to store the bundles into')),
2931 ('p', 'patch', None, _('show patch')),
2932 ('p', 'patch', None, _('show patch')),
2932 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2933 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2933 ('', 'template', '', _('display with template')),
2934 ('', 'template', '', _('display with template')),
2934 ('e', 'ssh', '', _('specify ssh command to use')),
2935 ('e', 'ssh', '', _('specify ssh command to use')),
2935 ('', 'remotecmd', '',
2936 ('', 'remotecmd', '',
2936 _('specify hg command to run on the remote side'))],
2937 _('specify hg command to run on the remote side'))],
2937 _('hg incoming [-p] [-n] [-M] [-r REV]...'
2938 _('hg incoming [-p] [-n] [-M] [-r REV]...'
2938 ' [--bundle FILENAME] [SOURCE]')),
2939 ' [--bundle FILENAME] [SOURCE]')),
2939 "^init":
2940 "^init":
2940 (init,
2941 (init,
2941 [('e', 'ssh', '', _('specify ssh command to use')),
2942 [('e', 'ssh', '', _('specify ssh command to use')),
2942 ('', 'remotecmd', '',
2943 ('', 'remotecmd', '',
2943 _('specify hg command to run on the remote side'))],
2944 _('specify hg command to run on the remote side'))],
2944 _('hg init [-e FILE] [--remotecmd FILE] [DEST]')),
2945 _('hg init [-e FILE] [--remotecmd FILE] [DEST]')),
2945 "locate":
2946 "locate":
2946 (locate,
2947 (locate,
2947 [('r', 'rev', '', _('search the repository as it stood at rev')),
2948 [('r', 'rev', '', _('search the repository as it stood at rev')),
2948 ('0', 'print0', None,
2949 ('0', 'print0', None,
2949 _('end filenames with NUL, for use with xargs')),
2950 _('end filenames with NUL, for use with xargs')),
2950 ('f', 'fullpath', None,
2951 ('f', 'fullpath', None,
2951 _('print complete paths from the filesystem root')),
2952 _('print complete paths from the filesystem root')),
2952 ('I', 'include', [], _('include names matching the given patterns')),
2953 ('I', 'include', [], _('include names matching the given patterns')),
2953 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2954 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2954 _('hg locate [OPTION]... [PATTERN]...')),
2955 _('hg locate [OPTION]... [PATTERN]...')),
2955 "^log|history":
2956 "^log|history":
2956 (log,
2957 (log,
2957 [('b', 'branches', None, _('show branches')),
2958 [('b', 'branches', None, _('show branches')),
2958 ('f', 'follow', None,
2959 ('f', 'follow', None,
2959 _('follow changeset history, or file history across copies and renames')),
2960 _('follow changeset history, or file history across copies and renames')),
2960 ('', 'follow-first', None,
2961 ('', 'follow-first', None,
2961 _('only follow the first parent of merge changesets')),
2962 _('only follow the first parent of merge changesets')),
2962 ('k', 'keyword', [], _('search for a keyword')),
2963 ('k', 'keyword', [], _('search for a keyword')),
2963 ('l', 'limit', '', _('limit number of changes displayed')),
2964 ('l', 'limit', '', _('limit number of changes displayed')),
2964 ('r', 'rev', [], _('show the specified revision or range')),
2965 ('r', 'rev', [], _('show the specified revision or range')),
2965 ('M', 'no-merges', None, _('do not show merges')),
2966 ('M', 'no-merges', None, _('do not show merges')),
2966 ('', 'style', '', _('display using template map file')),
2967 ('', 'style', '', _('display using template map file')),
2967 ('m', 'only-merges', None, _('show only merges')),
2968 ('m', 'only-merges', None, _('show only merges')),
2968 ('p', 'patch', None, _('show patch')),
2969 ('p', 'patch', None, _('show patch')),
2969 ('', 'template', '', _('display with template')),
2970 ('', 'template', '', _('display with template')),
2970 ('I', 'include', [], _('include names matching the given patterns')),
2971 ('I', 'include', [], _('include names matching the given patterns')),
2971 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2972 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2972 _('hg log [OPTION]... [FILE]')),
2973 _('hg log [OPTION]... [FILE]')),
2973 "manifest": (manifest, [], _('hg manifest [REV]')),
2974 "manifest": (manifest, [], _('hg manifest [REV]')),
2974 "merge":
2975 "merge":
2975 (merge,
2976 (merge,
2976 [('b', 'branch', '', _('merge with head of a specific branch')),
2977 [('b', 'branch', '', _('merge with head of a specific branch')),
2977 ('f', 'force', None, _('force a merge with outstanding changes'))],
2978 ('f', 'force', None, _('force a merge with outstanding changes'))],
2978 _('hg merge [-b TAG] [-f] [REV]')),
2979 _('hg merge [-b TAG] [-f] [REV]')),
2979 "outgoing|out": (outgoing,
2980 "outgoing|out": (outgoing,
2980 [('M', 'no-merges', None, _('do not show merges')),
2981 [('M', 'no-merges', None, _('do not show merges')),
2981 ('f', 'force', None,
2982 ('f', 'force', None,
2982 _('run even when remote repository is unrelated')),
2983 _('run even when remote repository is unrelated')),
2983 ('p', 'patch', None, _('show patch')),
2984 ('p', 'patch', None, _('show patch')),
2984 ('', 'style', '', _('display using template map file')),
2985 ('', 'style', '', _('display using template map file')),
2985 ('r', 'rev', [], _('a specific revision you would like to push')),
2986 ('r', 'rev', [], _('a specific revision you would like to push')),
2986 ('n', 'newest-first', None, _('show newest record first')),
2987 ('n', 'newest-first', None, _('show newest record first')),
2987 ('', 'template', '', _('display with template')),
2988 ('', 'template', '', _('display with template')),
2988 ('e', 'ssh', '', _('specify ssh command to use')),
2989 ('e', 'ssh', '', _('specify ssh command to use')),
2989 ('', 'remotecmd', '',
2990 ('', 'remotecmd', '',
2990 _('specify hg command to run on the remote side'))],
2991 _('specify hg command to run on the remote side'))],
2991 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
2992 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
2992 "^parents":
2993 "^parents":
2993 (parents,
2994 (parents,
2994 [('b', 'branches', None, _('show branches')),
2995 [('b', 'branches', None, _('show branches')),
2995 ('r', 'rev', '', _('show parents from the specified rev')),
2996 ('r', 'rev', '', _('show parents from the specified rev')),
2996 ('', 'style', '', _('display using template map file')),
2997 ('', 'style', '', _('display using template map file')),
2997 ('', 'template', '', _('display with template'))],
2998 ('', 'template', '', _('display with template'))],
2998 _('hg parents [-b] [-r REV] [FILE]')),
2999 _('hg parents [-b] [-r REV] [FILE]')),
2999 "paths": (paths, [], _('hg paths [NAME]')),
3000 "paths": (paths, [], _('hg paths [NAME]')),
3000 "^pull":
3001 "^pull":
3001 (pull,
3002 (pull,
3002 [('u', 'update', None,
3003 [('u', 'update', None,
3003 _('update the working directory to tip after pull')),
3004 _('update the working directory to tip after pull')),
3004 ('e', 'ssh', '', _('specify ssh command to use')),
3005 ('e', 'ssh', '', _('specify ssh command to use')),
3005 ('f', 'force', None,
3006 ('f', 'force', None,
3006 _('run even when remote repository is unrelated')),
3007 _('run even when remote repository is unrelated')),
3007 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
3008 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
3008 ('', 'remotecmd', '',
3009 ('', 'remotecmd', '',
3009 _('specify hg command to run on the remote side'))],
3010 _('specify hg command to run on the remote side'))],
3010 _('hg pull [-u] [-r REV]... [-e FILE] [--remotecmd FILE] [SOURCE]')),
3011 _('hg pull [-u] [-r REV]... [-e FILE] [--remotecmd FILE] [SOURCE]')),
3011 "^push":
3012 "^push":
3012 (push,
3013 (push,
3013 [('f', 'force', None, _('force push')),
3014 [('f', 'force', None, _('force push')),
3014 ('e', 'ssh', '', _('specify ssh command to use')),
3015 ('e', 'ssh', '', _('specify ssh command to use')),
3015 ('r', 'rev', [], _('a specific revision you would like to push')),
3016 ('r', 'rev', [], _('a specific revision you would like to push')),
3016 ('', 'remotecmd', '',
3017 ('', 'remotecmd', '',
3017 _('specify hg command to run on the remote side'))],
3018 _('specify hg command to run on the remote side'))],
3018 _('hg push [-f] [-r REV]... [-e FILE] [--remotecmd FILE] [DEST]')),
3019 _('hg push [-f] [-r REV]... [-e FILE] [--remotecmd FILE] [DEST]')),
3019 "debugrawcommit|rawcommit":
3020 "debugrawcommit|rawcommit":
3020 (rawcommit,
3021 (rawcommit,
3021 [('p', 'parent', [], _('parent')),
3022 [('p', 'parent', [], _('parent')),
3022 ('d', 'date', '', _('date code')),
3023 ('d', 'date', '', _('date code')),
3023 ('u', 'user', '', _('user')),
3024 ('u', 'user', '', _('user')),
3024 ('F', 'files', '', _('file list')),
3025 ('F', 'files', '', _('file list')),
3025 ('m', 'message', '', _('commit message')),
3026 ('m', 'message', '', _('commit message')),
3026 ('l', 'logfile', '', _('commit message file'))],
3027 ('l', 'logfile', '', _('commit message file'))],
3027 _('hg debugrawcommit [OPTION]... [FILE]...')),
3028 _('hg debugrawcommit [OPTION]... [FILE]...')),
3028 "recover": (recover, [], _('hg recover')),
3029 "recover": (recover, [], _('hg recover')),
3029 "^remove|rm":
3030 "^remove|rm":
3030 (remove,
3031 (remove,
3031 [('A', 'after', None, _('record remove that has already occurred')),
3032 [('A', 'after', None, _('record remove that has already occurred')),
3032 ('f', 'force', None, _('remove file even if modified')),
3033 ('f', 'force', None, _('remove file even if modified')),
3033 ('I', 'include', [], _('include names matching the given patterns')),
3034 ('I', 'include', [], _('include names matching the given patterns')),
3034 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3035 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3035 _('hg remove [OPTION]... FILE...')),
3036 _('hg remove [OPTION]... FILE...')),
3036 "rename|mv":
3037 "rename|mv":
3037 (rename,
3038 (rename,
3038 [('A', 'after', None, _('record a rename that has already occurred')),
3039 [('A', 'after', None, _('record a rename that has already occurred')),
3039 ('f', 'force', None,
3040 ('f', 'force', None,
3040 _('forcibly copy over an existing managed file')),
3041 _('forcibly copy over an existing managed file')),
3041 ('I', 'include', [], _('include names matching the given patterns')),
3042 ('I', 'include', [], _('include names matching the given patterns')),
3042 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3043 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3043 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3044 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3044 _('hg rename [OPTION]... SOURCE... DEST')),
3045 _('hg rename [OPTION]... SOURCE... DEST')),
3045 "^revert":
3046 "^revert":
3046 (revert,
3047 (revert,
3047 [('r', 'rev', '', _('revision to revert to')),
3048 [('r', 'rev', '', _('revision to revert to')),
3048 ('', 'no-backup', None, _('do not save backup copies of files')),
3049 ('', 'no-backup', None, _('do not save backup copies of files')),
3049 ('I', 'include', [], _('include names matching given patterns')),
3050 ('I', 'include', [], _('include names matching given patterns')),
3050 ('X', 'exclude', [], _('exclude names matching given patterns')),
3051 ('X', 'exclude', [], _('exclude names matching given patterns')),
3051 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3052 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
3052 _('hg revert [-r REV] [NAME]...')),
3053 _('hg revert [-r REV] [NAME]...')),
3053 "rollback": (rollback, [], _('hg rollback')),
3054 "rollback": (rollback, [], _('hg rollback')),
3054 "root": (root, [], _('hg root')),
3055 "root": (root, [], _('hg root')),
3055 "^serve":
3056 "^serve":
3056 (serve,
3057 (serve,
3057 [('A', 'accesslog', '', _('name of access log file to write to')),
3058 [('A', 'accesslog', '', _('name of access log file to write to')),
3058 ('d', 'daemon', None, _('run server in background')),
3059 ('d', 'daemon', None, _('run server in background')),
3059 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3060 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3060 ('E', 'errorlog', '', _('name of error log file to write to')),
3061 ('E', 'errorlog', '', _('name of error log file to write to')),
3061 ('p', 'port', 0, _('port to use (default: 8000)')),
3062 ('p', 'port', 0, _('port to use (default: 8000)')),
3062 ('a', 'address', '', _('address to use')),
3063 ('a', 'address', '', _('address to use')),
3063 ('n', 'name', '',
3064 ('n', 'name', '',
3064 _('name to show in web pages (default: working dir)')),
3065 _('name to show in web pages (default: working dir)')),
3065 ('', 'webdir-conf', '', _('name of the webdir config file'
3066 ('', 'webdir-conf', '', _('name of the webdir config file'
3066 ' (serve more than one repo)')),
3067 ' (serve more than one repo)')),
3067 ('', 'pid-file', '', _('name of file to write process ID to')),
3068 ('', 'pid-file', '', _('name of file to write process ID to')),
3068 ('', 'stdio', None, _('for remote clients')),
3069 ('', 'stdio', None, _('for remote clients')),
3069 ('t', 'templates', '', _('web templates to use')),
3070 ('t', 'templates', '', _('web templates to use')),
3070 ('', 'style', '', _('template style to use')),
3071 ('', 'style', '', _('template style to use')),
3071 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3072 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3072 _('hg serve [OPTION]...')),
3073 _('hg serve [OPTION]...')),
3073 "^status|st":
3074 "^status|st":
3074 (status,
3075 (status,
3075 [('A', 'all', None, _('show status of all files')),
3076 [('A', 'all', None, _('show status of all files')),
3076 ('m', 'modified', None, _('show only modified files')),
3077 ('m', 'modified', None, _('show only modified files')),
3077 ('a', 'added', None, _('show only added files')),
3078 ('a', 'added', None, _('show only added files')),
3078 ('r', 'removed', None, _('show only removed files')),
3079 ('r', 'removed', None, _('show only removed files')),
3079 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3080 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3080 ('c', 'clean', None, _('show only files without changes')),
3081 ('c', 'clean', None, _('show only files without changes')),
3081 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3082 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3082 ('i', 'ignored', None, _('show ignored files')),
3083 ('i', 'ignored', None, _('show ignored files')),
3083 ('n', 'no-status', None, _('hide status prefix')),
3084 ('n', 'no-status', None, _('hide status prefix')),
3084 ('C', 'copies', None, _('show source of copied files')),
3085 ('C', 'copies', None, _('show source of copied files')),
3085 ('0', 'print0', None,
3086 ('0', 'print0', None,
3086 _('end filenames with NUL, for use with xargs')),
3087 _('end filenames with NUL, for use with xargs')),
3087 ('I', 'include', [], _('include names matching the given patterns')),
3088 ('I', 'include', [], _('include names matching the given patterns')),
3088 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3089 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3089 _('hg status [OPTION]... [FILE]...')),
3090 _('hg status [OPTION]... [FILE]...')),
3090 "tag":
3091 "tag":
3091 (tag,
3092 (tag,
3092 [('l', 'local', None, _('make the tag local')),
3093 [('l', 'local', None, _('make the tag local')),
3093 ('m', 'message', '', _('message for tag commit log entry')),
3094 ('m', 'message', '', _('message for tag commit log entry')),
3094 ('d', 'date', '', _('record datecode as commit date')),
3095 ('d', 'date', '', _('record datecode as commit date')),
3095 ('u', 'user', '', _('record user as commiter')),
3096 ('u', 'user', '', _('record user as commiter')),
3096 ('r', 'rev', '', _('revision to tag'))],
3097 ('r', 'rev', '', _('revision to tag'))],
3097 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3098 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3098 "tags": (tags, [], _('hg tags')),
3099 "tags": (tags, [], _('hg tags')),
3099 "tip":
3100 "tip":
3100 (tip,
3101 (tip,
3101 [('b', 'branches', None, _('show branches')),
3102 [('b', 'branches', None, _('show branches')),
3102 ('', 'style', '', _('display using template map file')),
3103 ('', 'style', '', _('display using template map file')),
3103 ('p', 'patch', None, _('show patch')),
3104 ('p', 'patch', None, _('show patch')),
3104 ('', 'template', '', _('display with template'))],
3105 ('', 'template', '', _('display with template'))],
3105 _('hg tip [-b] [-p]')),
3106 _('hg tip [-b] [-p]')),
3106 "unbundle":
3107 "unbundle":
3107 (unbundle,
3108 (unbundle,
3108 [('u', 'update', None,
3109 [('u', 'update', None,
3109 _('update the working directory to tip after unbundle'))],
3110 _('update the working directory to tip after unbundle'))],
3110 _('hg unbundle [-u] FILE')),
3111 _('hg unbundle [-u] FILE')),
3111 "debugundo|undo": (undo, [], _('hg undo')),
3112 "debugundo|undo": (undo, [], _('hg undo')),
3112 "^update|up|checkout|co":
3113 "^update|up|checkout|co":
3113 (update,
3114 (update,
3114 [('b', 'branch', '', _('checkout the head of a specific branch')),
3115 [('b', 'branch', '', _('checkout the head of a specific branch')),
3115 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3116 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3116 ('C', 'clean', None, _('overwrite locally modified files')),
3117 ('C', 'clean', None, _('overwrite locally modified files')),
3117 ('f', 'force', None, _('force a merge with outstanding changes'))],
3118 ('f', 'force', None, _('force a merge with outstanding changes'))],
3118 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3119 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3119 "verify": (verify, [], _('hg verify')),
3120 "verify": (verify, [], _('hg verify')),
3120 "version": (show_version, [], _('hg version')),
3121 "version": (show_version, [], _('hg version')),
3121 }
3122 }
3122
3123
3123 globalopts = [
3124 globalopts = [
3124 ('R', 'repository', '',
3125 ('R', 'repository', '',
3125 _('repository root directory or symbolic path name')),
3126 _('repository root directory or symbolic path name')),
3126 ('', 'cwd', '', _('change working directory')),
3127 ('', 'cwd', '', _('change working directory')),
3127 ('y', 'noninteractive', None,
3128 ('y', 'noninteractive', None,
3128 _('do not prompt, assume \'yes\' for any required answers')),
3129 _('do not prompt, assume \'yes\' for any required answers')),
3129 ('q', 'quiet', None, _('suppress output')),
3130 ('q', 'quiet', None, _('suppress output')),
3130 ('v', 'verbose', None, _('enable additional output')),
3131 ('v', 'verbose', None, _('enable additional output')),
3131 ('', 'config', [], _('set/override config option')),
3132 ('', 'config', [], _('set/override config option')),
3132 ('', 'debug', None, _('enable debugging output')),
3133 ('', 'debug', None, _('enable debugging output')),
3133 ('', 'debugger', None, _('start debugger')),
3134 ('', 'debugger', None, _('start debugger')),
3134 ('', 'lsprof', None, _('print improved command execution profile')),
3135 ('', 'lsprof', None, _('print improved command execution profile')),
3135 ('', 'traceback', None, _('print traceback on exception')),
3136 ('', 'traceback', None, _('print traceback on exception')),
3136 ('', 'time', None, _('time how long the command takes')),
3137 ('', 'time', None, _('time how long the command takes')),
3137 ('', 'profile', None, _('print command execution profile')),
3138 ('', 'profile', None, _('print command execution profile')),
3138 ('', 'version', None, _('output version information and exit')),
3139 ('', 'version', None, _('output version information and exit')),
3139 ('h', 'help', None, _('display help and exit')),
3140 ('h', 'help', None, _('display help and exit')),
3140 ]
3141 ]
3141
3142
3142 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3143 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3143 " debugindex debugindexdot")
3144 " debugindex debugindexdot")
3144 optionalrepo = ("paths serve debugconfig")
3145 optionalrepo = ("paths serve debugconfig")
3145
3146
3146 def findpossible(cmd):
3147 def findpossible(cmd):
3147 """
3148 """
3148 Return cmd -> (aliases, command table entry)
3149 Return cmd -> (aliases, command table entry)
3149 for each matching command.
3150 for each matching command.
3150 Return debug commands (or their aliases) only if no normal command matches.
3151 Return debug commands (or their aliases) only if no normal command matches.
3151 """
3152 """
3152 choice = {}
3153 choice = {}
3153 debugchoice = {}
3154 debugchoice = {}
3154 for e in table.keys():
3155 for e in table.keys():
3155 aliases = e.lstrip("^").split("|")
3156 aliases = e.lstrip("^").split("|")
3156 found = None
3157 found = None
3157 if cmd in aliases:
3158 if cmd in aliases:
3158 found = cmd
3159 found = cmd
3159 else:
3160 else:
3160 for a in aliases:
3161 for a in aliases:
3161 if a.startswith(cmd):
3162 if a.startswith(cmd):
3162 found = a
3163 found = a
3163 break
3164 break
3164 if found is not None:
3165 if found is not None:
3165 if aliases[0].startswith("debug"):
3166 if aliases[0].startswith("debug"):
3166 debugchoice[found] = (aliases, table[e])
3167 debugchoice[found] = (aliases, table[e])
3167 else:
3168 else:
3168 choice[found] = (aliases, table[e])
3169 choice[found] = (aliases, table[e])
3169
3170
3170 if not choice and debugchoice:
3171 if not choice and debugchoice:
3171 choice = debugchoice
3172 choice = debugchoice
3172
3173
3173 return choice
3174 return choice
3174
3175
3175 def findcmd(cmd):
3176 def findcmd(cmd):
3176 """Return (aliases, command table entry) for command string."""
3177 """Return (aliases, command table entry) for command string."""
3177 choice = findpossible(cmd)
3178 choice = findpossible(cmd)
3178
3179
3179 if choice.has_key(cmd):
3180 if choice.has_key(cmd):
3180 return choice[cmd]
3181 return choice[cmd]
3181
3182
3182 if len(choice) > 1:
3183 if len(choice) > 1:
3183 clist = choice.keys()
3184 clist = choice.keys()
3184 clist.sort()
3185 clist.sort()
3185 raise AmbiguousCommand(cmd, clist)
3186 raise AmbiguousCommand(cmd, clist)
3186
3187
3187 if choice:
3188 if choice:
3188 return choice.values()[0]
3189 return choice.values()[0]
3189
3190
3190 raise UnknownCommand(cmd)
3191 raise UnknownCommand(cmd)
3191
3192
3192 def catchterm(*args):
3193 def catchterm(*args):
3193 raise util.SignalInterrupt
3194 raise util.SignalInterrupt
3194
3195
3195 def run():
3196 def run():
3196 sys.exit(dispatch(sys.argv[1:]))
3197 sys.exit(dispatch(sys.argv[1:]))
3197
3198
3198 class ParseError(Exception):
3199 class ParseError(Exception):
3199 """Exception raised on errors in parsing the command line."""
3200 """Exception raised on errors in parsing the command line."""
3200
3201
3201 def parse(ui, args):
3202 def parse(ui, args):
3202 options = {}
3203 options = {}
3203 cmdoptions = {}
3204 cmdoptions = {}
3204
3205
3205 try:
3206 try:
3206 args = fancyopts.fancyopts(args, globalopts, options)
3207 args = fancyopts.fancyopts(args, globalopts, options)
3207 except fancyopts.getopt.GetoptError, inst:
3208 except fancyopts.getopt.GetoptError, inst:
3208 raise ParseError(None, inst)
3209 raise ParseError(None, inst)
3209
3210
3210 if args:
3211 if args:
3211 cmd, args = args[0], args[1:]
3212 cmd, args = args[0], args[1:]
3212 aliases, i = findcmd(cmd)
3213 aliases, i = findcmd(cmd)
3213 cmd = aliases[0]
3214 cmd = aliases[0]
3214 defaults = ui.config("defaults", cmd)
3215 defaults = ui.config("defaults", cmd)
3215 if defaults:
3216 if defaults:
3216 args = defaults.split() + args
3217 args = defaults.split() + args
3217 c = list(i[1])
3218 c = list(i[1])
3218 else:
3219 else:
3219 cmd = None
3220 cmd = None
3220 c = []
3221 c = []
3221
3222
3222 # combine global options into local
3223 # combine global options into local
3223 for o in globalopts:
3224 for o in globalopts:
3224 c.append((o[0], o[1], options[o[1]], o[3]))
3225 c.append((o[0], o[1], options[o[1]], o[3]))
3225
3226
3226 try:
3227 try:
3227 args = fancyopts.fancyopts(args, c, cmdoptions)
3228 args = fancyopts.fancyopts(args, c, cmdoptions)
3228 except fancyopts.getopt.GetoptError, inst:
3229 except fancyopts.getopt.GetoptError, inst:
3229 raise ParseError(cmd, inst)
3230 raise ParseError(cmd, inst)
3230
3231
3231 # separate global options back out
3232 # separate global options back out
3232 for o in globalopts:
3233 for o in globalopts:
3233 n = o[1]
3234 n = o[1]
3234 options[n] = cmdoptions[n]
3235 options[n] = cmdoptions[n]
3235 del cmdoptions[n]
3236 del cmdoptions[n]
3236
3237
3237 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3238 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3238
3239
3239 external = {}
3240 external = {}
3240
3241
3241 def findext(name):
3242 def findext(name):
3242 '''return module with given extension name'''
3243 '''return module with given extension name'''
3243 try:
3244 try:
3244 return sys.modules[external[name]]
3245 return sys.modules[external[name]]
3245 except KeyError:
3246 except KeyError:
3246 for k, v in external.iteritems():
3247 for k, v in external.iteritems():
3247 if k.endswith('.' + name) or k.endswith('/' + name) or v == name:
3248 if k.endswith('.' + name) or k.endswith('/' + name) or v == name:
3248 return sys.modules[v]
3249 return sys.modules[v]
3249 raise KeyError(name)
3250 raise KeyError(name)
3250
3251
3251 def dispatch(args):
3252 def dispatch(args):
3252 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3253 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3253 num = getattr(signal, name, None)
3254 num = getattr(signal, name, None)
3254 if num: signal.signal(num, catchterm)
3255 if num: signal.signal(num, catchterm)
3255
3256
3256 try:
3257 try:
3257 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3258 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3258 except util.Abort, inst:
3259 except util.Abort, inst:
3259 sys.stderr.write(_("abort: %s\n") % inst)
3260 sys.stderr.write(_("abort: %s\n") % inst)
3260 return -1
3261 return -1
3261
3262
3262 for ext_name, load_from_name in u.extensions():
3263 for ext_name, load_from_name in u.extensions():
3263 try:
3264 try:
3264 if load_from_name:
3265 if load_from_name:
3265 # the module will be loaded in sys.modules
3266 # the module will be loaded in sys.modules
3266 # choose an unique name so that it doesn't
3267 # choose an unique name so that it doesn't
3267 # conflicts with other modules
3268 # conflicts with other modules
3268 module_name = "hgext_%s" % ext_name.replace('.', '_')
3269 module_name = "hgext_%s" % ext_name.replace('.', '_')
3269 mod = imp.load_source(module_name, load_from_name)
3270 mod = imp.load_source(module_name, load_from_name)
3270 else:
3271 else:
3271 def importh(name):
3272 def importh(name):
3272 mod = __import__(name)
3273 mod = __import__(name)
3273 components = name.split('.')
3274 components = name.split('.')
3274 for comp in components[1:]:
3275 for comp in components[1:]:
3275 mod = getattr(mod, comp)
3276 mod = getattr(mod, comp)
3276 return mod
3277 return mod
3277 try:
3278 try:
3278 mod = importh("hgext.%s" % ext_name)
3279 mod = importh("hgext.%s" % ext_name)
3279 except ImportError:
3280 except ImportError:
3280 mod = importh(ext_name)
3281 mod = importh(ext_name)
3281 external[ext_name] = mod.__name__
3282 external[ext_name] = mod.__name__
3282 except (util.SignalInterrupt, KeyboardInterrupt):
3283 except (util.SignalInterrupt, KeyboardInterrupt):
3283 raise
3284 raise
3284 except Exception, inst:
3285 except Exception, inst:
3285 u.warn(_("*** failed to import extension %s: %s\n") % (ext_name, inst))
3286 u.warn(_("*** failed to import extension %s: %s\n") % (ext_name, inst))
3286 if u.print_exc():
3287 if u.print_exc():
3287 return 1
3288 return 1
3288
3289
3289 for name in external.itervalues():
3290 for name in external.itervalues():
3290 mod = sys.modules[name]
3291 mod = sys.modules[name]
3291 uisetup = getattr(mod, 'uisetup', None)
3292 uisetup = getattr(mod, 'uisetup', None)
3292 if uisetup:
3293 if uisetup:
3293 uisetup(u)
3294 uisetup(u)
3294 cmdtable = getattr(mod, 'cmdtable', {})
3295 cmdtable = getattr(mod, 'cmdtable', {})
3295 for t in cmdtable:
3296 for t in cmdtable:
3296 if t in table:
3297 if t in table:
3297 u.warn(_("module %s overrides %s\n") % (name, t))
3298 u.warn(_("module %s overrides %s\n") % (name, t))
3298 table.update(cmdtable)
3299 table.update(cmdtable)
3299
3300
3300 try:
3301 try:
3301 cmd, func, args, options, cmdoptions = parse(u, args)
3302 cmd, func, args, options, cmdoptions = parse(u, args)
3302 if options["time"]:
3303 if options["time"]:
3303 def get_times():
3304 def get_times():
3304 t = os.times()
3305 t = os.times()
3305 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3306 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3306 t = (t[0], t[1], t[2], t[3], time.clock())
3307 t = (t[0], t[1], t[2], t[3], time.clock())
3307 return t
3308 return t
3308 s = get_times()
3309 s = get_times()
3309 def print_time():
3310 def print_time():
3310 t = get_times()
3311 t = get_times()
3311 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3312 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3312 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3313 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3313 atexit.register(print_time)
3314 atexit.register(print_time)
3314
3315
3315 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3316 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3316 not options["noninteractive"], options["traceback"],
3317 not options["noninteractive"], options["traceback"],
3317 options["config"])
3318 options["config"])
3318
3319
3319 # enter the debugger before command execution
3320 # enter the debugger before command execution
3320 if options['debugger']:
3321 if options['debugger']:
3321 pdb.set_trace()
3322 pdb.set_trace()
3322
3323
3323 try:
3324 try:
3324 if options['cwd']:
3325 if options['cwd']:
3325 try:
3326 try:
3326 os.chdir(options['cwd'])
3327 os.chdir(options['cwd'])
3327 except OSError, inst:
3328 except OSError, inst:
3328 raise util.Abort('%s: %s' %
3329 raise util.Abort('%s: %s' %
3329 (options['cwd'], inst.strerror))
3330 (options['cwd'], inst.strerror))
3330
3331
3331 path = u.expandpath(options["repository"]) or ""
3332 path = u.expandpath(options["repository"]) or ""
3332 repo = path and hg.repository(u, path=path) or None
3333 repo = path and hg.repository(u, path=path) or None
3333
3334
3334 if options['help']:
3335 if options['help']:
3335 return help_(u, cmd, options['version'])
3336 return help_(u, cmd, options['version'])
3336 elif options['version']:
3337 elif options['version']:
3337 return show_version(u)
3338 return show_version(u)
3338 elif not cmd:
3339 elif not cmd:
3339 return help_(u, 'shortlist')
3340 return help_(u, 'shortlist')
3340
3341
3341 if cmd not in norepo.split():
3342 if cmd not in norepo.split():
3342 try:
3343 try:
3343 if not repo:
3344 if not repo:
3344 repo = hg.repository(u, path=path)
3345 repo = hg.repository(u, path=path)
3345 u = repo.ui
3346 u = repo.ui
3346 for name in external.itervalues():
3347 for name in external.itervalues():
3347 mod = sys.modules[name]
3348 mod = sys.modules[name]
3348 if hasattr(mod, 'reposetup'):
3349 if hasattr(mod, 'reposetup'):
3349 mod.reposetup(u, repo)
3350 mod.reposetup(u, repo)
3350 hg.repo_setup_hooks.append(mod.reposetup)
3351 hg.repo_setup_hooks.append(mod.reposetup)
3351 except hg.RepoError:
3352 except hg.RepoError:
3352 if cmd not in optionalrepo.split():
3353 if cmd not in optionalrepo.split():
3353 raise
3354 raise
3354 d = lambda: func(u, repo, *args, **cmdoptions)
3355 d = lambda: func(u, repo, *args, **cmdoptions)
3355 else:
3356 else:
3356 d = lambda: func(u, *args, **cmdoptions)
3357 d = lambda: func(u, *args, **cmdoptions)
3357
3358
3358 # reupdate the options, repo/.hg/hgrc may have changed them
3359 # reupdate the options, repo/.hg/hgrc may have changed them
3359 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3360 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3360 not options["noninteractive"], options["traceback"],
3361 not options["noninteractive"], options["traceback"],
3361 options["config"])
3362 options["config"])
3362
3363
3363 try:
3364 try:
3364 if options['profile']:
3365 if options['profile']:
3365 import hotshot, hotshot.stats
3366 import hotshot, hotshot.stats
3366 prof = hotshot.Profile("hg.prof")
3367 prof = hotshot.Profile("hg.prof")
3367 try:
3368 try:
3368 try:
3369 try:
3369 return prof.runcall(d)
3370 return prof.runcall(d)
3370 except:
3371 except:
3371 try:
3372 try:
3372 u.warn(_('exception raised - generating '
3373 u.warn(_('exception raised - generating '
3373 'profile anyway\n'))
3374 'profile anyway\n'))
3374 except:
3375 except:
3375 pass
3376 pass
3376 raise
3377 raise
3377 finally:
3378 finally:
3378 prof.close()
3379 prof.close()
3379 stats = hotshot.stats.load("hg.prof")
3380 stats = hotshot.stats.load("hg.prof")
3380 stats.strip_dirs()
3381 stats.strip_dirs()
3381 stats.sort_stats('time', 'calls')
3382 stats.sort_stats('time', 'calls')
3382 stats.print_stats(40)
3383 stats.print_stats(40)
3383 elif options['lsprof']:
3384 elif options['lsprof']:
3384 try:
3385 try:
3385 from mercurial import lsprof
3386 from mercurial import lsprof
3386 except ImportError:
3387 except ImportError:
3387 raise util.Abort(_(
3388 raise util.Abort(_(
3388 'lsprof not available - install from '
3389 'lsprof not available - install from '
3389 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3390 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3390 p = lsprof.Profiler()
3391 p = lsprof.Profiler()
3391 p.enable(subcalls=True)
3392 p.enable(subcalls=True)
3392 try:
3393 try:
3393 return d()
3394 return d()
3394 finally:
3395 finally:
3395 p.disable()
3396 p.disable()
3396 stats = lsprof.Stats(p.getstats())
3397 stats = lsprof.Stats(p.getstats())
3397 stats.sort()
3398 stats.sort()
3398 stats.pprint(top=10, file=sys.stderr, climit=5)
3399 stats.pprint(top=10, file=sys.stderr, climit=5)
3399 else:
3400 else:
3400 return d()
3401 return d()
3401 finally:
3402 finally:
3402 u.flush()
3403 u.flush()
3403 except:
3404 except:
3404 # enter the debugger when we hit an exception
3405 # enter the debugger when we hit an exception
3405 if options['debugger']:
3406 if options['debugger']:
3406 pdb.post_mortem(sys.exc_info()[2])
3407 pdb.post_mortem(sys.exc_info()[2])
3407 u.print_exc()
3408 u.print_exc()
3408 raise
3409 raise
3409 except ParseError, inst:
3410 except ParseError, inst:
3410 if inst.args[0]:
3411 if inst.args[0]:
3411 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3412 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3412 help_(u, inst.args[0])
3413 help_(u, inst.args[0])
3413 else:
3414 else:
3414 u.warn(_("hg: %s\n") % inst.args[1])
3415 u.warn(_("hg: %s\n") % inst.args[1])
3415 help_(u, 'shortlist')
3416 help_(u, 'shortlist')
3416 except AmbiguousCommand, inst:
3417 except AmbiguousCommand, inst:
3417 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3418 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3418 (inst.args[0], " ".join(inst.args[1])))
3419 (inst.args[0], " ".join(inst.args[1])))
3419 except UnknownCommand, inst:
3420 except UnknownCommand, inst:
3420 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3421 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3421 help_(u, 'shortlist')
3422 help_(u, 'shortlist')
3422 except hg.RepoError, inst:
3423 except hg.RepoError, inst:
3423 u.warn(_("abort: %s!\n") % inst)
3424 u.warn(_("abort: %s!\n") % inst)
3424 except lock.LockHeld, inst:
3425 except lock.LockHeld, inst:
3425 if inst.errno == errno.ETIMEDOUT:
3426 if inst.errno == errno.ETIMEDOUT:
3426 reason = _('timed out waiting for lock held by %s') % inst.locker
3427 reason = _('timed out waiting for lock held by %s') % inst.locker
3427 else:
3428 else:
3428 reason = _('lock held by %s') % inst.locker
3429 reason = _('lock held by %s') % inst.locker
3429 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3430 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3430 except lock.LockUnavailable, inst:
3431 except lock.LockUnavailable, inst:
3431 u.warn(_("abort: could not lock %s: %s\n") %
3432 u.warn(_("abort: could not lock %s: %s\n") %
3432 (inst.desc or inst.filename, inst.strerror))
3433 (inst.desc or inst.filename, inst.strerror))
3433 except revlog.RevlogError, inst:
3434 except revlog.RevlogError, inst:
3434 u.warn(_("abort: "), inst, "!\n")
3435 u.warn(_("abort: "), inst, "!\n")
3435 except util.SignalInterrupt:
3436 except util.SignalInterrupt:
3436 u.warn(_("killed!\n"))
3437 u.warn(_("killed!\n"))
3437 except KeyboardInterrupt:
3438 except KeyboardInterrupt:
3438 try:
3439 try:
3439 u.warn(_("interrupted!\n"))
3440 u.warn(_("interrupted!\n"))
3440 except IOError, inst:
3441 except IOError, inst:
3441 if inst.errno == errno.EPIPE:
3442 if inst.errno == errno.EPIPE:
3442 if u.debugflag:
3443 if u.debugflag:
3443 u.warn(_("\nbroken pipe\n"))
3444 u.warn(_("\nbroken pipe\n"))
3444 else:
3445 else:
3445 raise
3446 raise
3446 except IOError, inst:
3447 except IOError, inst:
3447 if hasattr(inst, "code"):
3448 if hasattr(inst, "code"):
3448 u.warn(_("abort: %s\n") % inst)
3449 u.warn(_("abort: %s\n") % inst)
3449 elif hasattr(inst, "reason"):
3450 elif hasattr(inst, "reason"):
3450 u.warn(_("abort: error: %s\n") % inst.reason[1])
3451 u.warn(_("abort: error: %s\n") % inst.reason[1])
3451 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3452 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3452 if u.debugflag:
3453 if u.debugflag:
3453 u.warn(_("broken pipe\n"))
3454 u.warn(_("broken pipe\n"))
3454 elif getattr(inst, "strerror", None):
3455 elif getattr(inst, "strerror", None):
3455 if getattr(inst, "filename", None):
3456 if getattr(inst, "filename", None):
3456 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3457 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3457 else:
3458 else:
3458 u.warn(_("abort: %s\n") % inst.strerror)
3459 u.warn(_("abort: %s\n") % inst.strerror)
3459 else:
3460 else:
3460 raise
3461 raise
3461 except OSError, inst:
3462 except OSError, inst:
3462 if hasattr(inst, "filename"):
3463 if hasattr(inst, "filename"):
3463 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3464 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3464 else:
3465 else:
3465 u.warn(_("abort: %s\n") % inst.strerror)
3466 u.warn(_("abort: %s\n") % inst.strerror)
3466 except util.Abort, inst:
3467 except util.Abort, inst:
3467 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3468 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3468 except TypeError, inst:
3469 except TypeError, inst:
3469 # was this an argument error?
3470 # was this an argument error?
3470 tb = traceback.extract_tb(sys.exc_info()[2])
3471 tb = traceback.extract_tb(sys.exc_info()[2])
3471 if len(tb) > 2: # no
3472 if len(tb) > 2: # no
3472 raise
3473 raise
3473 u.debug(inst, "\n")
3474 u.debug(inst, "\n")
3474 u.warn(_("%s: invalid arguments\n") % cmd)
3475 u.warn(_("%s: invalid arguments\n") % cmd)
3475 help_(u, cmd)
3476 help_(u, cmd)
3476 except SystemExit, inst:
3477 except SystemExit, inst:
3477 # Commands shouldn't sys.exit directly, but give a return code.
3478 # Commands shouldn't sys.exit directly, but give a return code.
3478 # Just in case catch this and and pass exit code to caller.
3479 # Just in case catch this and and pass exit code to caller.
3479 return inst.code
3480 return inst.code
3480 except:
3481 except:
3481 u.warn(_("** unknown exception encountered, details follow\n"))
3482 u.warn(_("** unknown exception encountered, details follow\n"))
3482 u.warn(_("** report bug details to "
3483 u.warn(_("** report bug details to "
3483 "http://www.selenic.com/mercurial/bts\n"))
3484 "http://www.selenic.com/mercurial/bts\n"))
3484 u.warn(_("** or mercurial@selenic.com\n"))
3485 u.warn(_("** or mercurial@selenic.com\n"))
3485 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3486 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3486 % version.get_version())
3487 % version.get_version())
3487 raise
3488 raise
3488
3489
3489 return -1
3490 return -1
@@ -1,981 +1,981 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 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 import os
9 import os
10 import os.path
10 import os.path
11 import mimetypes
11 import mimetypes
12 from mercurial.demandload import demandload
12 from mercurial.demandload import demandload
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
15 demandload(globals(), "mercurial:templater")
15 demandload(globals(), "mercurial:templater")
16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
17 from mercurial.node import *
17 from mercurial.node import *
18 from mercurial.i18n import gettext as _
18 from mercurial.i18n import gettext as _
19
19
20 def _up(p):
20 def _up(p):
21 if p[0] != "/":
21 if p[0] != "/":
22 p = "/" + p
22 p = "/" + p
23 if p[-1] == "/":
23 if p[-1] == "/":
24 p = p[:-1]
24 p = p[:-1]
25 up = os.path.dirname(p)
25 up = os.path.dirname(p)
26 if up == "/":
26 if up == "/":
27 return "/"
27 return "/"
28 return up + "/"
28 return up + "/"
29
29
30 class hgweb(object):
30 class hgweb(object):
31 def __init__(self, repo, name=None):
31 def __init__(self, repo, name=None):
32 if type(repo) == type(""):
32 if type(repo) == type(""):
33 self.repo = hg.repository(ui.ui(), repo)
33 self.repo = hg.repository(ui.ui(), repo)
34 else:
34 else:
35 self.repo = repo
35 self.repo = repo
36
36
37 self.mtime = -1
37 self.mtime = -1
38 self.reponame = name
38 self.reponame = name
39 self.archives = 'zip', 'gz', 'bz2'
39 self.archives = 'zip', 'gz', 'bz2'
40 self.stripecount = 1
40 self.stripecount = 1
41 self.templatepath = self.repo.ui.config("web", "templates",
41 self.templatepath = self.repo.ui.config("web", "templates",
42 templater.templatepath())
42 templater.templatepath())
43
43
44 def refresh(self):
44 def refresh(self):
45 mtime = get_mtime(self.repo.root)
45 mtime = get_mtime(self.repo.root)
46 if mtime != self.mtime:
46 if mtime != self.mtime:
47 self.mtime = mtime
47 self.mtime = mtime
48 self.repo = hg.repository(self.repo.ui, self.repo.root)
48 self.repo = hg.repository(self.repo.ui, self.repo.root)
49 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
49 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
50 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
50 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
51 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
51 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
52 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
52 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
53 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
53 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
54
54
55 def archivelist(self, nodeid):
55 def archivelist(self, nodeid):
56 allowed = self.repo.ui.configlist("web", "allow_archive")
56 allowed = self.repo.ui.configlist("web", "allow_archive")
57 for i in self.archives:
57 for i in self.archives:
58 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
58 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
59 yield {"type" : i, "node" : nodeid, "url": ""}
59 yield {"type" : i, "node" : nodeid, "url": ""}
60
60
61 def listfiles(self, files, mf):
61 def listfiles(self, files, mf):
62 for f in files[:self.maxfiles]:
62 for f in files[:self.maxfiles]:
63 yield self.t("filenodelink", node=hex(mf[f]), file=f)
63 yield self.t("filenodelink", node=hex(mf[f]), file=f)
64 if len(files) > self.maxfiles:
64 if len(files) > self.maxfiles:
65 yield self.t("fileellipses")
65 yield self.t("fileellipses")
66
66
67 def listfilediffs(self, files, changeset):
67 def listfilediffs(self, files, changeset):
68 for f in files[:self.maxfiles]:
68 for f in files[:self.maxfiles]:
69 yield self.t("filedifflink", node=hex(changeset), file=f)
69 yield self.t("filedifflink", node=hex(changeset), file=f)
70 if len(files) > self.maxfiles:
70 if len(files) > self.maxfiles:
71 yield self.t("fileellipses")
71 yield self.t("fileellipses")
72
72
73 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
73 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
74 if not rev:
74 if not rev:
75 rev = lambda x: ""
75 rev = lambda x: ""
76 siblings = [s for s in siblings if s != nullid]
76 siblings = [s for s in siblings if s != nullid]
77 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
77 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
78 return
78 return
79 for s in siblings:
79 for s in siblings:
80 yield dict(node=hex(s), rev=rev(s), **args)
80 yield dict(node=hex(s), rev=rev(s), **args)
81
81
82 def renamelink(self, fl, node):
82 def renamelink(self, fl, node):
83 r = fl.renamed(node)
83 r = fl.renamed(node)
84 if r:
84 if r:
85 return [dict(file=r[0], node=hex(r[1]))]
85 return [dict(file=r[0], node=hex(r[1]))]
86 return []
86 return []
87
87
88 def showtag(self, t1, node=nullid, **args):
88 def showtag(self, t1, node=nullid, **args):
89 for t in self.repo.nodetags(node):
89 for t in self.repo.nodetags(node):
90 yield self.t(t1, tag=t, **args)
90 yield self.t(t1, tag=t, **args)
91
91
92 def diff(self, node1, node2, files):
92 def diff(self, node1, node2, files):
93 def filterfiles(filters, files):
93 def filterfiles(filters, files):
94 l = [x for x in files if x in filters]
94 l = [x for x in files if x in filters]
95
95
96 for t in filters:
96 for t in filters:
97 if t and t[-1] != os.sep:
97 if t and t[-1] != os.sep:
98 t += os.sep
98 t += os.sep
99 l += [x for x in files if x.startswith(t)]
99 l += [x for x in files if x.startswith(t)]
100 return l
100 return l
101
101
102 parity = [0]
102 parity = [0]
103 def diffblock(diff, f, fn):
103 def diffblock(diff, f, fn):
104 yield self.t("diffblock",
104 yield self.t("diffblock",
105 lines=prettyprintlines(diff),
105 lines=prettyprintlines(diff),
106 parity=parity[0],
106 parity=parity[0],
107 file=f,
107 file=f,
108 filenode=hex(fn or nullid))
108 filenode=hex(fn or nullid))
109 parity[0] = 1 - parity[0]
109 parity[0] = 1 - parity[0]
110
110
111 def prettyprintlines(diff):
111 def prettyprintlines(diff):
112 for l in diff.splitlines(1):
112 for l in diff.splitlines(1):
113 if l.startswith('+'):
113 if l.startswith('+'):
114 yield self.t("difflineplus", line=l)
114 yield self.t("difflineplus", line=l)
115 elif l.startswith('-'):
115 elif l.startswith('-'):
116 yield self.t("difflineminus", line=l)
116 yield self.t("difflineminus", line=l)
117 elif l.startswith('@'):
117 elif l.startswith('@'):
118 yield self.t("difflineat", line=l)
118 yield self.t("difflineat", line=l)
119 else:
119 else:
120 yield self.t("diffline", line=l)
120 yield self.t("diffline", line=l)
121
121
122 r = self.repo
122 r = self.repo
123 cl = r.changelog
123 cl = r.changelog
124 mf = r.manifest
124 mf = r.manifest
125 change1 = cl.read(node1)
125 change1 = cl.read(node1)
126 change2 = cl.read(node2)
126 change2 = cl.read(node2)
127 mmap1 = mf.read(change1[0])
127 mmap1 = mf.read(change1[0])
128 mmap2 = mf.read(change2[0])
128 mmap2 = mf.read(change2[0])
129 date1 = util.datestr(change1[2])
129 date1 = util.datestr(change1[2])
130 date2 = util.datestr(change2[2])
130 date2 = util.datestr(change2[2])
131
131
132 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
132 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
133 if files:
133 if files:
134 modified, added, removed = map(lambda x: filterfiles(files, x),
134 modified, added, removed = map(lambda x: filterfiles(files, x),
135 (modified, added, removed))
135 (modified, added, removed))
136
136
137 diffopts = ui.diffopts()
137 diffopts = patch.diffopts(ui)
138 for f in modified:
138 for f in modified:
139 to = r.file(f).read(mmap1[f])
139 to = r.file(f).read(mmap1[f])
140 tn = r.file(f).read(mmap2[f])
140 tn = r.file(f).read(mmap2[f])
141 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
141 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
142 opts=diffopts), f, tn)
142 opts=diffopts), f, tn)
143 for f in added:
143 for f in added:
144 to = None
144 to = None
145 tn = r.file(f).read(mmap2[f])
145 tn = r.file(f).read(mmap2[f])
146 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
146 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
147 opts=diffopts), f, tn)
147 opts=diffopts), f, tn)
148 for f in removed:
148 for f in removed:
149 to = r.file(f).read(mmap1[f])
149 to = r.file(f).read(mmap1[f])
150 tn = None
150 tn = None
151 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
151 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
152 opts=diffopts), f, tn)
152 opts=diffopts), f, tn)
153
153
154 def changelog(self, pos, shortlog=False):
154 def changelog(self, pos, shortlog=False):
155 def changenav(**map):
155 def changenav(**map):
156 def seq(factor, maxchanges=None):
156 def seq(factor, maxchanges=None):
157 if maxchanges:
157 if maxchanges:
158 yield maxchanges
158 yield maxchanges
159 if maxchanges >= 20 and maxchanges <= 40:
159 if maxchanges >= 20 and maxchanges <= 40:
160 yield 50
160 yield 50
161 else:
161 else:
162 yield 1 * factor
162 yield 1 * factor
163 yield 3 * factor
163 yield 3 * factor
164 for f in seq(factor * 10):
164 for f in seq(factor * 10):
165 yield f
165 yield f
166
166
167 l = []
167 l = []
168 last = 0
168 last = 0
169 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
169 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
170 for f in seq(1, maxchanges):
170 for f in seq(1, maxchanges):
171 if f < maxchanges or f <= last:
171 if f < maxchanges or f <= last:
172 continue
172 continue
173 if f > count:
173 if f > count:
174 break
174 break
175 last = f
175 last = f
176 r = "%d" % f
176 r = "%d" % f
177 if pos + f < count:
177 if pos + f < count:
178 l.append(("+" + r, pos + f))
178 l.append(("+" + r, pos + f))
179 if pos - f >= 0:
179 if pos - f >= 0:
180 l.insert(0, ("-" + r, pos - f))
180 l.insert(0, ("-" + r, pos - f))
181
181
182 yield {"rev": 0, "label": "(0)"}
182 yield {"rev": 0, "label": "(0)"}
183
183
184 for label, rev in l:
184 for label, rev in l:
185 yield {"label": label, "rev": rev}
185 yield {"label": label, "rev": rev}
186
186
187 yield {"label": "tip", "rev": "tip"}
187 yield {"label": "tip", "rev": "tip"}
188
188
189 def changelist(**map):
189 def changelist(**map):
190 parity = (start - end) & 1
190 parity = (start - end) & 1
191 cl = self.repo.changelog
191 cl = self.repo.changelog
192 l = [] # build a list in forward order for efficiency
192 l = [] # build a list in forward order for efficiency
193 for i in range(start, end):
193 for i in range(start, end):
194 n = cl.node(i)
194 n = cl.node(i)
195 changes = cl.read(n)
195 changes = cl.read(n)
196 hn = hex(n)
196 hn = hex(n)
197
197
198 l.insert(0, {"parity": parity,
198 l.insert(0, {"parity": parity,
199 "author": changes[1],
199 "author": changes[1],
200 "parent": self.siblings(cl.parents(n), cl.rev,
200 "parent": self.siblings(cl.parents(n), cl.rev,
201 cl.rev(n) - 1),
201 cl.rev(n) - 1),
202 "child": self.siblings(cl.children(n), cl.rev,
202 "child": self.siblings(cl.children(n), cl.rev,
203 cl.rev(n) + 1),
203 cl.rev(n) + 1),
204 "changelogtag": self.showtag("changelogtag",n),
204 "changelogtag": self.showtag("changelogtag",n),
205 "manifest": hex(changes[0]),
205 "manifest": hex(changes[0]),
206 "desc": changes[4],
206 "desc": changes[4],
207 "date": changes[2],
207 "date": changes[2],
208 "files": self.listfilediffs(changes[3], n),
208 "files": self.listfilediffs(changes[3], n),
209 "rev": i,
209 "rev": i,
210 "node": hn})
210 "node": hn})
211 parity = 1 - parity
211 parity = 1 - parity
212
212
213 for e in l:
213 for e in l:
214 yield e
214 yield e
215
215
216 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
216 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
217 cl = self.repo.changelog
217 cl = self.repo.changelog
218 mf = cl.read(cl.tip())[0]
218 mf = cl.read(cl.tip())[0]
219 count = cl.count()
219 count = cl.count()
220 start = max(0, pos - maxchanges + 1)
220 start = max(0, pos - maxchanges + 1)
221 end = min(count, start + maxchanges)
221 end = min(count, start + maxchanges)
222 pos = end - 1
222 pos = end - 1
223
223
224 yield self.t(shortlog and 'shortlog' or 'changelog',
224 yield self.t(shortlog and 'shortlog' or 'changelog',
225 changenav=changenav,
225 changenav=changenav,
226 manifest=hex(mf),
226 manifest=hex(mf),
227 rev=pos, changesets=count, entries=changelist,
227 rev=pos, changesets=count, entries=changelist,
228 archives=self.archivelist("tip"))
228 archives=self.archivelist("tip"))
229
229
230 def search(self, query):
230 def search(self, query):
231
231
232 def changelist(**map):
232 def changelist(**map):
233 cl = self.repo.changelog
233 cl = self.repo.changelog
234 count = 0
234 count = 0
235 qw = query.lower().split()
235 qw = query.lower().split()
236
236
237 def revgen():
237 def revgen():
238 for i in range(cl.count() - 1, 0, -100):
238 for i in range(cl.count() - 1, 0, -100):
239 l = []
239 l = []
240 for j in range(max(0, i - 100), i):
240 for j in range(max(0, i - 100), i):
241 n = cl.node(j)
241 n = cl.node(j)
242 changes = cl.read(n)
242 changes = cl.read(n)
243 l.append((n, j, changes))
243 l.append((n, j, changes))
244 l.reverse()
244 l.reverse()
245 for e in l:
245 for e in l:
246 yield e
246 yield e
247
247
248 for n, i, changes in revgen():
248 for n, i, changes in revgen():
249 miss = 0
249 miss = 0
250 for q in qw:
250 for q in qw:
251 if not (q in changes[1].lower() or
251 if not (q in changes[1].lower() or
252 q in changes[4].lower() or
252 q in changes[4].lower() or
253 q in " ".join(changes[3][:20]).lower()):
253 q in " ".join(changes[3][:20]).lower()):
254 miss = 1
254 miss = 1
255 break
255 break
256 if miss:
256 if miss:
257 continue
257 continue
258
258
259 count += 1
259 count += 1
260 hn = hex(n)
260 hn = hex(n)
261
261
262 yield self.t('searchentry',
262 yield self.t('searchentry',
263 parity=self.stripes(count),
263 parity=self.stripes(count),
264 author=changes[1],
264 author=changes[1],
265 parent=self.siblings(cl.parents(n), cl.rev),
265 parent=self.siblings(cl.parents(n), cl.rev),
266 child=self.siblings(cl.children(n), cl.rev),
266 child=self.siblings(cl.children(n), cl.rev),
267 changelogtag=self.showtag("changelogtag",n),
267 changelogtag=self.showtag("changelogtag",n),
268 manifest=hex(changes[0]),
268 manifest=hex(changes[0]),
269 desc=changes[4],
269 desc=changes[4],
270 date=changes[2],
270 date=changes[2],
271 files=self.listfilediffs(changes[3], n),
271 files=self.listfilediffs(changes[3], n),
272 rev=i,
272 rev=i,
273 node=hn)
273 node=hn)
274
274
275 if count >= self.maxchanges:
275 if count >= self.maxchanges:
276 break
276 break
277
277
278 cl = self.repo.changelog
278 cl = self.repo.changelog
279 mf = cl.read(cl.tip())[0]
279 mf = cl.read(cl.tip())[0]
280
280
281 yield self.t('search',
281 yield self.t('search',
282 query=query,
282 query=query,
283 manifest=hex(mf),
283 manifest=hex(mf),
284 entries=changelist)
284 entries=changelist)
285
285
286 def changeset(self, nodeid):
286 def changeset(self, nodeid):
287 cl = self.repo.changelog
287 cl = self.repo.changelog
288 n = self.repo.lookup(nodeid)
288 n = self.repo.lookup(nodeid)
289 nodeid = hex(n)
289 nodeid = hex(n)
290 changes = cl.read(n)
290 changes = cl.read(n)
291 p1 = cl.parents(n)[0]
291 p1 = cl.parents(n)[0]
292
292
293 files = []
293 files = []
294 mf = self.repo.manifest.read(changes[0])
294 mf = self.repo.manifest.read(changes[0])
295 for f in changes[3]:
295 for f in changes[3]:
296 files.append(self.t("filenodelink",
296 files.append(self.t("filenodelink",
297 filenode=hex(mf.get(f, nullid)), file=f))
297 filenode=hex(mf.get(f, nullid)), file=f))
298
298
299 def diff(**map):
299 def diff(**map):
300 yield self.diff(p1, n, None)
300 yield self.diff(p1, n, None)
301
301
302 yield self.t('changeset',
302 yield self.t('changeset',
303 diff=diff,
303 diff=diff,
304 rev=cl.rev(n),
304 rev=cl.rev(n),
305 node=nodeid,
305 node=nodeid,
306 parent=self.siblings(cl.parents(n), cl.rev),
306 parent=self.siblings(cl.parents(n), cl.rev),
307 child=self.siblings(cl.children(n), cl.rev),
307 child=self.siblings(cl.children(n), cl.rev),
308 changesettag=self.showtag("changesettag",n),
308 changesettag=self.showtag("changesettag",n),
309 manifest=hex(changes[0]),
309 manifest=hex(changes[0]),
310 author=changes[1],
310 author=changes[1],
311 desc=changes[4],
311 desc=changes[4],
312 date=changes[2],
312 date=changes[2],
313 files=files,
313 files=files,
314 archives=self.archivelist(nodeid))
314 archives=self.archivelist(nodeid))
315
315
316 def filelog(self, f, filenode):
316 def filelog(self, f, filenode):
317 cl = self.repo.changelog
317 cl = self.repo.changelog
318 fl = self.repo.file(f)
318 fl = self.repo.file(f)
319 filenode = hex(fl.lookup(filenode))
319 filenode = hex(fl.lookup(filenode))
320 count = fl.count()
320 count = fl.count()
321
321
322 def entries(**map):
322 def entries(**map):
323 l = []
323 l = []
324 parity = (count - 1) & 1
324 parity = (count - 1) & 1
325
325
326 for i in range(count):
326 for i in range(count):
327 n = fl.node(i)
327 n = fl.node(i)
328 lr = fl.linkrev(n)
328 lr = fl.linkrev(n)
329 cn = cl.node(lr)
329 cn = cl.node(lr)
330 cs = cl.read(cl.node(lr))
330 cs = cl.read(cl.node(lr))
331
331
332 l.insert(0, {"parity": parity,
332 l.insert(0, {"parity": parity,
333 "filenode": hex(n),
333 "filenode": hex(n),
334 "filerev": i,
334 "filerev": i,
335 "file": f,
335 "file": f,
336 "node": hex(cn),
336 "node": hex(cn),
337 "author": cs[1],
337 "author": cs[1],
338 "date": cs[2],
338 "date": cs[2],
339 "rename": self.renamelink(fl, n),
339 "rename": self.renamelink(fl, n),
340 "parent": self.siblings(fl.parents(n),
340 "parent": self.siblings(fl.parents(n),
341 fl.rev, file=f),
341 fl.rev, file=f),
342 "child": self.siblings(fl.children(n),
342 "child": self.siblings(fl.children(n),
343 fl.rev, file=f),
343 fl.rev, file=f),
344 "desc": cs[4]})
344 "desc": cs[4]})
345 parity = 1 - parity
345 parity = 1 - parity
346
346
347 for e in l:
347 for e in l:
348 yield e
348 yield e
349
349
350 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
350 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
351
351
352 def filerevision(self, f, node):
352 def filerevision(self, f, node):
353 fl = self.repo.file(f)
353 fl = self.repo.file(f)
354 n = fl.lookup(node)
354 n = fl.lookup(node)
355 node = hex(n)
355 node = hex(n)
356 text = fl.read(n)
356 text = fl.read(n)
357 changerev = fl.linkrev(n)
357 changerev = fl.linkrev(n)
358 cl = self.repo.changelog
358 cl = self.repo.changelog
359 cn = cl.node(changerev)
359 cn = cl.node(changerev)
360 cs = cl.read(cn)
360 cs = cl.read(cn)
361 mfn = cs[0]
361 mfn = cs[0]
362
362
363 mt = mimetypes.guess_type(f)[0]
363 mt = mimetypes.guess_type(f)[0]
364 rawtext = text
364 rawtext = text
365 if util.binary(text):
365 if util.binary(text):
366 mt = mt or 'application/octet-stream'
366 mt = mt or 'application/octet-stream'
367 text = "(binary:%s)" % mt
367 text = "(binary:%s)" % mt
368 mt = mt or 'text/plain'
368 mt = mt or 'text/plain'
369
369
370 def lines():
370 def lines():
371 for l, t in enumerate(text.splitlines(1)):
371 for l, t in enumerate(text.splitlines(1)):
372 yield {"line": t,
372 yield {"line": t,
373 "linenumber": "% 6d" % (l + 1),
373 "linenumber": "% 6d" % (l + 1),
374 "parity": self.stripes(l)}
374 "parity": self.stripes(l)}
375
375
376 yield self.t("filerevision",
376 yield self.t("filerevision",
377 file=f,
377 file=f,
378 filenode=node,
378 filenode=node,
379 path=_up(f),
379 path=_up(f),
380 text=lines(),
380 text=lines(),
381 raw=rawtext,
381 raw=rawtext,
382 mimetype=mt,
382 mimetype=mt,
383 rev=changerev,
383 rev=changerev,
384 node=hex(cn),
384 node=hex(cn),
385 manifest=hex(mfn),
385 manifest=hex(mfn),
386 author=cs[1],
386 author=cs[1],
387 date=cs[2],
387 date=cs[2],
388 parent=self.siblings(fl.parents(n), fl.rev, file=f),
388 parent=self.siblings(fl.parents(n), fl.rev, file=f),
389 child=self.siblings(fl.children(n), fl.rev, file=f),
389 child=self.siblings(fl.children(n), fl.rev, file=f),
390 rename=self.renamelink(fl, n),
390 rename=self.renamelink(fl, n),
391 permissions=self.repo.manifest.read(mfn).execf(f))
391 permissions=self.repo.manifest.read(mfn).execf(f))
392
392
393 def fileannotate(self, f, node):
393 def fileannotate(self, f, node):
394 bcache = {}
394 bcache = {}
395 ncache = {}
395 ncache = {}
396 fl = self.repo.file(f)
396 fl = self.repo.file(f)
397 n = fl.lookup(node)
397 n = fl.lookup(node)
398 node = hex(n)
398 node = hex(n)
399 changerev = fl.linkrev(n)
399 changerev = fl.linkrev(n)
400
400
401 cl = self.repo.changelog
401 cl = self.repo.changelog
402 cn = cl.node(changerev)
402 cn = cl.node(changerev)
403 cs = cl.read(cn)
403 cs = cl.read(cn)
404 mfn = cs[0]
404 mfn = cs[0]
405
405
406 def annotate(**map):
406 def annotate(**map):
407 parity = 0
407 parity = 0
408 last = None
408 last = None
409 for r, l in fl.annotate(n):
409 for r, l in fl.annotate(n):
410 try:
410 try:
411 cnode = ncache[r]
411 cnode = ncache[r]
412 except KeyError:
412 except KeyError:
413 cnode = ncache[r] = self.repo.changelog.node(r)
413 cnode = ncache[r] = self.repo.changelog.node(r)
414
414
415 try:
415 try:
416 name = bcache[r]
416 name = bcache[r]
417 except KeyError:
417 except KeyError:
418 cl = self.repo.changelog.read(cnode)
418 cl = self.repo.changelog.read(cnode)
419 bcache[r] = name = self.repo.ui.shortuser(cl[1])
419 bcache[r] = name = self.repo.ui.shortuser(cl[1])
420
420
421 if last != cnode:
421 if last != cnode:
422 parity = 1 - parity
422 parity = 1 - parity
423 last = cnode
423 last = cnode
424
424
425 yield {"parity": parity,
425 yield {"parity": parity,
426 "node": hex(cnode),
426 "node": hex(cnode),
427 "rev": r,
427 "rev": r,
428 "author": name,
428 "author": name,
429 "file": f,
429 "file": f,
430 "line": l}
430 "line": l}
431
431
432 yield self.t("fileannotate",
432 yield self.t("fileannotate",
433 file=f,
433 file=f,
434 filenode=node,
434 filenode=node,
435 annotate=annotate,
435 annotate=annotate,
436 path=_up(f),
436 path=_up(f),
437 rev=changerev,
437 rev=changerev,
438 node=hex(cn),
438 node=hex(cn),
439 manifest=hex(mfn),
439 manifest=hex(mfn),
440 author=cs[1],
440 author=cs[1],
441 date=cs[2],
441 date=cs[2],
442 rename=self.renamelink(fl, n),
442 rename=self.renamelink(fl, n),
443 parent=self.siblings(fl.parents(n), fl.rev, file=f),
443 parent=self.siblings(fl.parents(n), fl.rev, file=f),
444 child=self.siblings(fl.children(n), fl.rev, file=f),
444 child=self.siblings(fl.children(n), fl.rev, file=f),
445 permissions=self.repo.manifest.read(mfn).execf(f))
445 permissions=self.repo.manifest.read(mfn).execf(f))
446
446
447 def manifest(self, mnode, path):
447 def manifest(self, mnode, path):
448 man = self.repo.manifest
448 man = self.repo.manifest
449 mn = man.lookup(mnode)
449 mn = man.lookup(mnode)
450 mnode = hex(mn)
450 mnode = hex(mn)
451 mf = man.read(mn)
451 mf = man.read(mn)
452 rev = man.rev(mn)
452 rev = man.rev(mn)
453 changerev = man.linkrev(mn)
453 changerev = man.linkrev(mn)
454 node = self.repo.changelog.node(changerev)
454 node = self.repo.changelog.node(changerev)
455
455
456 files = {}
456 files = {}
457
457
458 p = path[1:]
458 p = path[1:]
459 if p and p[-1] != "/":
459 if p and p[-1] != "/":
460 p += "/"
460 p += "/"
461 l = len(p)
461 l = len(p)
462
462
463 for f,n in mf.items():
463 for f,n in mf.items():
464 if f[:l] != p:
464 if f[:l] != p:
465 continue
465 continue
466 remain = f[l:]
466 remain = f[l:]
467 if "/" in remain:
467 if "/" in remain:
468 short = remain[:remain.index("/") + 1] # bleah
468 short = remain[:remain.index("/") + 1] # bleah
469 files[short] = (f, None)
469 files[short] = (f, None)
470 else:
470 else:
471 short = os.path.basename(remain)
471 short = os.path.basename(remain)
472 files[short] = (f, n)
472 files[short] = (f, n)
473
473
474 def filelist(**map):
474 def filelist(**map):
475 parity = 0
475 parity = 0
476 fl = files.keys()
476 fl = files.keys()
477 fl.sort()
477 fl.sort()
478 for f in fl:
478 for f in fl:
479 full, fnode = files[f]
479 full, fnode = files[f]
480 if not fnode:
480 if not fnode:
481 continue
481 continue
482
482
483 yield {"file": full,
483 yield {"file": full,
484 "manifest": mnode,
484 "manifest": mnode,
485 "filenode": hex(fnode),
485 "filenode": hex(fnode),
486 "parity": self.stripes(parity),
486 "parity": self.stripes(parity),
487 "basename": f,
487 "basename": f,
488 "permissions": mf.execf(full)}
488 "permissions": mf.execf(full)}
489 parity += 1
489 parity += 1
490
490
491 def dirlist(**map):
491 def dirlist(**map):
492 parity = 0
492 parity = 0
493 fl = files.keys()
493 fl = files.keys()
494 fl.sort()
494 fl.sort()
495 for f in fl:
495 for f in fl:
496 full, fnode = files[f]
496 full, fnode = files[f]
497 if fnode:
497 if fnode:
498 continue
498 continue
499
499
500 yield {"parity": self.stripes(parity),
500 yield {"parity": self.stripes(parity),
501 "path": os.path.join(path, f),
501 "path": os.path.join(path, f),
502 "manifest": mnode,
502 "manifest": mnode,
503 "basename": f[:-1]}
503 "basename": f[:-1]}
504 parity += 1
504 parity += 1
505
505
506 yield self.t("manifest",
506 yield self.t("manifest",
507 manifest=mnode,
507 manifest=mnode,
508 rev=rev,
508 rev=rev,
509 node=hex(node),
509 node=hex(node),
510 path=path,
510 path=path,
511 up=_up(path),
511 up=_up(path),
512 fentries=filelist,
512 fentries=filelist,
513 dentries=dirlist,
513 dentries=dirlist,
514 archives=self.archivelist(hex(node)))
514 archives=self.archivelist(hex(node)))
515
515
516 def tags(self):
516 def tags(self):
517 cl = self.repo.changelog
517 cl = self.repo.changelog
518 mf = cl.read(cl.tip())[0]
518 mf = cl.read(cl.tip())[0]
519
519
520 i = self.repo.tagslist()
520 i = self.repo.tagslist()
521 i.reverse()
521 i.reverse()
522
522
523 def entries(notip=False, **map):
523 def entries(notip=False, **map):
524 parity = 0
524 parity = 0
525 for k,n in i:
525 for k,n in i:
526 if notip and k == "tip": continue
526 if notip and k == "tip": continue
527 yield {"parity": self.stripes(parity),
527 yield {"parity": self.stripes(parity),
528 "tag": k,
528 "tag": k,
529 "tagmanifest": hex(cl.read(n)[0]),
529 "tagmanifest": hex(cl.read(n)[0]),
530 "date": cl.read(n)[2],
530 "date": cl.read(n)[2],
531 "node": hex(n)}
531 "node": hex(n)}
532 parity += 1
532 parity += 1
533
533
534 yield self.t("tags",
534 yield self.t("tags",
535 manifest=hex(mf),
535 manifest=hex(mf),
536 entries=lambda **x: entries(False, **x),
536 entries=lambda **x: entries(False, **x),
537 entriesnotip=lambda **x: entries(True, **x))
537 entriesnotip=lambda **x: entries(True, **x))
538
538
539 def summary(self):
539 def summary(self):
540 cl = self.repo.changelog
540 cl = self.repo.changelog
541 mf = cl.read(cl.tip())[0]
541 mf = cl.read(cl.tip())[0]
542
542
543 i = self.repo.tagslist()
543 i = self.repo.tagslist()
544 i.reverse()
544 i.reverse()
545
545
546 def tagentries(**map):
546 def tagentries(**map):
547 parity = 0
547 parity = 0
548 count = 0
548 count = 0
549 for k,n in i:
549 for k,n in i:
550 if k == "tip": # skip tip
550 if k == "tip": # skip tip
551 continue;
551 continue;
552
552
553 count += 1
553 count += 1
554 if count > 10: # limit to 10 tags
554 if count > 10: # limit to 10 tags
555 break;
555 break;
556
556
557 c = cl.read(n)
557 c = cl.read(n)
558 m = c[0]
558 m = c[0]
559 t = c[2]
559 t = c[2]
560
560
561 yield self.t("tagentry",
561 yield self.t("tagentry",
562 parity = self.stripes(parity),
562 parity = self.stripes(parity),
563 tag = k,
563 tag = k,
564 node = hex(n),
564 node = hex(n),
565 date = t,
565 date = t,
566 tagmanifest = hex(m))
566 tagmanifest = hex(m))
567 parity += 1
567 parity += 1
568
568
569 def changelist(**map):
569 def changelist(**map):
570 parity = 0
570 parity = 0
571 cl = self.repo.changelog
571 cl = self.repo.changelog
572 l = [] # build a list in forward order for efficiency
572 l = [] # build a list in forward order for efficiency
573 for i in range(start, end):
573 for i in range(start, end):
574 n = cl.node(i)
574 n = cl.node(i)
575 changes = cl.read(n)
575 changes = cl.read(n)
576 hn = hex(n)
576 hn = hex(n)
577 t = changes[2]
577 t = changes[2]
578
578
579 l.insert(0, self.t(
579 l.insert(0, self.t(
580 'shortlogentry',
580 'shortlogentry',
581 parity = parity,
581 parity = parity,
582 author = changes[1],
582 author = changes[1],
583 manifest = hex(changes[0]),
583 manifest = hex(changes[0]),
584 desc = changes[4],
584 desc = changes[4],
585 date = t,
585 date = t,
586 rev = i,
586 rev = i,
587 node = hn))
587 node = hn))
588 parity = 1 - parity
588 parity = 1 - parity
589
589
590 yield l
590 yield l
591
591
592 cl = self.repo.changelog
592 cl = self.repo.changelog
593 mf = cl.read(cl.tip())[0]
593 mf = cl.read(cl.tip())[0]
594 count = cl.count()
594 count = cl.count()
595 start = max(0, count - self.maxchanges)
595 start = max(0, count - self.maxchanges)
596 end = min(count, start + self.maxchanges)
596 end = min(count, start + self.maxchanges)
597
597
598 yield self.t("summary",
598 yield self.t("summary",
599 desc = self.repo.ui.config("web", "description", "unknown"),
599 desc = self.repo.ui.config("web", "description", "unknown"),
600 owner = (self.repo.ui.config("ui", "username") or # preferred
600 owner = (self.repo.ui.config("ui", "username") or # preferred
601 self.repo.ui.config("web", "contact") or # deprecated
601 self.repo.ui.config("web", "contact") or # deprecated
602 self.repo.ui.config("web", "author", "unknown")), # also
602 self.repo.ui.config("web", "author", "unknown")), # also
603 lastchange = (0, 0), # FIXME
603 lastchange = (0, 0), # FIXME
604 manifest = hex(mf),
604 manifest = hex(mf),
605 tags = tagentries,
605 tags = tagentries,
606 shortlog = changelist,
606 shortlog = changelist,
607 archives=self.archivelist("tip"))
607 archives=self.archivelist("tip"))
608
608
609 def filediff(self, file, changeset):
609 def filediff(self, file, changeset):
610 cl = self.repo.changelog
610 cl = self.repo.changelog
611 n = self.repo.lookup(changeset)
611 n = self.repo.lookup(changeset)
612 changeset = hex(n)
612 changeset = hex(n)
613 p1 = cl.parents(n)[0]
613 p1 = cl.parents(n)[0]
614 cs = cl.read(n)
614 cs = cl.read(n)
615 mf = self.repo.manifest.read(cs[0])
615 mf = self.repo.manifest.read(cs[0])
616
616
617 def diff(**map):
617 def diff(**map):
618 yield self.diff(p1, n, [file])
618 yield self.diff(p1, n, [file])
619
619
620 yield self.t("filediff",
620 yield self.t("filediff",
621 file=file,
621 file=file,
622 filenode=hex(mf.get(file, nullid)),
622 filenode=hex(mf.get(file, nullid)),
623 node=changeset,
623 node=changeset,
624 rev=self.repo.changelog.rev(n),
624 rev=self.repo.changelog.rev(n),
625 parent=self.siblings(cl.parents(n), cl.rev),
625 parent=self.siblings(cl.parents(n), cl.rev),
626 child=self.siblings(cl.children(n), cl.rev),
626 child=self.siblings(cl.children(n), cl.rev),
627 diff=diff)
627 diff=diff)
628
628
629 archive_specs = {
629 archive_specs = {
630 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
630 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
631 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
631 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
632 'zip': ('application/zip', 'zip', '.zip', None),
632 'zip': ('application/zip', 'zip', '.zip', None),
633 }
633 }
634
634
635 def archive(self, req, cnode, type_):
635 def archive(self, req, cnode, type_):
636 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
636 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
637 name = "%s-%s" % (reponame, short(cnode))
637 name = "%s-%s" % (reponame, short(cnode))
638 mimetype, artype, extension, encoding = self.archive_specs[type_]
638 mimetype, artype, extension, encoding = self.archive_specs[type_]
639 headers = [('Content-type', mimetype),
639 headers = [('Content-type', mimetype),
640 ('Content-disposition', 'attachment; filename=%s%s' %
640 ('Content-disposition', 'attachment; filename=%s%s' %
641 (name, extension))]
641 (name, extension))]
642 if encoding:
642 if encoding:
643 headers.append(('Content-encoding', encoding))
643 headers.append(('Content-encoding', encoding))
644 req.header(headers)
644 req.header(headers)
645 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
645 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
646
646
647 # add tags to things
647 # add tags to things
648 # tags -> list of changesets corresponding to tags
648 # tags -> list of changesets corresponding to tags
649 # find tag, changeset, file
649 # find tag, changeset, file
650
650
651 def cleanpath(self, path):
651 def cleanpath(self, path):
652 p = util.normpath(path)
652 p = util.normpath(path)
653 if p[:2] == "..":
653 if p[:2] == "..":
654 raise Exception("suspicious path")
654 raise Exception("suspicious path")
655 return p
655 return p
656
656
657 def run(self):
657 def run(self):
658 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
658 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
659 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
659 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
660 import mercurial.hgweb.wsgicgi as wsgicgi
660 import mercurial.hgweb.wsgicgi as wsgicgi
661 from request import wsgiapplication
661 from request import wsgiapplication
662 def make_web_app():
662 def make_web_app():
663 return self
663 return self
664 wsgicgi.launch(wsgiapplication(make_web_app))
664 wsgicgi.launch(wsgiapplication(make_web_app))
665
665
666 def run_wsgi(self, req):
666 def run_wsgi(self, req):
667 def header(**map):
667 def header(**map):
668 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
668 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
669 msg = mimetools.Message(header_file, 0)
669 msg = mimetools.Message(header_file, 0)
670 req.header(msg.items())
670 req.header(msg.items())
671 yield header_file.read()
671 yield header_file.read()
672
672
673 def rawfileheader(**map):
673 def rawfileheader(**map):
674 req.header([('Content-type', map['mimetype']),
674 req.header([('Content-type', map['mimetype']),
675 ('Content-disposition', 'filename=%s' % map['file']),
675 ('Content-disposition', 'filename=%s' % map['file']),
676 ('Content-length', str(len(map['raw'])))])
676 ('Content-length', str(len(map['raw'])))])
677 yield ''
677 yield ''
678
678
679 def footer(**map):
679 def footer(**map):
680 yield self.t("footer",
680 yield self.t("footer",
681 motd=self.repo.ui.config("web", "motd", ""),
681 motd=self.repo.ui.config("web", "motd", ""),
682 **map)
682 **map)
683
683
684 def expand_form(form):
684 def expand_form(form):
685 shortcuts = {
685 shortcuts = {
686 'cl': [('cmd', ['changelog']), ('rev', None)],
686 'cl': [('cmd', ['changelog']), ('rev', None)],
687 'sl': [('cmd', ['shortlog']), ('rev', None)],
687 'sl': [('cmd', ['shortlog']), ('rev', None)],
688 'cs': [('cmd', ['changeset']), ('node', None)],
688 'cs': [('cmd', ['changeset']), ('node', None)],
689 'f': [('cmd', ['file']), ('filenode', None)],
689 'f': [('cmd', ['file']), ('filenode', None)],
690 'fl': [('cmd', ['filelog']), ('filenode', None)],
690 'fl': [('cmd', ['filelog']), ('filenode', None)],
691 'fd': [('cmd', ['filediff']), ('node', None)],
691 'fd': [('cmd', ['filediff']), ('node', None)],
692 'fa': [('cmd', ['annotate']), ('filenode', None)],
692 'fa': [('cmd', ['annotate']), ('filenode', None)],
693 'mf': [('cmd', ['manifest']), ('manifest', None)],
693 'mf': [('cmd', ['manifest']), ('manifest', None)],
694 'ca': [('cmd', ['archive']), ('node', None)],
694 'ca': [('cmd', ['archive']), ('node', None)],
695 'tags': [('cmd', ['tags'])],
695 'tags': [('cmd', ['tags'])],
696 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
696 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
697 'static': [('cmd', ['static']), ('file', None)]
697 'static': [('cmd', ['static']), ('file', None)]
698 }
698 }
699
699
700 for k in shortcuts.iterkeys():
700 for k in shortcuts.iterkeys():
701 if form.has_key(k):
701 if form.has_key(k):
702 for name, value in shortcuts[k]:
702 for name, value in shortcuts[k]:
703 if value is None:
703 if value is None:
704 value = form[k]
704 value = form[k]
705 form[name] = value
705 form[name] = value
706 del form[k]
706 del form[k]
707
707
708 self.refresh()
708 self.refresh()
709
709
710 expand_form(req.form)
710 expand_form(req.form)
711
711
712 m = os.path.join(self.templatepath, "map")
712 m = os.path.join(self.templatepath, "map")
713 style = self.repo.ui.config("web", "style", "")
713 style = self.repo.ui.config("web", "style", "")
714 if req.form.has_key('style'):
714 if req.form.has_key('style'):
715 style = req.form['style'][0]
715 style = req.form['style'][0]
716 if style:
716 if style:
717 b = os.path.basename("map-" + style)
717 b = os.path.basename("map-" + style)
718 p = os.path.join(self.templatepath, b)
718 p = os.path.join(self.templatepath, b)
719 if os.path.isfile(p):
719 if os.path.isfile(p):
720 m = p
720 m = p
721
721
722 port = req.env["SERVER_PORT"]
722 port = req.env["SERVER_PORT"]
723 port = port != "80" and (":" + port) or ""
723 port = port != "80" and (":" + port) or ""
724 uri = req.env["REQUEST_URI"]
724 uri = req.env["REQUEST_URI"]
725 if "?" in uri:
725 if "?" in uri:
726 uri = uri.split("?")[0]
726 uri = uri.split("?")[0]
727 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
727 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
728 if not self.reponame:
728 if not self.reponame:
729 self.reponame = (self.repo.ui.config("web", "name")
729 self.reponame = (self.repo.ui.config("web", "name")
730 or uri.strip('/') or self.repo.root)
730 or uri.strip('/') or self.repo.root)
731
731
732 self.t = templater.templater(m, templater.common_filters,
732 self.t = templater.templater(m, templater.common_filters,
733 defaults={"url": url,
733 defaults={"url": url,
734 "repo": self.reponame,
734 "repo": self.reponame,
735 "header": header,
735 "header": header,
736 "footer": footer,
736 "footer": footer,
737 "rawfileheader": rawfileheader,
737 "rawfileheader": rawfileheader,
738 })
738 })
739
739
740 if not req.form.has_key('cmd'):
740 if not req.form.has_key('cmd'):
741 req.form['cmd'] = [self.t.cache['default'],]
741 req.form['cmd'] = [self.t.cache['default'],]
742
742
743 cmd = req.form['cmd'][0]
743 cmd = req.form['cmd'][0]
744
744
745 method = getattr(self, 'do_' + cmd, None)
745 method = getattr(self, 'do_' + cmd, None)
746 if method:
746 if method:
747 method(req)
747 method(req)
748 else:
748 else:
749 req.write(self.t("error"))
749 req.write(self.t("error"))
750
750
751 def stripes(self, parity):
751 def stripes(self, parity):
752 "make horizontal stripes for easier reading"
752 "make horizontal stripes for easier reading"
753 if self.stripecount:
753 if self.stripecount:
754 return (1 + parity / self.stripecount) & 1
754 return (1 + parity / self.stripecount) & 1
755 else:
755 else:
756 return 0
756 return 0
757
757
758 def do_changelog(self, req):
758 def do_changelog(self, req):
759 hi = self.repo.changelog.count() - 1
759 hi = self.repo.changelog.count() - 1
760 if req.form.has_key('rev'):
760 if req.form.has_key('rev'):
761 hi = req.form['rev'][0]
761 hi = req.form['rev'][0]
762 try:
762 try:
763 hi = self.repo.changelog.rev(self.repo.lookup(hi))
763 hi = self.repo.changelog.rev(self.repo.lookup(hi))
764 except hg.RepoError:
764 except hg.RepoError:
765 req.write(self.search(hi)) # XXX redirect to 404 page?
765 req.write(self.search(hi)) # XXX redirect to 404 page?
766 return
766 return
767
767
768 req.write(self.changelog(hi))
768 req.write(self.changelog(hi))
769
769
770 def do_shortlog(self, req):
770 def do_shortlog(self, req):
771 hi = self.repo.changelog.count() - 1
771 hi = self.repo.changelog.count() - 1
772 if req.form.has_key('rev'):
772 if req.form.has_key('rev'):
773 hi = req.form['rev'][0]
773 hi = req.form['rev'][0]
774 try:
774 try:
775 hi = self.repo.changelog.rev(self.repo.lookup(hi))
775 hi = self.repo.changelog.rev(self.repo.lookup(hi))
776 except hg.RepoError:
776 except hg.RepoError:
777 req.write(self.search(hi)) # XXX redirect to 404 page?
777 req.write(self.search(hi)) # XXX redirect to 404 page?
778 return
778 return
779
779
780 req.write(self.changelog(hi, shortlog = True))
780 req.write(self.changelog(hi, shortlog = True))
781
781
782 def do_changeset(self, req):
782 def do_changeset(self, req):
783 req.write(self.changeset(req.form['node'][0]))
783 req.write(self.changeset(req.form['node'][0]))
784
784
785 def do_manifest(self, req):
785 def do_manifest(self, req):
786 req.write(self.manifest(req.form['manifest'][0],
786 req.write(self.manifest(req.form['manifest'][0],
787 self.cleanpath(req.form['path'][0])))
787 self.cleanpath(req.form['path'][0])))
788
788
789 def do_tags(self, req):
789 def do_tags(self, req):
790 req.write(self.tags())
790 req.write(self.tags())
791
791
792 def do_summary(self, req):
792 def do_summary(self, req):
793 req.write(self.summary())
793 req.write(self.summary())
794
794
795 def do_filediff(self, req):
795 def do_filediff(self, req):
796 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
796 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
797 req.form['node'][0]))
797 req.form['node'][0]))
798
798
799 def do_file(self, req):
799 def do_file(self, req):
800 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
800 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
801 req.form['filenode'][0]))
801 req.form['filenode'][0]))
802
802
803 def do_annotate(self, req):
803 def do_annotate(self, req):
804 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
804 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
805 req.form['filenode'][0]))
805 req.form['filenode'][0]))
806
806
807 def do_filelog(self, req):
807 def do_filelog(self, req):
808 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
808 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
809 req.form['filenode'][0]))
809 req.form['filenode'][0]))
810
810
811 def do_heads(self, req):
811 def do_heads(self, req):
812 resp = " ".join(map(hex, self.repo.heads())) + "\n"
812 resp = " ".join(map(hex, self.repo.heads())) + "\n"
813 req.httphdr("application/mercurial-0.1", length=len(resp))
813 req.httphdr("application/mercurial-0.1", length=len(resp))
814 req.write(resp)
814 req.write(resp)
815
815
816 def do_branches(self, req):
816 def do_branches(self, req):
817 nodes = []
817 nodes = []
818 if req.form.has_key('nodes'):
818 if req.form.has_key('nodes'):
819 nodes = map(bin, req.form['nodes'][0].split(" "))
819 nodes = map(bin, req.form['nodes'][0].split(" "))
820 resp = cStringIO.StringIO()
820 resp = cStringIO.StringIO()
821 for b in self.repo.branches(nodes):
821 for b in self.repo.branches(nodes):
822 resp.write(" ".join(map(hex, b)) + "\n")
822 resp.write(" ".join(map(hex, b)) + "\n")
823 resp = resp.getvalue()
823 resp = resp.getvalue()
824 req.httphdr("application/mercurial-0.1", length=len(resp))
824 req.httphdr("application/mercurial-0.1", length=len(resp))
825 req.write(resp)
825 req.write(resp)
826
826
827 def do_between(self, req):
827 def do_between(self, req):
828 nodes = []
828 nodes = []
829 if req.form.has_key('pairs'):
829 if req.form.has_key('pairs'):
830 pairs = [map(bin, p.split("-"))
830 pairs = [map(bin, p.split("-"))
831 for p in req.form['pairs'][0].split(" ")]
831 for p in req.form['pairs'][0].split(" ")]
832 resp = cStringIO.StringIO()
832 resp = cStringIO.StringIO()
833 for b in self.repo.between(pairs):
833 for b in self.repo.between(pairs):
834 resp.write(" ".join(map(hex, b)) + "\n")
834 resp.write(" ".join(map(hex, b)) + "\n")
835 resp = resp.getvalue()
835 resp = resp.getvalue()
836 req.httphdr("application/mercurial-0.1", length=len(resp))
836 req.httphdr("application/mercurial-0.1", length=len(resp))
837 req.write(resp)
837 req.write(resp)
838
838
839 def do_changegroup(self, req):
839 def do_changegroup(self, req):
840 req.httphdr("application/mercurial-0.1")
840 req.httphdr("application/mercurial-0.1")
841 nodes = []
841 nodes = []
842 if not self.allowpull:
842 if not self.allowpull:
843 return
843 return
844
844
845 if req.form.has_key('roots'):
845 if req.form.has_key('roots'):
846 nodes = map(bin, req.form['roots'][0].split(" "))
846 nodes = map(bin, req.form['roots'][0].split(" "))
847
847
848 z = zlib.compressobj()
848 z = zlib.compressobj()
849 f = self.repo.changegroup(nodes, 'serve')
849 f = self.repo.changegroup(nodes, 'serve')
850 while 1:
850 while 1:
851 chunk = f.read(4096)
851 chunk = f.read(4096)
852 if not chunk:
852 if not chunk:
853 break
853 break
854 req.write(z.compress(chunk))
854 req.write(z.compress(chunk))
855
855
856 req.write(z.flush())
856 req.write(z.flush())
857
857
858 def do_archive(self, req):
858 def do_archive(self, req):
859 changeset = self.repo.lookup(req.form['node'][0])
859 changeset = self.repo.lookup(req.form['node'][0])
860 type_ = req.form['type'][0]
860 type_ = req.form['type'][0]
861 allowed = self.repo.ui.configlist("web", "allow_archive")
861 allowed = self.repo.ui.configlist("web", "allow_archive")
862 if (type_ in self.archives and (type_ in allowed or
862 if (type_ in self.archives and (type_ in allowed or
863 self.repo.ui.configbool("web", "allow" + type_, False))):
863 self.repo.ui.configbool("web", "allow" + type_, False))):
864 self.archive(req, changeset, type_)
864 self.archive(req, changeset, type_)
865 return
865 return
866
866
867 req.write(self.t("error"))
867 req.write(self.t("error"))
868
868
869 def do_static(self, req):
869 def do_static(self, req):
870 fname = req.form['file'][0]
870 fname = req.form['file'][0]
871 static = self.repo.ui.config("web", "static",
871 static = self.repo.ui.config("web", "static",
872 os.path.join(self.templatepath,
872 os.path.join(self.templatepath,
873 "static"))
873 "static"))
874 req.write(staticfile(static, fname, req)
874 req.write(staticfile(static, fname, req)
875 or self.t("error", error="%r not found" % fname))
875 or self.t("error", error="%r not found" % fname))
876
876
877 def do_capabilities(self, req):
877 def do_capabilities(self, req):
878 caps = ['unbundle']
878 caps = ['unbundle']
879 if self.repo.ui.configbool('server', 'uncompressed'):
879 if self.repo.ui.configbool('server', 'uncompressed'):
880 caps.append('stream=%d' % self.repo.revlogversion)
880 caps.append('stream=%d' % self.repo.revlogversion)
881 resp = ' '.join(caps)
881 resp = ' '.join(caps)
882 req.httphdr("application/mercurial-0.1", length=len(resp))
882 req.httphdr("application/mercurial-0.1", length=len(resp))
883 req.write(resp)
883 req.write(resp)
884
884
885 def check_perm(self, req, op, default):
885 def check_perm(self, req, op, default):
886 '''check permission for operation based on user auth.
886 '''check permission for operation based on user auth.
887 return true if op allowed, else false.
887 return true if op allowed, else false.
888 default is policy to use if no config given.'''
888 default is policy to use if no config given.'''
889
889
890 user = req.env.get('REMOTE_USER')
890 user = req.env.get('REMOTE_USER')
891
891
892 deny = self.repo.ui.configlist('web', 'deny_' + op)
892 deny = self.repo.ui.configlist('web', 'deny_' + op)
893 if deny and (not user or deny == ['*'] or user in deny):
893 if deny and (not user or deny == ['*'] or user in deny):
894 return False
894 return False
895
895
896 allow = self.repo.ui.configlist('web', 'allow_' + op)
896 allow = self.repo.ui.configlist('web', 'allow_' + op)
897 return (allow and (allow == ['*'] or user in allow)) or default
897 return (allow and (allow == ['*'] or user in allow)) or default
898
898
899 def do_unbundle(self, req):
899 def do_unbundle(self, req):
900 def bail(response, headers={}):
900 def bail(response, headers={}):
901 length = int(req.env['CONTENT_LENGTH'])
901 length = int(req.env['CONTENT_LENGTH'])
902 for s in util.filechunkiter(req, limit=length):
902 for s in util.filechunkiter(req, limit=length):
903 # drain incoming bundle, else client will not see
903 # drain incoming bundle, else client will not see
904 # response when run outside cgi script
904 # response when run outside cgi script
905 pass
905 pass
906 req.httphdr("application/mercurial-0.1", headers=headers)
906 req.httphdr("application/mercurial-0.1", headers=headers)
907 req.write('0\n')
907 req.write('0\n')
908 req.write(response)
908 req.write(response)
909
909
910 # require ssl by default, auth info cannot be sniffed and
910 # require ssl by default, auth info cannot be sniffed and
911 # replayed
911 # replayed
912 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
912 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
913 if ssl_req:
913 if ssl_req:
914 if not req.env.get('HTTPS'):
914 if not req.env.get('HTTPS'):
915 bail(_('ssl required\n'))
915 bail(_('ssl required\n'))
916 return
916 return
917 proto = 'https'
917 proto = 'https'
918 else:
918 else:
919 proto = 'http'
919 proto = 'http'
920
920
921 # do not allow push unless explicitly allowed
921 # do not allow push unless explicitly allowed
922 if not self.check_perm(req, 'push', False):
922 if not self.check_perm(req, 'push', False):
923 bail(_('push not authorized\n'),
923 bail(_('push not authorized\n'),
924 headers={'status': '401 Unauthorized'})
924 headers={'status': '401 Unauthorized'})
925 return
925 return
926
926
927 req.httphdr("application/mercurial-0.1")
927 req.httphdr("application/mercurial-0.1")
928
928
929 their_heads = req.form['heads'][0].split(' ')
929 their_heads = req.form['heads'][0].split(' ')
930
930
931 def check_heads():
931 def check_heads():
932 heads = map(hex, self.repo.heads())
932 heads = map(hex, self.repo.heads())
933 return their_heads == [hex('force')] or their_heads == heads
933 return their_heads == [hex('force')] or their_heads == heads
934
934
935 # fail early if possible
935 # fail early if possible
936 if not check_heads():
936 if not check_heads():
937 bail(_('unsynced changes\n'))
937 bail(_('unsynced changes\n'))
938 return
938 return
939
939
940 # do not lock repo until all changegroup data is
940 # do not lock repo until all changegroup data is
941 # streamed. save to temporary file.
941 # streamed. save to temporary file.
942
942
943 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
943 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
944 fp = os.fdopen(fd, 'wb+')
944 fp = os.fdopen(fd, 'wb+')
945 try:
945 try:
946 length = int(req.env['CONTENT_LENGTH'])
946 length = int(req.env['CONTENT_LENGTH'])
947 for s in util.filechunkiter(req, limit=length):
947 for s in util.filechunkiter(req, limit=length):
948 fp.write(s)
948 fp.write(s)
949
949
950 lock = self.repo.lock()
950 lock = self.repo.lock()
951 try:
951 try:
952 if not check_heads():
952 if not check_heads():
953 req.write('0\n')
953 req.write('0\n')
954 req.write(_('unsynced changes\n'))
954 req.write(_('unsynced changes\n'))
955 return
955 return
956
956
957 fp.seek(0)
957 fp.seek(0)
958
958
959 # send addchangegroup output to client
959 # send addchangegroup output to client
960
960
961 old_stdout = sys.stdout
961 old_stdout = sys.stdout
962 sys.stdout = cStringIO.StringIO()
962 sys.stdout = cStringIO.StringIO()
963
963
964 try:
964 try:
965 url = 'remote:%s:%s' % (proto,
965 url = 'remote:%s:%s' % (proto,
966 req.env.get('REMOTE_HOST', ''))
966 req.env.get('REMOTE_HOST', ''))
967 ret = self.repo.addchangegroup(fp, 'serve', url)
967 ret = self.repo.addchangegroup(fp, 'serve', url)
968 finally:
968 finally:
969 val = sys.stdout.getvalue()
969 val = sys.stdout.getvalue()
970 sys.stdout = old_stdout
970 sys.stdout = old_stdout
971 req.write('%d\n' % ret)
971 req.write('%d\n' % ret)
972 req.write(val)
972 req.write(val)
973 finally:
973 finally:
974 lock.release()
974 lock.release()
975 finally:
975 finally:
976 fp.close()
976 fp.close()
977 os.unlink(tempname)
977 os.unlink(tempname)
978
978
979 def do_stream_out(self, req):
979 def do_stream_out(self, req):
980 req.httphdr("application/mercurial-0.1")
980 req.httphdr("application/mercurial-0.1")
981 streamclone.stream_out(self.repo, req)
981 streamclone.stream_out(self.repo, req)
@@ -1,365 +1,377 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from node import *
10 from node import *
11 demandload(globals(), "cmdutil mdiff util")
11 demandload(globals(), "cmdutil mdiff util")
12 demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
12 demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
13
13
14 def extract(ui, fileobj):
14 def extract(ui, fileobj):
15 '''extract patch from data read from fileobj.
15 '''extract patch from data read from fileobj.
16
16
17 patch can be normal patch or contained in email message.
17 patch can be normal patch or contained in email message.
18
18
19 return tuple (filename, message, user, date). any item in returned
19 return tuple (filename, message, user, date). any item in returned
20 tuple can be None. if filename is None, fileobj did not contain
20 tuple can be None. if filename is None, fileobj did not contain
21 patch. caller must unlink filename when done.'''
21 patch. caller must unlink filename when done.'''
22
22
23 # attempt to detect the start of a patch
23 # attempt to detect the start of a patch
24 # (this heuristic is borrowed from quilt)
24 # (this heuristic is borrowed from quilt)
25 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
25 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
26 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
26 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
27 '(---|\*\*\*)[ \t])', re.MULTILINE)
27 '(---|\*\*\*)[ \t])', re.MULTILINE)
28
28
29 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
29 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
30 tmpfp = os.fdopen(fd, 'w')
30 tmpfp = os.fdopen(fd, 'w')
31 try:
31 try:
32 hgpatch = False
32 hgpatch = False
33
33
34 msg = email.Parser.Parser().parse(fileobj)
34 msg = email.Parser.Parser().parse(fileobj)
35
35
36 message = msg['Subject']
36 message = msg['Subject']
37 user = msg['From']
37 user = msg['From']
38 # should try to parse msg['Date']
38 # should try to parse msg['Date']
39 date = None
39 date = None
40
40
41 if message:
41 if message:
42 message = message.replace('\n\t', ' ')
42 message = message.replace('\n\t', ' ')
43 ui.debug('Subject: %s\n' % message)
43 ui.debug('Subject: %s\n' % message)
44 if user:
44 if user:
45 ui.debug('From: %s\n' % user)
45 ui.debug('From: %s\n' % user)
46 diffs_seen = 0
46 diffs_seen = 0
47 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
47 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
48
48
49 for part in msg.walk():
49 for part in msg.walk():
50 content_type = part.get_content_type()
50 content_type = part.get_content_type()
51 ui.debug('Content-Type: %s\n' % content_type)
51 ui.debug('Content-Type: %s\n' % content_type)
52 if content_type not in ok_types:
52 if content_type not in ok_types:
53 continue
53 continue
54 payload = part.get_payload(decode=True)
54 payload = part.get_payload(decode=True)
55 m = diffre.search(payload)
55 m = diffre.search(payload)
56 if m:
56 if m:
57 ui.debug(_('found patch at byte %d\n') % m.start(0))
57 ui.debug(_('found patch at byte %d\n') % m.start(0))
58 diffs_seen += 1
58 diffs_seen += 1
59 cfp = cStringIO.StringIO()
59 cfp = cStringIO.StringIO()
60 if message:
60 if message:
61 cfp.write(message)
61 cfp.write(message)
62 cfp.write('\n')
62 cfp.write('\n')
63 for line in payload[:m.start(0)].splitlines():
63 for line in payload[:m.start(0)].splitlines():
64 if line.startswith('# HG changeset patch'):
64 if line.startswith('# HG changeset patch'):
65 ui.debug(_('patch generated by hg export\n'))
65 ui.debug(_('patch generated by hg export\n'))
66 hgpatch = True
66 hgpatch = True
67 # drop earlier commit message content
67 # drop earlier commit message content
68 cfp.seek(0)
68 cfp.seek(0)
69 cfp.truncate()
69 cfp.truncate()
70 elif hgpatch:
70 elif hgpatch:
71 if line.startswith('# User '):
71 if line.startswith('# User '):
72 user = line[7:]
72 user = line[7:]
73 ui.debug('From: %s\n' % user)
73 ui.debug('From: %s\n' % user)
74 elif line.startswith("# Date "):
74 elif line.startswith("# Date "):
75 date = line[7:]
75 date = line[7:]
76 if not line.startswith('# '):
76 if not line.startswith('# '):
77 cfp.write(line)
77 cfp.write(line)
78 cfp.write('\n')
78 cfp.write('\n')
79 message = cfp.getvalue()
79 message = cfp.getvalue()
80 if tmpfp:
80 if tmpfp:
81 tmpfp.write(payload)
81 tmpfp.write(payload)
82 if not payload.endswith('\n'):
82 if not payload.endswith('\n'):
83 tmpfp.write('\n')
83 tmpfp.write('\n')
84 elif not diffs_seen and message and content_type == 'text/plain':
84 elif not diffs_seen and message and content_type == 'text/plain':
85 message += '\n' + payload
85 message += '\n' + payload
86 except:
86 except:
87 tmpfp.close()
87 tmpfp.close()
88 os.unlink(tmpname)
88 os.unlink(tmpname)
89 raise
89 raise
90
90
91 tmpfp.close()
91 tmpfp.close()
92 if not diffs_seen:
92 if not diffs_seen:
93 os.unlink(tmpname)
93 os.unlink(tmpname)
94 return None, message, user, date
94 return None, message, user, date
95 return tmpname, message, user, date
95 return tmpname, message, user, date
96
96
97 def readgitpatch(patchname):
97 def readgitpatch(patchname):
98 """extract git-style metadata about patches from <patchname>"""
98 """extract git-style metadata about patches from <patchname>"""
99 class gitpatch:
99 class gitpatch:
100 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
100 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
101 def __init__(self, path):
101 def __init__(self, path):
102 self.path = path
102 self.path = path
103 self.oldpath = None
103 self.oldpath = None
104 self.mode = None
104 self.mode = None
105 self.op = 'MODIFY'
105 self.op = 'MODIFY'
106 self.copymod = False
106 self.copymod = False
107 self.lineno = 0
107 self.lineno = 0
108
108
109 # Filter patch for git information
109 # Filter patch for git information
110 gitre = re.compile('diff --git a/(.*) b/(.*)')
110 gitre = re.compile('diff --git a/(.*) b/(.*)')
111 pf = file(patchname)
111 pf = file(patchname)
112 gp = None
112 gp = None
113 gitpatches = []
113 gitpatches = []
114 # Can have a git patch with only metadata, causing patch to complain
114 # Can have a git patch with only metadata, causing patch to complain
115 dopatch = False
115 dopatch = False
116
116
117 lineno = 0
117 lineno = 0
118 for line in pf:
118 for line in pf:
119 lineno += 1
119 lineno += 1
120 if line.startswith('diff --git'):
120 if line.startswith('diff --git'):
121 m = gitre.match(line)
121 m = gitre.match(line)
122 if m:
122 if m:
123 if gp:
123 if gp:
124 gitpatches.append(gp)
124 gitpatches.append(gp)
125 src, dst = m.group(1,2)
125 src, dst = m.group(1,2)
126 gp = gitpatch(dst)
126 gp = gitpatch(dst)
127 gp.lineno = lineno
127 gp.lineno = lineno
128 elif gp:
128 elif gp:
129 if line.startswith('--- '):
129 if line.startswith('--- '):
130 if gp.op in ('COPY', 'RENAME'):
130 if gp.op in ('COPY', 'RENAME'):
131 gp.copymod = True
131 gp.copymod = True
132 dopatch = 'filter'
132 dopatch = 'filter'
133 gitpatches.append(gp)
133 gitpatches.append(gp)
134 gp = None
134 gp = None
135 if not dopatch:
135 if not dopatch:
136 dopatch = True
136 dopatch = True
137 continue
137 continue
138 if line.startswith('rename from '):
138 if line.startswith('rename from '):
139 gp.op = 'RENAME'
139 gp.op = 'RENAME'
140 gp.oldpath = line[12:].rstrip()
140 gp.oldpath = line[12:].rstrip()
141 elif line.startswith('rename to '):
141 elif line.startswith('rename to '):
142 gp.path = line[10:].rstrip()
142 gp.path = line[10:].rstrip()
143 elif line.startswith('copy from '):
143 elif line.startswith('copy from '):
144 gp.op = 'COPY'
144 gp.op = 'COPY'
145 gp.oldpath = line[10:].rstrip()
145 gp.oldpath = line[10:].rstrip()
146 elif line.startswith('copy to '):
146 elif line.startswith('copy to '):
147 gp.path = line[8:].rstrip()
147 gp.path = line[8:].rstrip()
148 elif line.startswith('deleted file'):
148 elif line.startswith('deleted file'):
149 gp.op = 'DELETE'
149 gp.op = 'DELETE'
150 elif line.startswith('new file mode '):
150 elif line.startswith('new file mode '):
151 gp.op = 'ADD'
151 gp.op = 'ADD'
152 gp.mode = int(line.rstrip()[-3:], 8)
152 gp.mode = int(line.rstrip()[-3:], 8)
153 elif line.startswith('new mode '):
153 elif line.startswith('new mode '):
154 gp.mode = int(line.rstrip()[-3:], 8)
154 gp.mode = int(line.rstrip()[-3:], 8)
155 if gp:
155 if gp:
156 gitpatches.append(gp)
156 gitpatches.append(gp)
157
157
158 if not gitpatches:
158 if not gitpatches:
159 dopatch = True
159 dopatch = True
160
160
161 return (dopatch, gitpatches)
161 return (dopatch, gitpatches)
162
162
163 def dogitpatch(patchname, gitpatches):
163 def dogitpatch(patchname, gitpatches):
164 """Preprocess git patch so that vanilla patch can handle it"""
164 """Preprocess git patch so that vanilla patch can handle it"""
165 pf = file(patchname)
165 pf = file(patchname)
166 pfline = 1
166 pfline = 1
167
167
168 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
168 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
169 tmpfp = os.fdopen(fd, 'w')
169 tmpfp = os.fdopen(fd, 'w')
170
170
171 try:
171 try:
172 for i in range(len(gitpatches)):
172 for i in range(len(gitpatches)):
173 p = gitpatches[i]
173 p = gitpatches[i]
174 if not p.copymod:
174 if not p.copymod:
175 continue
175 continue
176
176
177 if os.path.exists(p.path):
177 if os.path.exists(p.path):
178 raise util.Abort(_("cannot create %s: destination already exists") %
178 raise util.Abort(_("cannot create %s: destination already exists") %
179 p.path)
179 p.path)
180
180
181 (src, dst) = [os.path.join(os.getcwd(), n)
181 (src, dst) = [os.path.join(os.getcwd(), n)
182 for n in (p.oldpath, p.path)]
182 for n in (p.oldpath, p.path)]
183
183
184 targetdir = os.path.dirname(dst)
184 targetdir = os.path.dirname(dst)
185 if not os.path.isdir(targetdir):
185 if not os.path.isdir(targetdir):
186 os.makedirs(targetdir)
186 os.makedirs(targetdir)
187 try:
187 try:
188 shutil.copyfile(src, dst)
188 shutil.copyfile(src, dst)
189 shutil.copymode(src, dst)
189 shutil.copymode(src, dst)
190 except shutil.Error, inst:
190 except shutil.Error, inst:
191 raise util.Abort(str(inst))
191 raise util.Abort(str(inst))
192
192
193 # rewrite patch hunk
193 # rewrite patch hunk
194 while pfline < p.lineno:
194 while pfline < p.lineno:
195 tmpfp.write(pf.readline())
195 tmpfp.write(pf.readline())
196 pfline += 1
196 pfline += 1
197 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
197 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
198 line = pf.readline()
198 line = pf.readline()
199 pfline += 1
199 pfline += 1
200 while not line.startswith('--- a/'):
200 while not line.startswith('--- a/'):
201 tmpfp.write(line)
201 tmpfp.write(line)
202 line = pf.readline()
202 line = pf.readline()
203 pfline += 1
203 pfline += 1
204 tmpfp.write('--- a/%s\n' % p.path)
204 tmpfp.write('--- a/%s\n' % p.path)
205
205
206 line = pf.readline()
206 line = pf.readline()
207 while line:
207 while line:
208 tmpfp.write(line)
208 tmpfp.write(line)
209 line = pf.readline()
209 line = pf.readline()
210 except:
210 except:
211 tmpfp.close()
211 tmpfp.close()
212 os.unlink(patchname)
212 os.unlink(patchname)
213 raise
213 raise
214
214
215 tmpfp.close()
215 tmpfp.close()
216 return patchname
216 return patchname
217
217
218 def patch(strip, patchname, ui, cwd=None):
218 def patch(strip, patchname, ui, cwd=None):
219 """apply the patch <patchname> to the working directory.
219 """apply the patch <patchname> to the working directory.
220 a list of patched files is returned"""
220 a list of patched files is returned"""
221
221
222 (dopatch, gitpatches) = readgitpatch(patchname)
222 (dopatch, gitpatches) = readgitpatch(patchname)
223
223
224 files = {}
224 files = {}
225 if dopatch:
225 if dopatch:
226 if dopatch == 'filter':
226 if dopatch == 'filter':
227 patchname = dogitpatch(patchname, gitpatches)
227 patchname = dogitpatch(patchname, gitpatches)
228 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
228 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
229 args = []
229 args = []
230 if cwd:
230 if cwd:
231 args.append('-d %s' % util.shellquote(cwd))
231 args.append('-d %s' % util.shellquote(cwd))
232 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
232 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
233 util.shellquote(patchname)))
233 util.shellquote(patchname)))
234
234
235 if dopatch == 'filter':
235 if dopatch == 'filter':
236 False and os.unlink(patchname)
236 False and os.unlink(patchname)
237
237
238 for line in fp:
238 for line in fp:
239 line = line.rstrip()
239 line = line.rstrip()
240 ui.status("%s\n" % line)
240 ui.status("%s\n" % line)
241 if line.startswith('patching file '):
241 if line.startswith('patching file '):
242 pf = util.parse_patch_output(line)
242 pf = util.parse_patch_output(line)
243 files.setdefault(pf, (None, None))
243 files.setdefault(pf, (None, None))
244 code = fp.close()
244 code = fp.close()
245 if code:
245 if code:
246 raise util.Abort(_("patch command failed: %s") %
246 raise util.Abort(_("patch command failed: %s") %
247 util.explain_exit(code)[0])
247 util.explain_exit(code)[0])
248
248
249 for gp in gitpatches:
249 for gp in gitpatches:
250 files[gp.path] = (gp.op, gp)
250 files[gp.path] = (gp.op, gp)
251
251
252 return files
252 return files
253
253
254 def diffopts(ui, opts={}):
255 return mdiff.diffopts(
256 text=opts.get('text'),
257 showfunc=(opts.get('show_function') or
258 ui.configbool('diff', 'showfunc', None)),
259 ignorews=(opts.get('ignore_all_space') or
260 ui.configbool('diff', 'ignorews', None)),
261 ignorewsamount=(opts.get('ignore_space_change') or
262 ui.configbool('diff', 'ignorewsamount', None)),
263 ignoreblanklines=(opts.get('ignore_blank_lines') or
264 ui.configbool('diff', 'ignoreblanklines', None)))
265
254 def diff(repo, node1=None, node2=None, files=None, match=util.always,
266 def diff(repo, node1=None, node2=None, files=None, match=util.always,
255 fp=None, changes=None, opts=None):
267 fp=None, changes=None, opts=None):
256 '''print diff of changes to files between two nodes, or node and
268 '''print diff of changes to files between two nodes, or node and
257 working directory.
269 working directory.
258
270
259 if node1 is None, use first dirstate parent instead.
271 if node1 is None, use first dirstate parent instead.
260 if node2 is None, compare node1 with working directory.'''
272 if node2 is None, compare node1 with working directory.'''
261
273
262 if opts is None:
274 if opts is None:
263 opts = mdiff.defaultopts
275 opts = mdiff.defaultopts
264 if fp is None:
276 if fp is None:
265 fp = repo.ui
277 fp = repo.ui
266
278
267 if not node1:
279 if not node1:
268 node1 = repo.dirstate.parents()[0]
280 node1 = repo.dirstate.parents()[0]
269 # reading the data for node1 early allows it to play nicely
281 # reading the data for node1 early allows it to play nicely
270 # with repo.status and the revlog cache.
282 # with repo.status and the revlog cache.
271 change = repo.changelog.read(node1)
283 change = repo.changelog.read(node1)
272 mmap = repo.manifest.read(change[0])
284 mmap = repo.manifest.read(change[0])
273 date1 = util.datestr(change[2])
285 date1 = util.datestr(change[2])
274
286
275 if not changes:
287 if not changes:
276 changes = repo.status(node1, node2, files, match=match)[:5]
288 changes = repo.status(node1, node2, files, match=match)[:5]
277 modified, added, removed, deleted, unknown = changes
289 modified, added, removed, deleted, unknown = changes
278 if files:
290 if files:
279 def filterfiles(filters):
291 def filterfiles(filters):
280 l = [x for x in filters if x in files]
292 l = [x for x in filters if x in files]
281
293
282 for t in files:
294 for t in files:
283 if not t.endswith("/"):
295 if not t.endswith("/"):
284 t += "/"
296 t += "/"
285 l += [x for x in filters if x.startswith(t)]
297 l += [x for x in filters if x.startswith(t)]
286 return l
298 return l
287
299
288 modified, added, removed = map(filterfiles, (modified, added, removed))
300 modified, added, removed = map(filterfiles, (modified, added, removed))
289
301
290 if not modified and not added and not removed:
302 if not modified and not added and not removed:
291 return
303 return
292
304
293 if node2:
305 if node2:
294 change = repo.changelog.read(node2)
306 change = repo.changelog.read(node2)
295 mmap2 = repo.manifest.read(change[0])
307 mmap2 = repo.manifest.read(change[0])
296 _date2 = util.datestr(change[2])
308 _date2 = util.datestr(change[2])
297 def date2(f):
309 def date2(f):
298 return _date2
310 return _date2
299 def read(f):
311 def read(f):
300 return repo.file(f).read(mmap2[f])
312 return repo.file(f).read(mmap2[f])
301 else:
313 else:
302 tz = util.makedate()[1]
314 tz = util.makedate()[1]
303 _date2 = util.datestr()
315 _date2 = util.datestr()
304 def date2(f):
316 def date2(f):
305 try:
317 try:
306 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
318 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
307 except OSError, err:
319 except OSError, err:
308 if err.errno != errno.ENOENT: raise
320 if err.errno != errno.ENOENT: raise
309 return _date2
321 return _date2
310 def read(f):
322 def read(f):
311 return repo.wread(f)
323 return repo.wread(f)
312
324
313 if repo.ui.quiet:
325 if repo.ui.quiet:
314 r = None
326 r = None
315 else:
327 else:
316 hexfunc = repo.ui.verbose and hex or short
328 hexfunc = repo.ui.verbose and hex or short
317 r = [hexfunc(node) for node in [node1, node2] if node]
329 r = [hexfunc(node) for node in [node1, node2] if node]
318
330
319 all = modified + added + removed
331 all = modified + added + removed
320 all.sort()
332 all.sort()
321 for f in all:
333 for f in all:
322 to = None
334 to = None
323 tn = None
335 tn = None
324 if f in mmap:
336 if f in mmap:
325 to = repo.file(f).read(mmap[f])
337 to = repo.file(f).read(mmap[f])
326 if f not in removed:
338 if f not in removed:
327 tn = read(f)
339 tn = read(f)
328 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
340 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
329
341
330 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
342 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
331 opts=None):
343 opts=None):
332 '''export changesets as hg patches.'''
344 '''export changesets as hg patches.'''
333
345
334 total = len(revs)
346 total = len(revs)
335 revwidth = max(map(len, revs))
347 revwidth = max(map(len, revs))
336
348
337 def single(node, seqno, fp):
349 def single(node, seqno, fp):
338 parents = [p for p in repo.changelog.parents(node) if p != nullid]
350 parents = [p for p in repo.changelog.parents(node) if p != nullid]
339 if switch_parent:
351 if switch_parent:
340 parents.reverse()
352 parents.reverse()
341 prev = (parents and parents[0]) or nullid
353 prev = (parents and parents[0]) or nullid
342 change = repo.changelog.read(node)
354 change = repo.changelog.read(node)
343
355
344 if not fp:
356 if not fp:
345 fp = cmdutil.make_file(repo, template, node, total=total,
357 fp = cmdutil.make_file(repo, template, node, total=total,
346 seqno=seqno, revwidth=revwidth)
358 seqno=seqno, revwidth=revwidth)
347 if fp not in (sys.stdout, repo.ui):
359 if fp not in (sys.stdout, repo.ui):
348 repo.ui.note("%s\n" % fp.name)
360 repo.ui.note("%s\n" % fp.name)
349
361
350 fp.write("# HG changeset patch\n")
362 fp.write("# HG changeset patch\n")
351 fp.write("# User %s\n" % change[1])
363 fp.write("# User %s\n" % change[1])
352 fp.write("# Date %d %d\n" % change[2])
364 fp.write("# Date %d %d\n" % change[2])
353 fp.write("# Node ID %s\n" % hex(node))
365 fp.write("# Node ID %s\n" % hex(node))
354 fp.write("# Parent %s\n" % hex(prev))
366 fp.write("# Parent %s\n" % hex(prev))
355 if len(parents) > 1:
367 if len(parents) > 1:
356 fp.write("# Parent %s\n" % hex(parents[1]))
368 fp.write("# Parent %s\n" % hex(parents[1]))
357 fp.write(change[4].rstrip())
369 fp.write(change[4].rstrip())
358 fp.write("\n\n")
370 fp.write("\n\n")
359
371
360 diff(repo, prev, node, fp=fp, opts=opts)
372 diff(repo, prev, node, fp=fp, opts=opts)
361 if fp not in (sys.stdout, repo.ui):
373 if fp not in (sys.stdout, repo.ui):
362 fp.close()
374 fp.close()
363
375
364 for seqno, cset in enumerate(revs):
376 for seqno, cset in enumerate(revs):
365 single(cset, seqno, fp)
377 single(cset, seqno, fp)
@@ -1,358 +1,346 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import gettext as _
8 from i18n import gettext as _
9 from demandload import *
9 from demandload import *
10 demandload(globals(), "errno getpass os re smtplib socket sys tempfile")
10 demandload(globals(), "errno getpass os re smtplib socket sys tempfile")
11 demandload(globals(), "ConfigParser mdiff templater traceback util")
11 demandload(globals(), "ConfigParser mdiff templater traceback util")
12
12
13 class ui(object):
13 class ui(object):
14 def __init__(self, verbose=False, debug=False, quiet=False,
14 def __init__(self, verbose=False, debug=False, quiet=False,
15 interactive=True, traceback=False, parentui=None):
15 interactive=True, traceback=False, parentui=None):
16 self.overlay = {}
16 self.overlay = {}
17 if parentui is None:
17 if parentui is None:
18 # this is the parent of all ui children
18 # this is the parent of all ui children
19 self.parentui = None
19 self.parentui = None
20 self.cdata = ConfigParser.SafeConfigParser()
20 self.cdata = ConfigParser.SafeConfigParser()
21 self.readconfig(util.rcpath())
21 self.readconfig(util.rcpath())
22
22
23 self.quiet = self.configbool("ui", "quiet")
23 self.quiet = self.configbool("ui", "quiet")
24 self.verbose = self.configbool("ui", "verbose")
24 self.verbose = self.configbool("ui", "verbose")
25 self.debugflag = self.configbool("ui", "debug")
25 self.debugflag = self.configbool("ui", "debug")
26 self.interactive = self.configbool("ui", "interactive", True)
26 self.interactive = self.configbool("ui", "interactive", True)
27 self.traceback = traceback
27 self.traceback = traceback
28
28
29 self.updateopts(verbose, debug, quiet, interactive)
29 self.updateopts(verbose, debug, quiet, interactive)
30 self.diffcache = None
30 self.diffcache = None
31 self.header = []
31 self.header = []
32 self.prev_header = []
32 self.prev_header = []
33 self.revlogopts = self.configrevlog()
33 self.revlogopts = self.configrevlog()
34 else:
34 else:
35 # parentui may point to an ui object which is already a child
35 # parentui may point to an ui object which is already a child
36 self.parentui = parentui.parentui or parentui
36 self.parentui = parentui.parentui or parentui
37 parent_cdata = self.parentui.cdata
37 parent_cdata = self.parentui.cdata
38 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
38 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
39 # make interpolation work
39 # make interpolation work
40 for section in parent_cdata.sections():
40 for section in parent_cdata.sections():
41 self.cdata.add_section(section)
41 self.cdata.add_section(section)
42 for name, value in parent_cdata.items(section, raw=True):
42 for name, value in parent_cdata.items(section, raw=True):
43 self.cdata.set(section, name, value)
43 self.cdata.set(section, name, value)
44
44
45 def __getattr__(self, key):
45 def __getattr__(self, key):
46 return getattr(self.parentui, key)
46 return getattr(self.parentui, key)
47
47
48 def updateopts(self, verbose=False, debug=False, quiet=False,
48 def updateopts(self, verbose=False, debug=False, quiet=False,
49 interactive=True, traceback=False, config=[]):
49 interactive=True, traceback=False, config=[]):
50 self.quiet = (self.quiet or quiet) and not verbose and not debug
50 self.quiet = (self.quiet or quiet) and not verbose and not debug
51 self.verbose = (self.verbose or verbose) or debug
51 self.verbose = (self.verbose or verbose) or debug
52 self.debugflag = (self.debugflag or debug)
52 self.debugflag = (self.debugflag or debug)
53 self.interactive = (self.interactive and interactive)
53 self.interactive = (self.interactive and interactive)
54 self.traceback = self.traceback or traceback
54 self.traceback = self.traceback or traceback
55 for cfg in config:
55 for cfg in config:
56 try:
56 try:
57 name, value = cfg.split('=', 1)
57 name, value = cfg.split('=', 1)
58 section, name = name.split('.', 1)
58 section, name = name.split('.', 1)
59 if not self.cdata.has_section(section):
59 if not self.cdata.has_section(section):
60 self.cdata.add_section(section)
60 self.cdata.add_section(section)
61 if not section or not name:
61 if not section or not name:
62 raise IndexError
62 raise IndexError
63 self.cdata.set(section, name, value)
63 self.cdata.set(section, name, value)
64 except (IndexError, ValueError):
64 except (IndexError, ValueError):
65 raise util.Abort(_('malformed --config option: %s') % cfg)
65 raise util.Abort(_('malformed --config option: %s') % cfg)
66
66
67 def readconfig(self, fn, root=None):
67 def readconfig(self, fn, root=None):
68 if isinstance(fn, basestring):
68 if isinstance(fn, basestring):
69 fn = [fn]
69 fn = [fn]
70 for f in fn:
70 for f in fn:
71 try:
71 try:
72 self.cdata.read(f)
72 self.cdata.read(f)
73 except ConfigParser.ParsingError, inst:
73 except ConfigParser.ParsingError, inst:
74 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
74 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
75 # translate paths relative to root (or home) into absolute paths
75 # translate paths relative to root (or home) into absolute paths
76 if root is None:
76 if root is None:
77 root = os.path.expanduser('~')
77 root = os.path.expanduser('~')
78 for name, path in self.configitems("paths"):
78 for name, path in self.configitems("paths"):
79 if path and "://" not in path and not os.path.isabs(path):
79 if path and "://" not in path and not os.path.isabs(path):
80 self.cdata.set("paths", name, os.path.join(root, path))
80 self.cdata.set("paths", name, os.path.join(root, path))
81
81
82 def setconfig(self, section, name, val):
82 def setconfig(self, section, name, val):
83 self.overlay[(section, name)] = val
83 self.overlay[(section, name)] = val
84
84
85 def config(self, section, name, default=None):
85 def config(self, section, name, default=None):
86 if self.overlay.has_key((section, name)):
86 if self.overlay.has_key((section, name)):
87 return self.overlay[(section, name)]
87 return self.overlay[(section, name)]
88 if self.cdata.has_option(section, name):
88 if self.cdata.has_option(section, name):
89 try:
89 try:
90 return self.cdata.get(section, name)
90 return self.cdata.get(section, name)
91 except ConfigParser.InterpolationError, inst:
91 except ConfigParser.InterpolationError, inst:
92 raise util.Abort(_("Error in configuration:\n%s") % inst)
92 raise util.Abort(_("Error in configuration:\n%s") % inst)
93 if self.parentui is None:
93 if self.parentui is None:
94 return default
94 return default
95 else:
95 else:
96 return self.parentui.config(section, name, default)
96 return self.parentui.config(section, name, default)
97
97
98 def configlist(self, section, name, default=None):
98 def configlist(self, section, name, default=None):
99 """Return a list of comma/space separated strings"""
99 """Return a list of comma/space separated strings"""
100 result = self.config(section, name)
100 result = self.config(section, name)
101 if result is None:
101 if result is None:
102 result = default or []
102 result = default or []
103 if isinstance(result, basestring):
103 if isinstance(result, basestring):
104 result = result.replace(",", " ").split()
104 result = result.replace(",", " ").split()
105 return result
105 return result
106
106
107 def configbool(self, section, name, default=False):
107 def configbool(self, section, name, default=False):
108 if self.overlay.has_key((section, name)):
108 if self.overlay.has_key((section, name)):
109 return self.overlay[(section, name)]
109 return self.overlay[(section, name)]
110 if self.cdata.has_option(section, name):
110 if self.cdata.has_option(section, name):
111 try:
111 try:
112 return self.cdata.getboolean(section, name)
112 return self.cdata.getboolean(section, name)
113 except ConfigParser.InterpolationError, inst:
113 except ConfigParser.InterpolationError, inst:
114 raise util.Abort(_("Error in configuration:\n%s") % inst)
114 raise util.Abort(_("Error in configuration:\n%s") % inst)
115 if self.parentui is None:
115 if self.parentui is None:
116 return default
116 return default
117 else:
117 else:
118 return self.parentui.configbool(section, name, default)
118 return self.parentui.configbool(section, name, default)
119
119
120 def has_config(self, section):
120 def has_config(self, section):
121 '''tell whether section exists in config.'''
121 '''tell whether section exists in config.'''
122 return self.cdata.has_section(section)
122 return self.cdata.has_section(section)
123
123
124 def configitems(self, section):
124 def configitems(self, section):
125 items = {}
125 items = {}
126 if self.parentui is not None:
126 if self.parentui is not None:
127 items = dict(self.parentui.configitems(section))
127 items = dict(self.parentui.configitems(section))
128 if self.cdata.has_section(section):
128 if self.cdata.has_section(section):
129 try:
129 try:
130 items.update(dict(self.cdata.items(section)))
130 items.update(dict(self.cdata.items(section)))
131 except ConfigParser.InterpolationError, inst:
131 except ConfigParser.InterpolationError, inst:
132 raise util.Abort(_("Error in configuration:\n%s") % inst)
132 raise util.Abort(_("Error in configuration:\n%s") % inst)
133 x = items.items()
133 x = items.items()
134 x.sort()
134 x.sort()
135 return x
135 return x
136
136
137 def walkconfig(self, seen=None):
137 def walkconfig(self, seen=None):
138 if seen is None:
138 if seen is None:
139 seen = {}
139 seen = {}
140 for (section, name), value in self.overlay.iteritems():
140 for (section, name), value in self.overlay.iteritems():
141 yield section, name, value
141 yield section, name, value
142 seen[section, name] = 1
142 seen[section, name] = 1
143 for section in self.cdata.sections():
143 for section in self.cdata.sections():
144 for name, value in self.cdata.items(section):
144 for name, value in self.cdata.items(section):
145 if (section, name) in seen: continue
145 if (section, name) in seen: continue
146 yield section, name, value.replace('\n', '\\n')
146 yield section, name, value.replace('\n', '\\n')
147 seen[section, name] = 1
147 seen[section, name] = 1
148 if self.parentui is not None:
148 if self.parentui is not None:
149 for parent in self.parentui.walkconfig(seen):
149 for parent in self.parentui.walkconfig(seen):
150 yield parent
150 yield parent
151
151
152 def extensions(self):
152 def extensions(self):
153 result = self.configitems("extensions")
153 result = self.configitems("extensions")
154 for i, (key, value) in enumerate(result):
154 for i, (key, value) in enumerate(result):
155 if value:
155 if value:
156 result[i] = (key, os.path.expanduser(value))
156 result[i] = (key, os.path.expanduser(value))
157 return result
157 return result
158
158
159 def hgignorefiles(self):
159 def hgignorefiles(self):
160 result = []
160 result = []
161 for key, value in self.configitems("ui"):
161 for key, value in self.configitems("ui"):
162 if key == 'ignore' or key.startswith('ignore.'):
162 if key == 'ignore' or key.startswith('ignore.'):
163 result.append(os.path.expanduser(value))
163 result.append(os.path.expanduser(value))
164 return result
164 return result
165
165
166 def configrevlog(self):
166 def configrevlog(self):
167 result = {}
167 result = {}
168 for key, value in self.configitems("revlog"):
168 for key, value in self.configitems("revlog"):
169 result[key.lower()] = value
169 result[key.lower()] = value
170 return result
170 return result
171
171
172 def diffopts(self, opts={}):
173 return mdiff.diffopts(
174 text=opts.get('text'),
175 showfunc=(opts.get('show_function') or
176 self.configbool('diff', 'showfunc', None)),
177 ignorews=(opts.get('ignore_all_space') or
178 self.configbool('diff', 'ignorews', None)),
179 ignorewsamount=(opts.get('ignore_space_change') or
180 self.configbool('diff', 'ignorewsamount', None)),
181 ignoreblanklines=(opts.get('ignore_blank_lines') or
182 self.configbool('diff', 'ignoreblanklines', None)))
183
184 def username(self):
172 def username(self):
185 """Return default username to be used in commits.
173 """Return default username to be used in commits.
186
174
187 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
175 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
188 and stop searching if one of these is set.
176 and stop searching if one of these is set.
189 Abort if found username is an empty string to force specifying
177 Abort if found username is an empty string to force specifying
190 the commit user elsewhere, e.g. with line option or repo hgrc.
178 the commit user elsewhere, e.g. with line option or repo hgrc.
191 If not found, use ($LOGNAME or $USER or $LNAME or
179 If not found, use ($LOGNAME or $USER or $LNAME or
192 $USERNAME) +"@full.hostname".
180 $USERNAME) +"@full.hostname".
193 """
181 """
194 user = os.environ.get("HGUSER")
182 user = os.environ.get("HGUSER")
195 if user is None:
183 if user is None:
196 user = self.config("ui", "username")
184 user = self.config("ui", "username")
197 if user is None:
185 if user is None:
198 user = os.environ.get("EMAIL")
186 user = os.environ.get("EMAIL")
199 if user is None:
187 if user is None:
200 try:
188 try:
201 user = '%s@%s' % (util.getuser(), socket.getfqdn())
189 user = '%s@%s' % (util.getuser(), socket.getfqdn())
202 except KeyError:
190 except KeyError:
203 raise util.Abort(_("Please specify a username."))
191 raise util.Abort(_("Please specify a username."))
204 return user
192 return user
205
193
206 def shortuser(self, user):
194 def shortuser(self, user):
207 """Return a short representation of a user name or email address."""
195 """Return a short representation of a user name or email address."""
208 if not self.verbose: user = util.shortuser(user)
196 if not self.verbose: user = util.shortuser(user)
209 return user
197 return user
210
198
211 def expandpath(self, loc, default=None):
199 def expandpath(self, loc, default=None):
212 """Return repository location relative to cwd or from [paths]"""
200 """Return repository location relative to cwd or from [paths]"""
213 if "://" in loc or os.path.isdir(loc):
201 if "://" in loc or os.path.isdir(loc):
214 return loc
202 return loc
215
203
216 path = self.config("paths", loc)
204 path = self.config("paths", loc)
217 if not path and default is not None:
205 if not path and default is not None:
218 path = self.config("paths", default)
206 path = self.config("paths", default)
219 return path or loc
207 return path or loc
220
208
221 def write(self, *args):
209 def write(self, *args):
222 if self.header:
210 if self.header:
223 if self.header != self.prev_header:
211 if self.header != self.prev_header:
224 self.prev_header = self.header
212 self.prev_header = self.header
225 self.write(*self.header)
213 self.write(*self.header)
226 self.header = []
214 self.header = []
227 for a in args:
215 for a in args:
228 sys.stdout.write(str(a))
216 sys.stdout.write(str(a))
229
217
230 def write_header(self, *args):
218 def write_header(self, *args):
231 for a in args:
219 for a in args:
232 self.header.append(str(a))
220 self.header.append(str(a))
233
221
234 def write_err(self, *args):
222 def write_err(self, *args):
235 try:
223 try:
236 if not sys.stdout.closed: sys.stdout.flush()
224 if not sys.stdout.closed: sys.stdout.flush()
237 for a in args:
225 for a in args:
238 sys.stderr.write(str(a))
226 sys.stderr.write(str(a))
239 except IOError, inst:
227 except IOError, inst:
240 if inst.errno != errno.EPIPE:
228 if inst.errno != errno.EPIPE:
241 raise
229 raise
242
230
243 def flush(self):
231 def flush(self):
244 try: sys.stdout.flush()
232 try: sys.stdout.flush()
245 except: pass
233 except: pass
246 try: sys.stderr.flush()
234 try: sys.stderr.flush()
247 except: pass
235 except: pass
248
236
249 def readline(self):
237 def readline(self):
250 return sys.stdin.readline()[:-1]
238 return sys.stdin.readline()[:-1]
251 def prompt(self, msg, pat=None, default="y"):
239 def prompt(self, msg, pat=None, default="y"):
252 if not self.interactive: return default
240 if not self.interactive: return default
253 while 1:
241 while 1:
254 self.write(msg, " ")
242 self.write(msg, " ")
255 r = self.readline()
243 r = self.readline()
256 if not pat or re.match(pat, r):
244 if not pat or re.match(pat, r):
257 return r
245 return r
258 else:
246 else:
259 self.write(_("unrecognized response\n"))
247 self.write(_("unrecognized response\n"))
260 def getpass(self, prompt=None, default=None):
248 def getpass(self, prompt=None, default=None):
261 if not self.interactive: return default
249 if not self.interactive: return default
262 return getpass.getpass(prompt or _('password: '))
250 return getpass.getpass(prompt or _('password: '))
263 def status(self, *msg):
251 def status(self, *msg):
264 if not self.quiet: self.write(*msg)
252 if not self.quiet: self.write(*msg)
265 def warn(self, *msg):
253 def warn(self, *msg):
266 self.write_err(*msg)
254 self.write_err(*msg)
267 def note(self, *msg):
255 def note(self, *msg):
268 if self.verbose: self.write(*msg)
256 if self.verbose: self.write(*msg)
269 def debug(self, *msg):
257 def debug(self, *msg):
270 if self.debugflag: self.write(*msg)
258 if self.debugflag: self.write(*msg)
271 def edit(self, text, user):
259 def edit(self, text, user):
272 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
260 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
273 text=True)
261 text=True)
274 try:
262 try:
275 f = os.fdopen(fd, "w")
263 f = os.fdopen(fd, "w")
276 f.write(text)
264 f.write(text)
277 f.close()
265 f.close()
278
266
279 editor = (os.environ.get("HGEDITOR") or
267 editor = (os.environ.get("HGEDITOR") or
280 self.config("ui", "editor") or
268 self.config("ui", "editor") or
281 os.environ.get("EDITOR", "vi"))
269 os.environ.get("EDITOR", "vi"))
282
270
283 util.system("%s \"%s\"" % (editor, name),
271 util.system("%s \"%s\"" % (editor, name),
284 environ={'HGUSER': user},
272 environ={'HGUSER': user},
285 onerr=util.Abort, errprefix=_("edit failed"))
273 onerr=util.Abort, errprefix=_("edit failed"))
286
274
287 f = open(name)
275 f = open(name)
288 t = f.read()
276 t = f.read()
289 f.close()
277 f.close()
290 t = re.sub("(?m)^HG:.*\n", "", t)
278 t = re.sub("(?m)^HG:.*\n", "", t)
291 finally:
279 finally:
292 os.unlink(name)
280 os.unlink(name)
293
281
294 return t
282 return t
295
283
296 def sendmail(self):
284 def sendmail(self):
297 '''send mail message. object returned has one method, sendmail.
285 '''send mail message. object returned has one method, sendmail.
298 call as sendmail(sender, list-of-recipients, msg).'''
286 call as sendmail(sender, list-of-recipients, msg).'''
299
287
300 def smtp():
288 def smtp():
301 '''send mail using smtp.'''
289 '''send mail using smtp.'''
302
290
303 local_hostname = self.config('smtp', 'local_hostname')
291 local_hostname = self.config('smtp', 'local_hostname')
304 s = smtplib.SMTP(local_hostname=local_hostname)
292 s = smtplib.SMTP(local_hostname=local_hostname)
305 mailhost = self.config('smtp', 'host')
293 mailhost = self.config('smtp', 'host')
306 if not mailhost:
294 if not mailhost:
307 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
295 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
308 mailport = int(self.config('smtp', 'port', 25))
296 mailport = int(self.config('smtp', 'port', 25))
309 self.note(_('sending mail: smtp host %s, port %s\n') %
297 self.note(_('sending mail: smtp host %s, port %s\n') %
310 (mailhost, mailport))
298 (mailhost, mailport))
311 s.connect(host=mailhost, port=mailport)
299 s.connect(host=mailhost, port=mailport)
312 if self.configbool('smtp', 'tls'):
300 if self.configbool('smtp', 'tls'):
313 self.note(_('(using tls)\n'))
301 self.note(_('(using tls)\n'))
314 s.ehlo()
302 s.ehlo()
315 s.starttls()
303 s.starttls()
316 s.ehlo()
304 s.ehlo()
317 username = self.config('smtp', 'username')
305 username = self.config('smtp', 'username')
318 password = self.config('smtp', 'password')
306 password = self.config('smtp', 'password')
319 if username and password:
307 if username and password:
320 self.note(_('(authenticating to mail server as %s)\n') %
308 self.note(_('(authenticating to mail server as %s)\n') %
321 (username))
309 (username))
322 s.login(username, password)
310 s.login(username, password)
323 return s
311 return s
324
312
325 class sendmail(object):
313 class sendmail(object):
326 '''send mail using sendmail.'''
314 '''send mail using sendmail.'''
327
315
328 def __init__(self, ui, program):
316 def __init__(self, ui, program):
329 self.ui = ui
317 self.ui = ui
330 self.program = program
318 self.program = program
331
319
332 def sendmail(self, sender, recipients, msg):
320 def sendmail(self, sender, recipients, msg):
333 cmdline = '%s -f %s %s' % (
321 cmdline = '%s -f %s %s' % (
334 self.program, templater.email(sender),
322 self.program, templater.email(sender),
335 ' '.join(map(templater.email, recipients)))
323 ' '.join(map(templater.email, recipients)))
336 self.ui.note(_('sending mail: %s\n') % cmdline)
324 self.ui.note(_('sending mail: %s\n') % cmdline)
337 fp = os.popen(cmdline, 'w')
325 fp = os.popen(cmdline, 'w')
338 fp.write(msg)
326 fp.write(msg)
339 ret = fp.close()
327 ret = fp.close()
340 if ret:
328 if ret:
341 raise util.Abort('%s %s' % (
329 raise util.Abort('%s %s' % (
342 os.path.basename(self.program.split(None, 1)[0]),
330 os.path.basename(self.program.split(None, 1)[0]),
343 util.explain_exit(ret)[0]))
331 util.explain_exit(ret)[0]))
344
332
345 method = self.config('email', 'method', 'smtp')
333 method = self.config('email', 'method', 'smtp')
346 if method == 'smtp':
334 if method == 'smtp':
347 mail = smtp()
335 mail = smtp()
348 else:
336 else:
349 mail = sendmail(self, method)
337 mail = sendmail(self, method)
350 return mail
338 return mail
351
339
352 def print_exc(self):
340 def print_exc(self):
353 '''print exception traceback if traceback printing enabled.
341 '''print exception traceback if traceback printing enabled.
354 only to call in exception handler. returns true if traceback
342 only to call in exception handler. returns true if traceback
355 printed.'''
343 printed.'''
356 if self.traceback:
344 if self.traceback:
357 traceback.print_exc()
345 traceback.print_exc()
358 return self.traceback
346 return self.traceback
General Comments 0
You need to be logged in to leave comments. Login now