##// END OF EJS Templates
Make util.unique return a list
Matt Mackall -
r3535:4d97184a default
parent child Browse files
Show More
@@ -1,2157 +1,2157 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 from mercurial import commands
34 from mercurial import commands
35 demandload(globals(), "os sys re struct traceback errno bz2")
35 demandload(globals(), "os sys re struct traceback errno bz2")
36 demandload(globals(), "mercurial:cmdutil,hg,patch,revlog,ui,util")
36 demandload(globals(), "mercurial:cmdutil,hg,patch,revlog,ui,util")
37
37
38 commands.norepo += " qclone qversion"
38 commands.norepo += " qclone qversion"
39
39
40 class statusentry:
40 class statusentry:
41 def __init__(self, rev, name=None):
41 def __init__(self, rev, name=None):
42 if not name:
42 if not name:
43 fields = rev.split(':', 1)
43 fields = rev.split(':', 1)
44 if len(fields) == 2:
44 if len(fields) == 2:
45 self.rev, self.name = fields
45 self.rev, self.name = fields
46 else:
46 else:
47 self.rev, self.name = None, None
47 self.rev, self.name = None, None
48 else:
48 else:
49 self.rev, self.name = rev, name
49 self.rev, self.name = rev, name
50
50
51 def __str__(self):
51 def __str__(self):
52 return self.rev + ':' + self.name
52 return self.rev + ':' + self.name
53
53
54 class queue:
54 class queue:
55 def __init__(self, ui, path, patchdir=None):
55 def __init__(self, ui, path, patchdir=None):
56 self.basepath = path
56 self.basepath = path
57 self.path = patchdir or os.path.join(path, "patches")
57 self.path = patchdir or os.path.join(path, "patches")
58 self.opener = util.opener(self.path)
58 self.opener = util.opener(self.path)
59 self.ui = ui
59 self.ui = ui
60 self.applied = []
60 self.applied = []
61 self.full_series = []
61 self.full_series = []
62 self.applied_dirty = 0
62 self.applied_dirty = 0
63 self.series_dirty = 0
63 self.series_dirty = 0
64 self.series_path = "series"
64 self.series_path = "series"
65 self.status_path = "status"
65 self.status_path = "status"
66 self.guards_path = "guards"
66 self.guards_path = "guards"
67 self.active_guards = None
67 self.active_guards = None
68 self.guards_dirty = False
68 self.guards_dirty = False
69 self._diffopts = None
69 self._diffopts = None
70
70
71 if os.path.exists(self.join(self.series_path)):
71 if os.path.exists(self.join(self.series_path)):
72 self.full_series = self.opener(self.series_path).read().splitlines()
72 self.full_series = self.opener(self.series_path).read().splitlines()
73 self.parse_series()
73 self.parse_series()
74
74
75 if os.path.exists(self.join(self.status_path)):
75 if os.path.exists(self.join(self.status_path)):
76 lines = self.opener(self.status_path).read().splitlines()
76 lines = self.opener(self.status_path).read().splitlines()
77 self.applied = [statusentry(l) for l in lines]
77 self.applied = [statusentry(l) for l in lines]
78
78
79 def diffopts(self):
79 def diffopts(self):
80 if self._diffopts is None:
80 if self._diffopts is None:
81 self._diffopts = patch.diffopts(self.ui)
81 self._diffopts = patch.diffopts(self.ui)
82 return self._diffopts
82 return self._diffopts
83
83
84 def join(self, *p):
84 def join(self, *p):
85 return os.path.join(self.path, *p)
85 return os.path.join(self.path, *p)
86
86
87 def find_series(self, patch):
87 def find_series(self, patch):
88 pre = re.compile("(\s*)([^#]+)")
88 pre = re.compile("(\s*)([^#]+)")
89 index = 0
89 index = 0
90 for l in self.full_series:
90 for l in self.full_series:
91 m = pre.match(l)
91 m = pre.match(l)
92 if m:
92 if m:
93 s = m.group(2)
93 s = m.group(2)
94 s = s.rstrip()
94 s = s.rstrip()
95 if s == patch:
95 if s == patch:
96 return index
96 return index
97 index += 1
97 index += 1
98 return None
98 return None
99
99
100 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
100 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
101
101
102 def parse_series(self):
102 def parse_series(self):
103 self.series = []
103 self.series = []
104 self.series_guards = []
104 self.series_guards = []
105 for l in self.full_series:
105 for l in self.full_series:
106 h = l.find('#')
106 h = l.find('#')
107 if h == -1:
107 if h == -1:
108 patch = l
108 patch = l
109 comment = ''
109 comment = ''
110 elif h == 0:
110 elif h == 0:
111 continue
111 continue
112 else:
112 else:
113 patch = l[:h]
113 patch = l[:h]
114 comment = l[h:]
114 comment = l[h:]
115 patch = patch.strip()
115 patch = patch.strip()
116 if patch:
116 if patch:
117 if patch in self.series:
117 if patch in self.series:
118 raise util.Abort(_('%s appears more than once in %s') %
118 raise util.Abort(_('%s appears more than once in %s') %
119 (patch, self.join(self.series_path)))
119 (patch, self.join(self.series_path)))
120 self.series.append(patch)
120 self.series.append(patch)
121 self.series_guards.append(self.guard_re.findall(comment))
121 self.series_guards.append(self.guard_re.findall(comment))
122
122
123 def check_guard(self, guard):
123 def check_guard(self, guard):
124 bad_chars = '# \t\r\n\f'
124 bad_chars = '# \t\r\n\f'
125 first = guard[0]
125 first = guard[0]
126 for c in '-+':
126 for c in '-+':
127 if first == c:
127 if first == c:
128 return (_('guard %r starts with invalid character: %r') %
128 return (_('guard %r starts with invalid character: %r') %
129 (guard, c))
129 (guard, c))
130 for c in bad_chars:
130 for c in bad_chars:
131 if c in guard:
131 if c in guard:
132 return _('invalid character in guard %r: %r') % (guard, c)
132 return _('invalid character in guard %r: %r') % (guard, c)
133
133
134 def set_active(self, guards):
134 def set_active(self, guards):
135 for guard in guards:
135 for guard in guards:
136 bad = self.check_guard(guard)
136 bad = self.check_guard(guard)
137 if bad:
137 if bad:
138 raise util.Abort(bad)
138 raise util.Abort(bad)
139 guards = dict.fromkeys(guards).keys()
139 guards = dict.fromkeys(guards).keys()
140 guards.sort()
140 guards.sort()
141 self.ui.debug('active guards: %s\n' % ' '.join(guards))
141 self.ui.debug('active guards: %s\n' % ' '.join(guards))
142 self.active_guards = guards
142 self.active_guards = guards
143 self.guards_dirty = True
143 self.guards_dirty = True
144
144
145 def active(self):
145 def active(self):
146 if self.active_guards is None:
146 if self.active_guards is None:
147 self.active_guards = []
147 self.active_guards = []
148 try:
148 try:
149 guards = self.opener(self.guards_path).read().split()
149 guards = self.opener(self.guards_path).read().split()
150 except IOError, err:
150 except IOError, err:
151 if err.errno != errno.ENOENT: raise
151 if err.errno != errno.ENOENT: raise
152 guards = []
152 guards = []
153 for i, guard in enumerate(guards):
153 for i, guard in enumerate(guards):
154 bad = self.check_guard(guard)
154 bad = self.check_guard(guard)
155 if bad:
155 if bad:
156 self.ui.warn('%s:%d: %s\n' %
156 self.ui.warn('%s:%d: %s\n' %
157 (self.join(self.guards_path), i + 1, bad))
157 (self.join(self.guards_path), i + 1, bad))
158 else:
158 else:
159 self.active_guards.append(guard)
159 self.active_guards.append(guard)
160 return self.active_guards
160 return self.active_guards
161
161
162 def set_guards(self, idx, guards):
162 def set_guards(self, idx, guards):
163 for g in guards:
163 for g in guards:
164 if len(g) < 2:
164 if len(g) < 2:
165 raise util.Abort(_('guard %r too short') % g)
165 raise util.Abort(_('guard %r too short') % g)
166 if g[0] not in '-+':
166 if g[0] not in '-+':
167 raise util.Abort(_('guard %r starts with invalid char') % g)
167 raise util.Abort(_('guard %r starts with invalid char') % g)
168 bad = self.check_guard(g[1:])
168 bad = self.check_guard(g[1:])
169 if bad:
169 if bad:
170 raise util.Abort(bad)
170 raise util.Abort(bad)
171 drop = self.guard_re.sub('', self.full_series[idx])
171 drop = self.guard_re.sub('', self.full_series[idx])
172 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
172 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
173 self.parse_series()
173 self.parse_series()
174 self.series_dirty = True
174 self.series_dirty = True
175
175
176 def pushable(self, idx):
176 def pushable(self, idx):
177 if isinstance(idx, str):
177 if isinstance(idx, str):
178 idx = self.series.index(idx)
178 idx = self.series.index(idx)
179 patchguards = self.series_guards[idx]
179 patchguards = self.series_guards[idx]
180 if not patchguards:
180 if not patchguards:
181 return True, None
181 return True, None
182 default = False
182 default = False
183 guards = self.active()
183 guards = self.active()
184 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
184 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
185 if exactneg:
185 if exactneg:
186 return False, exactneg[0]
186 return False, exactneg[0]
187 pos = [g for g in patchguards if g[0] == '+']
187 pos = [g for g in patchguards if g[0] == '+']
188 exactpos = [g for g in pos if g[1:] in guards]
188 exactpos = [g for g in pos if g[1:] in guards]
189 if pos:
189 if pos:
190 if exactpos:
190 if exactpos:
191 return True, exactpos[0]
191 return True, exactpos[0]
192 return False, pos
192 return False, pos
193 return True, ''
193 return True, ''
194
194
195 def explain_pushable(self, idx, all_patches=False):
195 def explain_pushable(self, idx, all_patches=False):
196 write = all_patches and self.ui.write or self.ui.warn
196 write = all_patches and self.ui.write or self.ui.warn
197 if all_patches or self.ui.verbose:
197 if all_patches or self.ui.verbose:
198 if isinstance(idx, str):
198 if isinstance(idx, str):
199 idx = self.series.index(idx)
199 idx = self.series.index(idx)
200 pushable, why = self.pushable(idx)
200 pushable, why = self.pushable(idx)
201 if all_patches and pushable:
201 if all_patches and pushable:
202 if why is None:
202 if why is None:
203 write(_('allowing %s - no guards in effect\n') %
203 write(_('allowing %s - no guards in effect\n') %
204 self.series[idx])
204 self.series[idx])
205 else:
205 else:
206 if not why:
206 if not why:
207 write(_('allowing %s - no matching negative guards\n') %
207 write(_('allowing %s - no matching negative guards\n') %
208 self.series[idx])
208 self.series[idx])
209 else:
209 else:
210 write(_('allowing %s - guarded by %r\n') %
210 write(_('allowing %s - guarded by %r\n') %
211 (self.series[idx], why))
211 (self.series[idx], why))
212 if not pushable:
212 if not pushable:
213 if why:
213 if why:
214 write(_('skipping %s - guarded by %r\n') %
214 write(_('skipping %s - guarded by %r\n') %
215 (self.series[idx], ' '.join(why)))
215 (self.series[idx], ' '.join(why)))
216 else:
216 else:
217 write(_('skipping %s - no matching guards\n') %
217 write(_('skipping %s - no matching guards\n') %
218 self.series[idx])
218 self.series[idx])
219
219
220 def save_dirty(self):
220 def save_dirty(self):
221 def write_list(items, path):
221 def write_list(items, path):
222 fp = self.opener(path, 'w')
222 fp = self.opener(path, 'w')
223 for i in items:
223 for i in items:
224 print >> fp, i
224 print >> fp, i
225 fp.close()
225 fp.close()
226 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
226 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
227 if self.series_dirty: write_list(self.full_series, self.series_path)
227 if self.series_dirty: write_list(self.full_series, self.series_path)
228 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
228 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
229
229
230 def readheaders(self, patch):
230 def readheaders(self, patch):
231 def eatdiff(lines):
231 def eatdiff(lines):
232 while lines:
232 while lines:
233 l = lines[-1]
233 l = lines[-1]
234 if (l.startswith("diff -") or
234 if (l.startswith("diff -") or
235 l.startswith("Index:") or
235 l.startswith("Index:") or
236 l.startswith("===========")):
236 l.startswith("===========")):
237 del lines[-1]
237 del lines[-1]
238 else:
238 else:
239 break
239 break
240 def eatempty(lines):
240 def eatempty(lines):
241 while lines:
241 while lines:
242 l = lines[-1]
242 l = lines[-1]
243 if re.match('\s*$', l):
243 if re.match('\s*$', l):
244 del lines[-1]
244 del lines[-1]
245 else:
245 else:
246 break
246 break
247
247
248 pf = self.join(patch)
248 pf = self.join(patch)
249 message = []
249 message = []
250 comments = []
250 comments = []
251 user = None
251 user = None
252 date = None
252 date = None
253 format = None
253 format = None
254 subject = None
254 subject = None
255 diffstart = 0
255 diffstart = 0
256
256
257 for line in file(pf):
257 for line in file(pf):
258 line = line.rstrip()
258 line = line.rstrip()
259 if line.startswith('diff --git'):
259 if line.startswith('diff --git'):
260 diffstart = 2
260 diffstart = 2
261 break
261 break
262 if diffstart:
262 if diffstart:
263 if line.startswith('+++ '):
263 if line.startswith('+++ '):
264 diffstart = 2
264 diffstart = 2
265 break
265 break
266 if line.startswith("--- "):
266 if line.startswith("--- "):
267 diffstart = 1
267 diffstart = 1
268 continue
268 continue
269 elif format == "hgpatch":
269 elif format == "hgpatch":
270 # parse values when importing the result of an hg export
270 # parse values when importing the result of an hg export
271 if line.startswith("# User "):
271 if line.startswith("# User "):
272 user = line[7:]
272 user = line[7:]
273 elif line.startswith("# Date "):
273 elif line.startswith("# Date "):
274 date = line[7:]
274 date = line[7:]
275 elif not line.startswith("# ") and line:
275 elif not line.startswith("# ") and line:
276 message.append(line)
276 message.append(line)
277 format = None
277 format = None
278 elif line == '# HG changeset patch':
278 elif line == '# HG changeset patch':
279 format = "hgpatch"
279 format = "hgpatch"
280 elif (format != "tagdone" and (line.startswith("Subject: ") or
280 elif (format != "tagdone" and (line.startswith("Subject: ") or
281 line.startswith("subject: "))):
281 line.startswith("subject: "))):
282 subject = line[9:]
282 subject = line[9:]
283 format = "tag"
283 format = "tag"
284 elif (format != "tagdone" and (line.startswith("From: ") or
284 elif (format != "tagdone" and (line.startswith("From: ") or
285 line.startswith("from: "))):
285 line.startswith("from: "))):
286 user = line[6:]
286 user = line[6:]
287 format = "tag"
287 format = "tag"
288 elif format == "tag" and line == "":
288 elif format == "tag" and line == "":
289 # when looking for tags (subject: from: etc) they
289 # when looking for tags (subject: from: etc) they
290 # end once you find a blank line in the source
290 # end once you find a blank line in the source
291 format = "tagdone"
291 format = "tagdone"
292 elif message or line:
292 elif message or line:
293 message.append(line)
293 message.append(line)
294 comments.append(line)
294 comments.append(line)
295
295
296 eatdiff(message)
296 eatdiff(message)
297 eatdiff(comments)
297 eatdiff(comments)
298 eatempty(message)
298 eatempty(message)
299 eatempty(comments)
299 eatempty(comments)
300
300
301 # make sure message isn't empty
301 # make sure message isn't empty
302 if format and format.startswith("tag") and subject:
302 if format and format.startswith("tag") and subject:
303 message.insert(0, "")
303 message.insert(0, "")
304 message.insert(0, subject)
304 message.insert(0, subject)
305 return (message, comments, user, date, diffstart > 1)
305 return (message, comments, user, date, diffstart > 1)
306
306
307 def printdiff(self, repo, node1, node2=None, files=None,
307 def printdiff(self, repo, node1, node2=None, files=None,
308 fp=None, changes=None, opts={}):
308 fp=None, changes=None, opts={}):
309 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
309 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
310
310
311 patch.diff(repo, node1, node2, fns, match=matchfn,
311 patch.diff(repo, node1, node2, fns, match=matchfn,
312 fp=fp, changes=changes, opts=self.diffopts())
312 fp=fp, changes=changes, opts=self.diffopts())
313
313
314 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
314 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
315 # first try just applying the patch
315 # first try just applying the patch
316 (err, n) = self.apply(repo, [ patch ], update_status=False,
316 (err, n) = self.apply(repo, [ patch ], update_status=False,
317 strict=True, merge=rev, wlock=wlock)
317 strict=True, merge=rev, wlock=wlock)
318
318
319 if err == 0:
319 if err == 0:
320 return (err, n)
320 return (err, n)
321
321
322 if n is None:
322 if n is None:
323 raise util.Abort(_("apply failed for patch %s") % patch)
323 raise util.Abort(_("apply failed for patch %s") % patch)
324
324
325 self.ui.warn("patch didn't work out, merging %s\n" % patch)
325 self.ui.warn("patch didn't work out, merging %s\n" % patch)
326
326
327 # apply failed, strip away that rev and merge.
327 # apply failed, strip away that rev and merge.
328 hg.clean(repo, head, wlock=wlock)
328 hg.clean(repo, head, wlock=wlock)
329 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
329 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
330
330
331 c = repo.changelog.read(rev)
331 c = repo.changelog.read(rev)
332 ret = hg.merge(repo, rev, wlock=wlock)
332 ret = hg.merge(repo, rev, wlock=wlock)
333 if ret:
333 if ret:
334 raise util.Abort(_("update returned %d") % ret)
334 raise util.Abort(_("update returned %d") % ret)
335 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
335 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
336 if n == None:
336 if n == None:
337 raise util.Abort(_("repo commit failed"))
337 raise util.Abort(_("repo commit failed"))
338 try:
338 try:
339 message, comments, user, date, patchfound = mergeq.readheaders(patch)
339 message, comments, user, date, patchfound = mergeq.readheaders(patch)
340 except:
340 except:
341 raise util.Abort(_("unable to read %s") % patch)
341 raise util.Abort(_("unable to read %s") % patch)
342
342
343 patchf = self.opener(patch, "w")
343 patchf = self.opener(patch, "w")
344 if comments:
344 if comments:
345 comments = "\n".join(comments) + '\n\n'
345 comments = "\n".join(comments) + '\n\n'
346 patchf.write(comments)
346 patchf.write(comments)
347 self.printdiff(repo, head, n, fp=patchf)
347 self.printdiff(repo, head, n, fp=patchf)
348 patchf.close()
348 patchf.close()
349 return (0, n)
349 return (0, n)
350
350
351 def qparents(self, repo, rev=None):
351 def qparents(self, repo, rev=None):
352 if rev is None:
352 if rev is None:
353 (p1, p2) = repo.dirstate.parents()
353 (p1, p2) = repo.dirstate.parents()
354 if p2 == revlog.nullid:
354 if p2 == revlog.nullid:
355 return p1
355 return p1
356 if len(self.applied) == 0:
356 if len(self.applied) == 0:
357 return None
357 return None
358 return revlog.bin(self.applied[-1].rev)
358 return revlog.bin(self.applied[-1].rev)
359 pp = repo.changelog.parents(rev)
359 pp = repo.changelog.parents(rev)
360 if pp[1] != revlog.nullid:
360 if pp[1] != revlog.nullid:
361 arevs = [ x.rev for x in self.applied ]
361 arevs = [ x.rev for x in self.applied ]
362 p0 = revlog.hex(pp[0])
362 p0 = revlog.hex(pp[0])
363 p1 = revlog.hex(pp[1])
363 p1 = revlog.hex(pp[1])
364 if p0 in arevs:
364 if p0 in arevs:
365 return pp[0]
365 return pp[0]
366 if p1 in arevs:
366 if p1 in arevs:
367 return pp[1]
367 return pp[1]
368 return pp[0]
368 return pp[0]
369
369
370 def mergepatch(self, repo, mergeq, series, wlock):
370 def mergepatch(self, repo, mergeq, series, wlock):
371 if len(self.applied) == 0:
371 if len(self.applied) == 0:
372 # each of the patches merged in will have two parents. This
372 # each of the patches merged in will have two parents. This
373 # can confuse the qrefresh, qdiff, and strip code because it
373 # can confuse the qrefresh, qdiff, and strip code because it
374 # needs to know which parent is actually in the patch queue.
374 # needs to know which parent is actually in the patch queue.
375 # so, we insert a merge marker with only one parent. This way
375 # so, we insert a merge marker with only one parent. This way
376 # the first patch in the queue is never a merge patch
376 # the first patch in the queue is never a merge patch
377 #
377 #
378 pname = ".hg.patches.merge.marker"
378 pname = ".hg.patches.merge.marker"
379 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
379 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
380 wlock=wlock)
380 wlock=wlock)
381 self.applied.append(statusentry(revlog.hex(n), pname))
381 self.applied.append(statusentry(revlog.hex(n), pname))
382 self.applied_dirty = 1
382 self.applied_dirty = 1
383
383
384 head = self.qparents(repo)
384 head = self.qparents(repo)
385
385
386 for patch in series:
386 for patch in series:
387 patch = mergeq.lookup(patch, strict=True)
387 patch = mergeq.lookup(patch, strict=True)
388 if not patch:
388 if not patch:
389 self.ui.warn("patch %s does not exist\n" % patch)
389 self.ui.warn("patch %s does not exist\n" % patch)
390 return (1, None)
390 return (1, None)
391 pushable, reason = self.pushable(patch)
391 pushable, reason = self.pushable(patch)
392 if not pushable:
392 if not pushable:
393 self.explain_pushable(patch, all_patches=True)
393 self.explain_pushable(patch, all_patches=True)
394 continue
394 continue
395 info = mergeq.isapplied(patch)
395 info = mergeq.isapplied(patch)
396 if not info:
396 if not info:
397 self.ui.warn("patch %s is not applied\n" % patch)
397 self.ui.warn("patch %s is not applied\n" % patch)
398 return (1, None)
398 return (1, None)
399 rev = revlog.bin(info[1])
399 rev = revlog.bin(info[1])
400 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
400 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
401 if head:
401 if head:
402 self.applied.append(statusentry(revlog.hex(head), patch))
402 self.applied.append(statusentry(revlog.hex(head), patch))
403 self.applied_dirty = 1
403 self.applied_dirty = 1
404 if err:
404 if err:
405 return (err, head)
405 return (err, head)
406 return (0, head)
406 return (0, head)
407
407
408 def patch(self, repo, patchfile):
408 def patch(self, repo, patchfile):
409 '''Apply patchfile to the working directory.
409 '''Apply patchfile to the working directory.
410 patchfile: file name of patch'''
410 patchfile: file name of patch'''
411 files = {}
411 files = {}
412 try:
412 try:
413 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
413 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
414 files=files)
414 files=files)
415 except Exception, inst:
415 except Exception, inst:
416 self.ui.note(str(inst) + '\n')
416 self.ui.note(str(inst) + '\n')
417 if not self.ui.verbose:
417 if not self.ui.verbose:
418 self.ui.warn("patch failed, unable to continue (try -v)\n")
418 self.ui.warn("patch failed, unable to continue (try -v)\n")
419 return (False, files, False)
419 return (False, files, False)
420
420
421 return (True, files, fuzz)
421 return (True, files, fuzz)
422
422
423 def apply(self, repo, series, list=False, update_status=True,
423 def apply(self, repo, series, list=False, update_status=True,
424 strict=False, patchdir=None, merge=None, wlock=None):
424 strict=False, patchdir=None, merge=None, wlock=None):
425 # TODO unify with commands.py
425 # TODO unify with commands.py
426 if not patchdir:
426 if not patchdir:
427 patchdir = self.path
427 patchdir = self.path
428 err = 0
428 err = 0
429 if not wlock:
429 if not wlock:
430 wlock = repo.wlock()
430 wlock = repo.wlock()
431 lock = repo.lock()
431 lock = repo.lock()
432 tr = repo.transaction()
432 tr = repo.transaction()
433 n = None
433 n = None
434 for patchname in series:
434 for patchname in series:
435 pushable, reason = self.pushable(patchname)
435 pushable, reason = self.pushable(patchname)
436 if not pushable:
436 if not pushable:
437 self.explain_pushable(patchname, all_patches=True)
437 self.explain_pushable(patchname, all_patches=True)
438 continue
438 continue
439 self.ui.warn("applying %s\n" % patchname)
439 self.ui.warn("applying %s\n" % patchname)
440 pf = os.path.join(patchdir, patchname)
440 pf = os.path.join(patchdir, patchname)
441
441
442 try:
442 try:
443 message, comments, user, date, patchfound = self.readheaders(patchname)
443 message, comments, user, date, patchfound = self.readheaders(patchname)
444 except:
444 except:
445 self.ui.warn("Unable to read %s\n" % patchname)
445 self.ui.warn("Unable to read %s\n" % patchname)
446 err = 1
446 err = 1
447 break
447 break
448
448
449 if not message:
449 if not message:
450 message = "imported patch %s\n" % patchname
450 message = "imported patch %s\n" % patchname
451 else:
451 else:
452 if list:
452 if list:
453 message.append("\nimported patch %s" % patchname)
453 message.append("\nimported patch %s" % patchname)
454 message = '\n'.join(message)
454 message = '\n'.join(message)
455
455
456 (patcherr, files, fuzz) = self.patch(repo, pf)
456 (patcherr, files, fuzz) = self.patch(repo, pf)
457 patcherr = not patcherr
457 patcherr = not patcherr
458
458
459 if merge and files:
459 if merge and files:
460 # Mark as merged and update dirstate parent info
460 # Mark as merged and update dirstate parent info
461 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
461 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
462 p1, p2 = repo.dirstate.parents()
462 p1, p2 = repo.dirstate.parents()
463 repo.dirstate.setparents(p1, merge)
463 repo.dirstate.setparents(p1, merge)
464 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
464 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
465 n = repo.commit(files, message, user, date, force=1, lock=lock,
465 n = repo.commit(files, message, user, date, force=1, lock=lock,
466 wlock=wlock)
466 wlock=wlock)
467
467
468 if n == None:
468 if n == None:
469 raise util.Abort(_("repo commit failed"))
469 raise util.Abort(_("repo commit failed"))
470
470
471 if update_status:
471 if update_status:
472 self.applied.append(statusentry(revlog.hex(n), patchname))
472 self.applied.append(statusentry(revlog.hex(n), patchname))
473
473
474 if patcherr:
474 if patcherr:
475 if not patchfound:
475 if not patchfound:
476 self.ui.warn("patch %s is empty\n" % patchname)
476 self.ui.warn("patch %s is empty\n" % patchname)
477 err = 0
477 err = 0
478 else:
478 else:
479 self.ui.warn("patch failed, rejects left in working dir\n")
479 self.ui.warn("patch failed, rejects left in working dir\n")
480 err = 1
480 err = 1
481 break
481 break
482
482
483 if fuzz and strict:
483 if fuzz and strict:
484 self.ui.warn("fuzz found when applying patch, stopping\n")
484 self.ui.warn("fuzz found when applying patch, stopping\n")
485 err = 1
485 err = 1
486 break
486 break
487 tr.close()
487 tr.close()
488 return (err, n)
488 return (err, n)
489
489
490 def delete(self, repo, patches, opts):
490 def delete(self, repo, patches, opts):
491 realpatches = []
491 realpatches = []
492 for patch in patches:
492 for patch in patches:
493 patch = self.lookup(patch, strict=True)
493 patch = self.lookup(patch, strict=True)
494 info = self.isapplied(patch)
494 info = self.isapplied(patch)
495 if info:
495 if info:
496 raise util.Abort(_("cannot delete applied patch %s") % patch)
496 raise util.Abort(_("cannot delete applied patch %s") % patch)
497 if patch not in self.series:
497 if patch not in self.series:
498 raise util.Abort(_("patch %s not in series file") % patch)
498 raise util.Abort(_("patch %s not in series file") % patch)
499 realpatches.append(patch)
499 realpatches.append(patch)
500
500
501 appliedbase = 0
501 appliedbase = 0
502 if opts.get('rev'):
502 if opts.get('rev'):
503 if not self.applied:
503 if not self.applied:
504 raise util.Abort(_('no patches applied'))
504 raise util.Abort(_('no patches applied'))
505 revs = cmdutil.revrange(ui, repo, opts['rev'])
505 revs = cmdutil.revrange(ui, repo, opts['rev'])
506 if len(revs) > 1 and revs[0] > revs[1]:
506 if len(revs) > 1 and revs[0] > revs[1]:
507 revs.reverse()
507 revs.reverse()
508 for rev in revs:
508 for rev in revs:
509 if appliedbase >= len(self.applied):
509 if appliedbase >= len(self.applied):
510 raise util.Abort(_("revision %d is not managed") % rev)
510 raise util.Abort(_("revision %d is not managed") % rev)
511
511
512 base = revlog.bin(self.applied[appliedbase].rev)
512 base = revlog.bin(self.applied[appliedbase].rev)
513 node = repo.changelog.node(rev)
513 node = repo.changelog.node(rev)
514 if node != base:
514 if node != base:
515 raise util.Abort(_("cannot delete revision %d above "
515 raise util.Abort(_("cannot delete revision %d above "
516 "applied patches") % rev)
516 "applied patches") % rev)
517 realpatches.append(self.applied[appliedbase].name)
517 realpatches.append(self.applied[appliedbase].name)
518 appliedbase += 1
518 appliedbase += 1
519
519
520 if not opts.get('keep'):
520 if not opts.get('keep'):
521 r = self.qrepo()
521 r = self.qrepo()
522 if r:
522 if r:
523 r.remove(realpatches, True)
523 r.remove(realpatches, True)
524 else:
524 else:
525 for p in realpatches:
525 for p in realpatches:
526 os.unlink(self.join(p))
526 os.unlink(self.join(p))
527
527
528 if appliedbase:
528 if appliedbase:
529 del self.applied[:appliedbase]
529 del self.applied[:appliedbase]
530 self.applied_dirty = 1
530 self.applied_dirty = 1
531 indices = [self.find_series(p) for p in realpatches]
531 indices = [self.find_series(p) for p in realpatches]
532 indices.sort()
532 indices.sort()
533 for i in indices[-1::-1]:
533 for i in indices[-1::-1]:
534 del self.full_series[i]
534 del self.full_series[i]
535 self.parse_series()
535 self.parse_series()
536 self.series_dirty = 1
536 self.series_dirty = 1
537
537
538 def check_toppatch(self, repo):
538 def check_toppatch(self, repo):
539 if len(self.applied) > 0:
539 if len(self.applied) > 0:
540 top = revlog.bin(self.applied[-1].rev)
540 top = revlog.bin(self.applied[-1].rev)
541 pp = repo.dirstate.parents()
541 pp = repo.dirstate.parents()
542 if top not in pp:
542 if top not in pp:
543 raise util.Abort(_("queue top not at same revision as working directory"))
543 raise util.Abort(_("queue top not at same revision as working directory"))
544 return top
544 return top
545 return None
545 return None
546 def check_localchanges(self, repo, force=False, refresh=True):
546 def check_localchanges(self, repo, force=False, refresh=True):
547 m, a, r, d = repo.status()[:4]
547 m, a, r, d = repo.status()[:4]
548 if m or a or r or d:
548 if m or a or r or d:
549 if not force:
549 if not force:
550 if refresh:
550 if refresh:
551 raise util.Abort(_("local changes found, refresh first"))
551 raise util.Abort(_("local changes found, refresh first"))
552 else:
552 else:
553 raise util.Abort(_("local changes found"))
553 raise util.Abort(_("local changes found"))
554 return m, a, r, d
554 return m, a, r, d
555 def new(self, repo, patch, msg=None, force=None):
555 def new(self, repo, patch, msg=None, force=None):
556 if os.path.exists(self.join(patch)):
556 if os.path.exists(self.join(patch)):
557 raise util.Abort(_('patch "%s" already exists') % patch)
557 raise util.Abort(_('patch "%s" already exists') % patch)
558 m, a, r, d = self.check_localchanges(repo, force)
558 m, a, r, d = self.check_localchanges(repo, force)
559 commitfiles = m + a + r
559 commitfiles = m + a + r
560 self.check_toppatch(repo)
560 self.check_toppatch(repo)
561 wlock = repo.wlock()
561 wlock = repo.wlock()
562 insert = self.full_series_end()
562 insert = self.full_series_end()
563 if msg:
563 if msg:
564 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
564 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
565 wlock=wlock)
565 wlock=wlock)
566 else:
566 else:
567 n = repo.commit(commitfiles,
567 n = repo.commit(commitfiles,
568 "New patch: %s" % patch, force=True, wlock=wlock)
568 "New patch: %s" % patch, force=True, wlock=wlock)
569 if n == None:
569 if n == None:
570 raise util.Abort(_("repo commit failed"))
570 raise util.Abort(_("repo commit failed"))
571 self.full_series[insert:insert] = [patch]
571 self.full_series[insert:insert] = [patch]
572 self.applied.append(statusentry(revlog.hex(n), patch))
572 self.applied.append(statusentry(revlog.hex(n), patch))
573 self.parse_series()
573 self.parse_series()
574 self.series_dirty = 1
574 self.series_dirty = 1
575 self.applied_dirty = 1
575 self.applied_dirty = 1
576 p = self.opener(patch, "w")
576 p = self.opener(patch, "w")
577 if msg:
577 if msg:
578 msg = msg + "\n"
578 msg = msg + "\n"
579 p.write(msg)
579 p.write(msg)
580 p.close()
580 p.close()
581 wlock = None
581 wlock = None
582 r = self.qrepo()
582 r = self.qrepo()
583 if r: r.add([patch])
583 if r: r.add([patch])
584 if commitfiles:
584 if commitfiles:
585 self.refresh(repo, short=True)
585 self.refresh(repo, short=True)
586
586
587 def strip(self, repo, rev, update=True, backup="all", wlock=None):
587 def strip(self, repo, rev, update=True, backup="all", wlock=None):
588 def limitheads(chlog, stop):
588 def limitheads(chlog, stop):
589 """return the list of all nodes that have no children"""
589 """return the list of all nodes that have no children"""
590 p = {}
590 p = {}
591 h = []
591 h = []
592 stoprev = 0
592 stoprev = 0
593 if stop in chlog.nodemap:
593 if stop in chlog.nodemap:
594 stoprev = chlog.rev(stop)
594 stoprev = chlog.rev(stop)
595
595
596 for r in xrange(chlog.count() - 1, -1, -1):
596 for r in xrange(chlog.count() - 1, -1, -1):
597 n = chlog.node(r)
597 n = chlog.node(r)
598 if n not in p:
598 if n not in p:
599 h.append(n)
599 h.append(n)
600 if n == stop:
600 if n == stop:
601 break
601 break
602 if r < stoprev:
602 if r < stoprev:
603 break
603 break
604 for pn in chlog.parents(n):
604 for pn in chlog.parents(n):
605 p[pn] = 1
605 p[pn] = 1
606 return h
606 return h
607
607
608 def bundle(cg):
608 def bundle(cg):
609 backupdir = repo.join("strip-backup")
609 backupdir = repo.join("strip-backup")
610 if not os.path.isdir(backupdir):
610 if not os.path.isdir(backupdir):
611 os.mkdir(backupdir)
611 os.mkdir(backupdir)
612 name = os.path.join(backupdir, "%s" % revlog.short(rev))
612 name = os.path.join(backupdir, "%s" % revlog.short(rev))
613 name = savename(name)
613 name = savename(name)
614 self.ui.warn("saving bundle to %s\n" % name)
614 self.ui.warn("saving bundle to %s\n" % name)
615 # TODO, exclusive open
615 # TODO, exclusive open
616 f = open(name, "wb")
616 f = open(name, "wb")
617 try:
617 try:
618 f.write("HG10")
618 f.write("HG10")
619 z = bz2.BZ2Compressor(9)
619 z = bz2.BZ2Compressor(9)
620 while 1:
620 while 1:
621 chunk = cg.read(4096)
621 chunk = cg.read(4096)
622 if not chunk:
622 if not chunk:
623 break
623 break
624 f.write(z.compress(chunk))
624 f.write(z.compress(chunk))
625 f.write(z.flush())
625 f.write(z.flush())
626 except:
626 except:
627 os.unlink(name)
627 os.unlink(name)
628 raise
628 raise
629 f.close()
629 f.close()
630 return name
630 return name
631
631
632 def stripall(rev, revnum):
632 def stripall(rev, revnum):
633 cl = repo.changelog
633 cl = repo.changelog
634 c = cl.read(rev)
634 c = cl.read(rev)
635 mm = repo.manifest.read(c[0])
635 mm = repo.manifest.read(c[0])
636 seen = {}
636 seen = {}
637
637
638 for x in xrange(revnum, cl.count()):
638 for x in xrange(revnum, cl.count()):
639 c = cl.read(cl.node(x))
639 c = cl.read(cl.node(x))
640 for f in c[3]:
640 for f in c[3]:
641 if f in seen:
641 if f in seen:
642 continue
642 continue
643 seen[f] = 1
643 seen[f] = 1
644 if f in mm:
644 if f in mm:
645 filerev = mm[f]
645 filerev = mm[f]
646 else:
646 else:
647 filerev = 0
647 filerev = 0
648 seen[f] = filerev
648 seen[f] = filerev
649 # we go in two steps here so the strip loop happens in a
649 # we go in two steps here so the strip loop happens in a
650 # sensible order. When stripping many files, this helps keep
650 # sensible order. When stripping many files, this helps keep
651 # our disk access patterns under control.
651 # our disk access patterns under control.
652 seen_list = seen.keys()
652 seen_list = seen.keys()
653 seen_list.sort()
653 seen_list.sort()
654 for f in seen_list:
654 for f in seen_list:
655 ff = repo.file(f)
655 ff = repo.file(f)
656 filerev = seen[f]
656 filerev = seen[f]
657 if filerev != 0:
657 if filerev != 0:
658 if filerev in ff.nodemap:
658 if filerev in ff.nodemap:
659 filerev = ff.rev(filerev)
659 filerev = ff.rev(filerev)
660 else:
660 else:
661 filerev = 0
661 filerev = 0
662 ff.strip(filerev, revnum)
662 ff.strip(filerev, revnum)
663
663
664 if not wlock:
664 if not wlock:
665 wlock = repo.wlock()
665 wlock = repo.wlock()
666 lock = repo.lock()
666 lock = repo.lock()
667 chlog = repo.changelog
667 chlog = repo.changelog
668 # TODO delete the undo files, and handle undo of merge sets
668 # TODO delete the undo files, and handle undo of merge sets
669 pp = chlog.parents(rev)
669 pp = chlog.parents(rev)
670 revnum = chlog.rev(rev)
670 revnum = chlog.rev(rev)
671
671
672 if update:
672 if update:
673 self.check_localchanges(repo, refresh=False)
673 self.check_localchanges(repo, refresh=False)
674 urev = self.qparents(repo, rev)
674 urev = self.qparents(repo, rev)
675 hg.clean(repo, urev, wlock=wlock)
675 hg.clean(repo, urev, wlock=wlock)
676 repo.dirstate.write()
676 repo.dirstate.write()
677
677
678 # save is a list of all the branches we are truncating away
678 # save is a list of all the branches we are truncating away
679 # that we actually want to keep. changegroup will be used
679 # that we actually want to keep. changegroup will be used
680 # to preserve them and add them back after the truncate
680 # to preserve them and add them back after the truncate
681 saveheads = []
681 saveheads = []
682 savebases = {}
682 savebases = {}
683
683
684 heads = limitheads(chlog, rev)
684 heads = limitheads(chlog, rev)
685 seen = {}
685 seen = {}
686
686
687 # search through all the heads, finding those where the revision
687 # search through all the heads, finding those where the revision
688 # we want to strip away is an ancestor. Also look for merges
688 # we want to strip away is an ancestor. Also look for merges
689 # that might be turned into new heads by the strip.
689 # that might be turned into new heads by the strip.
690 while heads:
690 while heads:
691 h = heads.pop()
691 h = heads.pop()
692 n = h
692 n = h
693 while True:
693 while True:
694 seen[n] = 1
694 seen[n] = 1
695 pp = chlog.parents(n)
695 pp = chlog.parents(n)
696 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
696 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
697 if pp[1] not in seen:
697 if pp[1] not in seen:
698 heads.append(pp[1])
698 heads.append(pp[1])
699 if pp[0] == revlog.nullid:
699 if pp[0] == revlog.nullid:
700 break
700 break
701 if chlog.rev(pp[0]) < revnum:
701 if chlog.rev(pp[0]) < revnum:
702 break
702 break
703 n = pp[0]
703 n = pp[0]
704 if n == rev:
704 if n == rev:
705 break
705 break
706 r = chlog.reachable(h, rev)
706 r = chlog.reachable(h, rev)
707 if rev not in r:
707 if rev not in r:
708 saveheads.append(h)
708 saveheads.append(h)
709 for x in r:
709 for x in r:
710 if chlog.rev(x) > revnum:
710 if chlog.rev(x) > revnum:
711 savebases[x] = 1
711 savebases[x] = 1
712
712
713 # create a changegroup for all the branches we need to keep
713 # create a changegroup for all the branches we need to keep
714 if backup == "all":
714 if backup == "all":
715 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
715 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
716 bundle(backupch)
716 bundle(backupch)
717 if saveheads:
717 if saveheads:
718 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
718 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
719 chgrpfile = bundle(backupch)
719 chgrpfile = bundle(backupch)
720
720
721 stripall(rev, revnum)
721 stripall(rev, revnum)
722
722
723 change = chlog.read(rev)
723 change = chlog.read(rev)
724 chlog.strip(revnum, revnum)
724 chlog.strip(revnum, revnum)
725 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
725 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
726 if saveheads:
726 if saveheads:
727 self.ui.status("adding branch\n")
727 self.ui.status("adding branch\n")
728 commands.unbundle(self.ui, repo, chgrpfile, update=False)
728 commands.unbundle(self.ui, repo, chgrpfile, update=False)
729 if backup != "strip":
729 if backup != "strip":
730 os.unlink(chgrpfile)
730 os.unlink(chgrpfile)
731
731
732 def isapplied(self, patch):
732 def isapplied(self, patch):
733 """returns (index, rev, patch)"""
733 """returns (index, rev, patch)"""
734 for i in xrange(len(self.applied)):
734 for i in xrange(len(self.applied)):
735 a = self.applied[i]
735 a = self.applied[i]
736 if a.name == patch:
736 if a.name == patch:
737 return (i, a.rev, a.name)
737 return (i, a.rev, a.name)
738 return None
738 return None
739
739
740 # if the exact patch name does not exist, we try a few
740 # if the exact patch name does not exist, we try a few
741 # variations. If strict is passed, we try only #1
741 # variations. If strict is passed, we try only #1
742 #
742 #
743 # 1) a number to indicate an offset in the series file
743 # 1) a number to indicate an offset in the series file
744 # 2) a unique substring of the patch name was given
744 # 2) a unique substring of the patch name was given
745 # 3) patchname[-+]num to indicate an offset in the series file
745 # 3) patchname[-+]num to indicate an offset in the series file
746 def lookup(self, patch, strict=False):
746 def lookup(self, patch, strict=False):
747 patch = patch and str(patch)
747 patch = patch and str(patch)
748
748
749 def partial_name(s):
749 def partial_name(s):
750 if s in self.series:
750 if s in self.series:
751 return s
751 return s
752 matches = [x for x in self.series if s in x]
752 matches = [x for x in self.series if s in x]
753 if len(matches) > 1:
753 if len(matches) > 1:
754 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
754 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
755 for m in matches:
755 for m in matches:
756 self.ui.warn(' %s\n' % m)
756 self.ui.warn(' %s\n' % m)
757 return None
757 return None
758 if matches:
758 if matches:
759 return matches[0]
759 return matches[0]
760 if len(self.series) > 0 and len(self.applied) > 0:
760 if len(self.series) > 0 and len(self.applied) > 0:
761 if s == 'qtip':
761 if s == 'qtip':
762 return self.series[self.series_end()-1]
762 return self.series[self.series_end()-1]
763 if s == 'qbase':
763 if s == 'qbase':
764 return self.series[0]
764 return self.series[0]
765 return None
765 return None
766 if patch == None:
766 if patch == None:
767 return None
767 return None
768
768
769 # we don't want to return a partial match until we make
769 # we don't want to return a partial match until we make
770 # sure the file name passed in does not exist (checked below)
770 # sure the file name passed in does not exist (checked below)
771 res = partial_name(patch)
771 res = partial_name(patch)
772 if res and res == patch:
772 if res and res == patch:
773 return res
773 return res
774
774
775 if not os.path.isfile(self.join(patch)):
775 if not os.path.isfile(self.join(patch)):
776 try:
776 try:
777 sno = int(patch)
777 sno = int(patch)
778 except(ValueError, OverflowError):
778 except(ValueError, OverflowError):
779 pass
779 pass
780 else:
780 else:
781 if sno < len(self.series):
781 if sno < len(self.series):
782 return self.series[sno]
782 return self.series[sno]
783 if not strict:
783 if not strict:
784 # return any partial match made above
784 # return any partial match made above
785 if res:
785 if res:
786 return res
786 return res
787 minus = patch.rfind('-')
787 minus = patch.rfind('-')
788 if minus >= 0:
788 if minus >= 0:
789 res = partial_name(patch[:minus])
789 res = partial_name(patch[:minus])
790 if res:
790 if res:
791 i = self.series.index(res)
791 i = self.series.index(res)
792 try:
792 try:
793 off = int(patch[minus+1:] or 1)
793 off = int(patch[minus+1:] or 1)
794 except(ValueError, OverflowError):
794 except(ValueError, OverflowError):
795 pass
795 pass
796 else:
796 else:
797 if i - off >= 0:
797 if i - off >= 0:
798 return self.series[i - off]
798 return self.series[i - off]
799 plus = patch.rfind('+')
799 plus = patch.rfind('+')
800 if plus >= 0:
800 if plus >= 0:
801 res = partial_name(patch[:plus])
801 res = partial_name(patch[:plus])
802 if res:
802 if res:
803 i = self.series.index(res)
803 i = self.series.index(res)
804 try:
804 try:
805 off = int(patch[plus+1:] or 1)
805 off = int(patch[plus+1:] or 1)
806 except(ValueError, OverflowError):
806 except(ValueError, OverflowError):
807 pass
807 pass
808 else:
808 else:
809 if i + off < len(self.series):
809 if i + off < len(self.series):
810 return self.series[i + off]
810 return self.series[i + off]
811 raise util.Abort(_("patch %s not in series") % patch)
811 raise util.Abort(_("patch %s not in series") % patch)
812
812
813 def push(self, repo, patch=None, force=False, list=False,
813 def push(self, repo, patch=None, force=False, list=False,
814 mergeq=None, wlock=None):
814 mergeq=None, wlock=None):
815 if not wlock:
815 if not wlock:
816 wlock = repo.wlock()
816 wlock = repo.wlock()
817 patch = self.lookup(patch)
817 patch = self.lookup(patch)
818 if patch and self.isapplied(patch):
818 if patch and self.isapplied(patch):
819 raise util.Abort(_("patch %s is already applied") % patch)
819 raise util.Abort(_("patch %s is already applied") % patch)
820 if self.series_end() == len(self.series):
820 if self.series_end() == len(self.series):
821 raise util.Abort(_("patch series fully applied"))
821 raise util.Abort(_("patch series fully applied"))
822 if not force:
822 if not force:
823 self.check_localchanges(repo)
823 self.check_localchanges(repo)
824
824
825 self.applied_dirty = 1;
825 self.applied_dirty = 1;
826 start = self.series_end()
826 start = self.series_end()
827 if start > 0:
827 if start > 0:
828 self.check_toppatch(repo)
828 self.check_toppatch(repo)
829 if not patch:
829 if not patch:
830 patch = self.series[start]
830 patch = self.series[start]
831 end = start + 1
831 end = start + 1
832 else:
832 else:
833 end = self.series.index(patch, start) + 1
833 end = self.series.index(patch, start) + 1
834 s = self.series[start:end]
834 s = self.series[start:end]
835 if mergeq:
835 if mergeq:
836 ret = self.mergepatch(repo, mergeq, s, wlock)
836 ret = self.mergepatch(repo, mergeq, s, wlock)
837 else:
837 else:
838 ret = self.apply(repo, s, list, wlock=wlock)
838 ret = self.apply(repo, s, list, wlock=wlock)
839 top = self.applied[-1].name
839 top = self.applied[-1].name
840 if ret[0]:
840 if ret[0]:
841 self.ui.write("Errors during apply, please fix and refresh %s\n" %
841 self.ui.write("Errors during apply, please fix and refresh %s\n" %
842 top)
842 top)
843 else:
843 else:
844 self.ui.write("Now at: %s\n" % top)
844 self.ui.write("Now at: %s\n" % top)
845 return ret[0]
845 return ret[0]
846
846
847 def pop(self, repo, patch=None, force=False, update=True, all=False,
847 def pop(self, repo, patch=None, force=False, update=True, all=False,
848 wlock=None):
848 wlock=None):
849 def getfile(f, rev):
849 def getfile(f, rev):
850 t = repo.file(f).read(rev)
850 t = repo.file(f).read(rev)
851 try:
851 try:
852 repo.wfile(f, "w").write(t)
852 repo.wfile(f, "w").write(t)
853 except IOError:
853 except IOError:
854 try:
854 try:
855 os.makedirs(os.path.dirname(repo.wjoin(f)))
855 os.makedirs(os.path.dirname(repo.wjoin(f)))
856 except OSError, err:
856 except OSError, err:
857 if err.errno != errno.EEXIST: raise
857 if err.errno != errno.EEXIST: raise
858 repo.wfile(f, "w").write(t)
858 repo.wfile(f, "w").write(t)
859
859
860 if not wlock:
860 if not wlock:
861 wlock = repo.wlock()
861 wlock = repo.wlock()
862 if patch:
862 if patch:
863 # index, rev, patch
863 # index, rev, patch
864 info = self.isapplied(patch)
864 info = self.isapplied(patch)
865 if not info:
865 if not info:
866 patch = self.lookup(patch)
866 patch = self.lookup(patch)
867 info = self.isapplied(patch)
867 info = self.isapplied(patch)
868 if not info:
868 if not info:
869 raise util.Abort(_("patch %s is not applied") % patch)
869 raise util.Abort(_("patch %s is not applied") % patch)
870 if len(self.applied) == 0:
870 if len(self.applied) == 0:
871 raise util.Abort(_("no patches applied"))
871 raise util.Abort(_("no patches applied"))
872
872
873 if not update:
873 if not update:
874 parents = repo.dirstate.parents()
874 parents = repo.dirstate.parents()
875 rr = [ revlog.bin(x.rev) for x in self.applied ]
875 rr = [ revlog.bin(x.rev) for x in self.applied ]
876 for p in parents:
876 for p in parents:
877 if p in rr:
877 if p in rr:
878 self.ui.warn("qpop: forcing dirstate update\n")
878 self.ui.warn("qpop: forcing dirstate update\n")
879 update = True
879 update = True
880
880
881 if not force and update:
881 if not force and update:
882 self.check_localchanges(repo)
882 self.check_localchanges(repo)
883
883
884 self.applied_dirty = 1;
884 self.applied_dirty = 1;
885 end = len(self.applied)
885 end = len(self.applied)
886 if not patch:
886 if not patch:
887 if all:
887 if all:
888 popi = 0
888 popi = 0
889 else:
889 else:
890 popi = len(self.applied) - 1
890 popi = len(self.applied) - 1
891 else:
891 else:
892 popi = info[0] + 1
892 popi = info[0] + 1
893 if popi >= end:
893 if popi >= end:
894 self.ui.warn("qpop: %s is already at the top\n" % patch)
894 self.ui.warn("qpop: %s is already at the top\n" % patch)
895 return
895 return
896 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
896 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
897
897
898 start = info[0]
898 start = info[0]
899 rev = revlog.bin(info[1])
899 rev = revlog.bin(info[1])
900
900
901 # we know there are no local changes, so we can make a simplified
901 # we know there are no local changes, so we can make a simplified
902 # form of hg.update.
902 # form of hg.update.
903 if update:
903 if update:
904 top = self.check_toppatch(repo)
904 top = self.check_toppatch(repo)
905 qp = self.qparents(repo, rev)
905 qp = self.qparents(repo, rev)
906 changes = repo.changelog.read(qp)
906 changes = repo.changelog.read(qp)
907 mmap = repo.manifest.read(changes[0])
907 mmap = repo.manifest.read(changes[0])
908 m, a, r, d, u = repo.status(qp, top)[:5]
908 m, a, r, d, u = repo.status(qp, top)[:5]
909 if d:
909 if d:
910 raise util.Abort("deletions found between repo revs")
910 raise util.Abort("deletions found between repo revs")
911 for f in m:
911 for f in m:
912 getfile(f, mmap[f])
912 getfile(f, mmap[f])
913 for f in r:
913 for f in r:
914 getfile(f, mmap[f])
914 getfile(f, mmap[f])
915 util.set_exec(repo.wjoin(f), mmap.execf(f))
915 util.set_exec(repo.wjoin(f), mmap.execf(f))
916 repo.dirstate.update(m + r, 'n')
916 repo.dirstate.update(m + r, 'n')
917 for f in a:
917 for f in a:
918 try: os.unlink(repo.wjoin(f))
918 try: os.unlink(repo.wjoin(f))
919 except: raise
919 except: raise
920 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
920 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
921 except: pass
921 except: pass
922 if a:
922 if a:
923 repo.dirstate.forget(a)
923 repo.dirstate.forget(a)
924 repo.dirstate.setparents(qp, revlog.nullid)
924 repo.dirstate.setparents(qp, revlog.nullid)
925 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
925 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
926 del self.applied[start:end]
926 del self.applied[start:end]
927 if len(self.applied):
927 if len(self.applied):
928 self.ui.write("Now at: %s\n" % self.applied[-1].name)
928 self.ui.write("Now at: %s\n" % self.applied[-1].name)
929 else:
929 else:
930 self.ui.write("Patch queue now empty\n")
930 self.ui.write("Patch queue now empty\n")
931
931
932 def diff(self, repo, pats, opts):
932 def diff(self, repo, pats, opts):
933 top = self.check_toppatch(repo)
933 top = self.check_toppatch(repo)
934 if not top:
934 if not top:
935 self.ui.write("No patches applied\n")
935 self.ui.write("No patches applied\n")
936 return
936 return
937 qp = self.qparents(repo, top)
937 qp = self.qparents(repo, top)
938 self.printdiff(repo, qp, files=pats, opts=opts)
938 self.printdiff(repo, qp, files=pats, opts=opts)
939
939
940 def refresh(self, repo, pats=None, **opts):
940 def refresh(self, repo, pats=None, **opts):
941 if len(self.applied) == 0:
941 if len(self.applied) == 0:
942 self.ui.write("No patches applied\n")
942 self.ui.write("No patches applied\n")
943 return 1
943 return 1
944 wlock = repo.wlock()
944 wlock = repo.wlock()
945 self.check_toppatch(repo)
945 self.check_toppatch(repo)
946 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
946 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
947 top = revlog.bin(top)
947 top = revlog.bin(top)
948 cparents = repo.changelog.parents(top)
948 cparents = repo.changelog.parents(top)
949 patchparent = self.qparents(repo, top)
949 patchparent = self.qparents(repo, top)
950 message, comments, user, date, patchfound = self.readheaders(patchfn)
950 message, comments, user, date, patchfound = self.readheaders(patchfn)
951
951
952 patchf = self.opener(patchfn, "w")
952 patchf = self.opener(patchfn, "w")
953 msg = opts.get('msg', '').rstrip()
953 msg = opts.get('msg', '').rstrip()
954 if msg:
954 if msg:
955 if comments:
955 if comments:
956 # Remove existing message.
956 # Remove existing message.
957 ci = 0
957 ci = 0
958 for mi in xrange(len(message)):
958 for mi in xrange(len(message)):
959 while message[mi] != comments[ci]:
959 while message[mi] != comments[ci]:
960 ci += 1
960 ci += 1
961 del comments[ci]
961 del comments[ci]
962 comments.append(msg)
962 comments.append(msg)
963 if comments:
963 if comments:
964 comments = "\n".join(comments) + '\n\n'
964 comments = "\n".join(comments) + '\n\n'
965 patchf.write(comments)
965 patchf.write(comments)
966
966
967 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
967 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
968 tip = repo.changelog.tip()
968 tip = repo.changelog.tip()
969 if top == tip:
969 if top == tip:
970 # if the top of our patch queue is also the tip, there is an
970 # if the top of our patch queue is also the tip, there is an
971 # optimization here. We update the dirstate in place and strip
971 # optimization here. We update the dirstate in place and strip
972 # off the tip commit. Then just commit the current directory
972 # off the tip commit. Then just commit the current directory
973 # tree. We can also send repo.commit the list of files
973 # tree. We can also send repo.commit the list of files
974 # changed to speed up the diff
974 # changed to speed up the diff
975 #
975 #
976 # in short mode, we only diff the files included in the
976 # in short mode, we only diff the files included in the
977 # patch already
977 # patch already
978 #
978 #
979 # this should really read:
979 # this should really read:
980 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
980 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
981 # but we do it backwards to take advantage of manifest/chlog
981 # but we do it backwards to take advantage of manifest/chlog
982 # caching against the next repo.status call
982 # caching against the next repo.status call
983 #
983 #
984 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
984 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
985 if opts.get('short'):
985 if opts.get('short'):
986 filelist = mm + aa + dd
986 filelist = mm + aa + dd
987 else:
987 else:
988 filelist = None
988 filelist = None
989 m, a, r, d, u = repo.status(files=filelist)[:5]
989 m, a, r, d, u = repo.status(files=filelist)[:5]
990
990
991 # we might end up with files that were added between tip and
991 # we might end up with files that were added between tip and
992 # the dirstate parent, but then changed in the local dirstate.
992 # the dirstate parent, but then changed in the local dirstate.
993 # in this case, we want them to only show up in the added section
993 # in this case, we want them to only show up in the added section
994 for x in m:
994 for x in m:
995 if x not in aa:
995 if x not in aa:
996 mm.append(x)
996 mm.append(x)
997 # we might end up with files added by the local dirstate that
997 # we might end up with files added by the local dirstate that
998 # were deleted by the patch. In this case, they should only
998 # were deleted by the patch. In this case, they should only
999 # show up in the changed section.
999 # show up in the changed section.
1000 for x in a:
1000 for x in a:
1001 if x in dd:
1001 if x in dd:
1002 del dd[dd.index(x)]
1002 del dd[dd.index(x)]
1003 mm.append(x)
1003 mm.append(x)
1004 else:
1004 else:
1005 aa.append(x)
1005 aa.append(x)
1006 # make sure any files deleted in the local dirstate
1006 # make sure any files deleted in the local dirstate
1007 # are not in the add or change column of the patch
1007 # are not in the add or change column of the patch
1008 forget = []
1008 forget = []
1009 for x in d + r:
1009 for x in d + r:
1010 if x in aa:
1010 if x in aa:
1011 del aa[aa.index(x)]
1011 del aa[aa.index(x)]
1012 forget.append(x)
1012 forget.append(x)
1013 continue
1013 continue
1014 elif x in mm:
1014 elif x in mm:
1015 del mm[mm.index(x)]
1015 del mm[mm.index(x)]
1016 dd.append(x)
1016 dd.append(x)
1017
1017
1018 m = list(util.unique(mm))
1018 m = util.unique(mm)
1019 r = list(util.unique(dd))
1019 r = util.unique(dd)
1020 a = list(util.unique(aa))
1020 a = util.unique(aa)
1021 filelist = filter(matchfn, util.unique(m + r + a))
1021 filelist = filter(matchfn, util.unique(m + r + a))
1022 if opts.get('git'):
1022 if opts.get('git'):
1023 self.diffopts().git = True
1023 self.diffopts().git = True
1024 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1024 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1025 fp=patchf, changes=(m, a, r, [], u),
1025 fp=patchf, changes=(m, a, r, [], u),
1026 opts=self.diffopts())
1026 opts=self.diffopts())
1027 patchf.close()
1027 patchf.close()
1028
1028
1029 changes = repo.changelog.read(tip)
1029 changes = repo.changelog.read(tip)
1030 repo.dirstate.setparents(*cparents)
1030 repo.dirstate.setparents(*cparents)
1031 copies = [(f, repo.dirstate.copied(f)) for f in a]
1031 copies = [(f, repo.dirstate.copied(f)) for f in a]
1032 repo.dirstate.update(a, 'a')
1032 repo.dirstate.update(a, 'a')
1033 for dst, src in copies:
1033 for dst, src in copies:
1034 repo.dirstate.copy(src, dst)
1034 repo.dirstate.copy(src, dst)
1035 repo.dirstate.update(r, 'r')
1035 repo.dirstate.update(r, 'r')
1036 # if the patch excludes a modified file, mark that file with mtime=0
1036 # if the patch excludes a modified file, mark that file with mtime=0
1037 # so status can see it.
1037 # so status can see it.
1038 mm = []
1038 mm = []
1039 for i in xrange(len(m)-1, -1, -1):
1039 for i in xrange(len(m)-1, -1, -1):
1040 if not matchfn(m[i]):
1040 if not matchfn(m[i]):
1041 mm.append(m[i])
1041 mm.append(m[i])
1042 del m[i]
1042 del m[i]
1043 repo.dirstate.update(m, 'n')
1043 repo.dirstate.update(m, 'n')
1044 repo.dirstate.update(mm, 'n', st_mtime=0)
1044 repo.dirstate.update(mm, 'n', st_mtime=0)
1045 repo.dirstate.forget(forget)
1045 repo.dirstate.forget(forget)
1046
1046
1047 if not msg:
1047 if not msg:
1048 if not message:
1048 if not message:
1049 message = "patch queue: %s\n" % patchfn
1049 message = "patch queue: %s\n" % patchfn
1050 else:
1050 else:
1051 message = "\n".join(message)
1051 message = "\n".join(message)
1052 else:
1052 else:
1053 message = msg
1053 message = msg
1054
1054
1055 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1055 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1056 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1056 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1057 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1057 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1058 self.applied_dirty = 1
1058 self.applied_dirty = 1
1059 else:
1059 else:
1060 self.printdiff(repo, patchparent, fp=patchf)
1060 self.printdiff(repo, patchparent, fp=patchf)
1061 patchf.close()
1061 patchf.close()
1062 self.pop(repo, force=True, wlock=wlock)
1062 self.pop(repo, force=True, wlock=wlock)
1063 self.push(repo, force=True, wlock=wlock)
1063 self.push(repo, force=True, wlock=wlock)
1064
1064
1065 def init(self, repo, create=False):
1065 def init(self, repo, create=False):
1066 if os.path.isdir(self.path):
1066 if os.path.isdir(self.path):
1067 raise util.Abort(_("patch queue directory already exists"))
1067 raise util.Abort(_("patch queue directory already exists"))
1068 os.mkdir(self.path)
1068 os.mkdir(self.path)
1069 if create:
1069 if create:
1070 return self.qrepo(create=True)
1070 return self.qrepo(create=True)
1071
1071
1072 def unapplied(self, repo, patch=None):
1072 def unapplied(self, repo, patch=None):
1073 if patch and patch not in self.series:
1073 if patch and patch not in self.series:
1074 raise util.Abort(_("patch %s is not in series file") % patch)
1074 raise util.Abort(_("patch %s is not in series file") % patch)
1075 if not patch:
1075 if not patch:
1076 start = self.series_end()
1076 start = self.series_end()
1077 else:
1077 else:
1078 start = self.series.index(patch) + 1
1078 start = self.series.index(patch) + 1
1079 unapplied = []
1079 unapplied = []
1080 for i in xrange(start, len(self.series)):
1080 for i in xrange(start, len(self.series)):
1081 pushable, reason = self.pushable(i)
1081 pushable, reason = self.pushable(i)
1082 if pushable:
1082 if pushable:
1083 unapplied.append((i, self.series[i]))
1083 unapplied.append((i, self.series[i]))
1084 self.explain_pushable(i)
1084 self.explain_pushable(i)
1085 return unapplied
1085 return unapplied
1086
1086
1087 def qseries(self, repo, missing=None, start=0, length=0, status=None,
1087 def qseries(self, repo, missing=None, start=0, length=0, status=None,
1088 summary=False):
1088 summary=False):
1089 def displayname(patchname):
1089 def displayname(patchname):
1090 if summary:
1090 if summary:
1091 msg = self.readheaders(patchname)[0]
1091 msg = self.readheaders(patchname)[0]
1092 msg = msg and ': ' + msg[0] or ': '
1092 msg = msg and ': ' + msg[0] or ': '
1093 else:
1093 else:
1094 msg = ''
1094 msg = ''
1095 return '%s%s' % (patchname, msg)
1095 return '%s%s' % (patchname, msg)
1096
1096
1097 def pname(i):
1097 def pname(i):
1098 if status == 'A':
1098 if status == 'A':
1099 return self.applied[i].name
1099 return self.applied[i].name
1100 else:
1100 else:
1101 return self.series[i]
1101 return self.series[i]
1102
1102
1103 unapplied = self.series_end(all_patches=True)
1103 unapplied = self.series_end(all_patches=True)
1104 if not length:
1104 if not length:
1105 length = len(self.series) - start
1105 length = len(self.series) - start
1106 if not missing:
1106 if not missing:
1107 for i in xrange(start, start+length):
1107 for i in xrange(start, start+length):
1108 pfx = ''
1108 pfx = ''
1109 patch = pname(i)
1109 patch = pname(i)
1110 if self.ui.verbose:
1110 if self.ui.verbose:
1111 if i < unapplied:
1111 if i < unapplied:
1112 status = 'A'
1112 status = 'A'
1113 elif self.pushable(i)[0]:
1113 elif self.pushable(i)[0]:
1114 status = 'U'
1114 status = 'U'
1115 else:
1115 else:
1116 status = 'G'
1116 status = 'G'
1117 pfx = '%d %s ' % (i, status)
1117 pfx = '%d %s ' % (i, status)
1118 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1118 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1119 else:
1119 else:
1120 msng_list = []
1120 msng_list = []
1121 for root, dirs, files in os.walk(self.path):
1121 for root, dirs, files in os.walk(self.path):
1122 d = root[len(self.path) + 1:]
1122 d = root[len(self.path) + 1:]
1123 for f in files:
1123 for f in files:
1124 fl = os.path.join(d, f)
1124 fl = os.path.join(d, f)
1125 if (fl not in self.series and
1125 if (fl not in self.series and
1126 fl not in (self.status_path, self.series_path)
1126 fl not in (self.status_path, self.series_path)
1127 and not fl.startswith('.')):
1127 and not fl.startswith('.')):
1128 msng_list.append(fl)
1128 msng_list.append(fl)
1129 msng_list.sort()
1129 msng_list.sort()
1130 for x in msng_list:
1130 for x in msng_list:
1131 pfx = self.ui.verbose and ('D ') or ''
1131 pfx = self.ui.verbose and ('D ') or ''
1132 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1132 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1133
1133
1134 def issaveline(self, l):
1134 def issaveline(self, l):
1135 if l.name == '.hg.patches.save.line':
1135 if l.name == '.hg.patches.save.line':
1136 return True
1136 return True
1137
1137
1138 def qrepo(self, create=False):
1138 def qrepo(self, create=False):
1139 if create or os.path.isdir(self.join(".hg")):
1139 if create or os.path.isdir(self.join(".hg")):
1140 return hg.repository(self.ui, path=self.path, create=create)
1140 return hg.repository(self.ui, path=self.path, create=create)
1141
1141
1142 def restore(self, repo, rev, delete=None, qupdate=None):
1142 def restore(self, repo, rev, delete=None, qupdate=None):
1143 c = repo.changelog.read(rev)
1143 c = repo.changelog.read(rev)
1144 desc = c[4].strip()
1144 desc = c[4].strip()
1145 lines = desc.splitlines()
1145 lines = desc.splitlines()
1146 i = 0
1146 i = 0
1147 datastart = None
1147 datastart = None
1148 series = []
1148 series = []
1149 applied = []
1149 applied = []
1150 qpp = None
1150 qpp = None
1151 for i in xrange(0, len(lines)):
1151 for i in xrange(0, len(lines)):
1152 if lines[i] == 'Patch Data:':
1152 if lines[i] == 'Patch Data:':
1153 datastart = i + 1
1153 datastart = i + 1
1154 elif lines[i].startswith('Dirstate:'):
1154 elif lines[i].startswith('Dirstate:'):
1155 l = lines[i].rstrip()
1155 l = lines[i].rstrip()
1156 l = l[10:].split(' ')
1156 l = l[10:].split(' ')
1157 qpp = [ hg.bin(x) for x in l ]
1157 qpp = [ hg.bin(x) for x in l ]
1158 elif datastart != None:
1158 elif datastart != None:
1159 l = lines[i].rstrip()
1159 l = lines[i].rstrip()
1160 se = statusentry(l)
1160 se = statusentry(l)
1161 file_ = se.name
1161 file_ = se.name
1162 if se.rev:
1162 if se.rev:
1163 applied.append(se)
1163 applied.append(se)
1164 else:
1164 else:
1165 series.append(file_)
1165 series.append(file_)
1166 if datastart == None:
1166 if datastart == None:
1167 self.ui.warn("No saved patch data found\n")
1167 self.ui.warn("No saved patch data found\n")
1168 return 1
1168 return 1
1169 self.ui.warn("restoring status: %s\n" % lines[0])
1169 self.ui.warn("restoring status: %s\n" % lines[0])
1170 self.full_series = series
1170 self.full_series = series
1171 self.applied = applied
1171 self.applied = applied
1172 self.parse_series()
1172 self.parse_series()
1173 self.series_dirty = 1
1173 self.series_dirty = 1
1174 self.applied_dirty = 1
1174 self.applied_dirty = 1
1175 heads = repo.changelog.heads()
1175 heads = repo.changelog.heads()
1176 if delete:
1176 if delete:
1177 if rev not in heads:
1177 if rev not in heads:
1178 self.ui.warn("save entry has children, leaving it alone\n")
1178 self.ui.warn("save entry has children, leaving it alone\n")
1179 else:
1179 else:
1180 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1180 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1181 pp = repo.dirstate.parents()
1181 pp = repo.dirstate.parents()
1182 if rev in pp:
1182 if rev in pp:
1183 update = True
1183 update = True
1184 else:
1184 else:
1185 update = False
1185 update = False
1186 self.strip(repo, rev, update=update, backup='strip')
1186 self.strip(repo, rev, update=update, backup='strip')
1187 if qpp:
1187 if qpp:
1188 self.ui.warn("saved queue repository parents: %s %s\n" %
1188 self.ui.warn("saved queue repository parents: %s %s\n" %
1189 (hg.short(qpp[0]), hg.short(qpp[1])))
1189 (hg.short(qpp[0]), hg.short(qpp[1])))
1190 if qupdate:
1190 if qupdate:
1191 print "queue directory updating"
1191 print "queue directory updating"
1192 r = self.qrepo()
1192 r = self.qrepo()
1193 if not r:
1193 if not r:
1194 self.ui.warn("Unable to load queue repository\n")
1194 self.ui.warn("Unable to load queue repository\n")
1195 return 1
1195 return 1
1196 hg.clean(r, qpp[0])
1196 hg.clean(r, qpp[0])
1197
1197
1198 def save(self, repo, msg=None):
1198 def save(self, repo, msg=None):
1199 if len(self.applied) == 0:
1199 if len(self.applied) == 0:
1200 self.ui.warn("save: no patches applied, exiting\n")
1200 self.ui.warn("save: no patches applied, exiting\n")
1201 return 1
1201 return 1
1202 if self.issaveline(self.applied[-1]):
1202 if self.issaveline(self.applied[-1]):
1203 self.ui.warn("status is already saved\n")
1203 self.ui.warn("status is already saved\n")
1204 return 1
1204 return 1
1205
1205
1206 ar = [ ':' + x for x in self.full_series ]
1206 ar = [ ':' + x for x in self.full_series ]
1207 if not msg:
1207 if not msg:
1208 msg = "hg patches saved state"
1208 msg = "hg patches saved state"
1209 else:
1209 else:
1210 msg = "hg patches: " + msg.rstrip('\r\n')
1210 msg = "hg patches: " + msg.rstrip('\r\n')
1211 r = self.qrepo()
1211 r = self.qrepo()
1212 if r:
1212 if r:
1213 pp = r.dirstate.parents()
1213 pp = r.dirstate.parents()
1214 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1214 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1215 msg += "\n\nPatch Data:\n"
1215 msg += "\n\nPatch Data:\n"
1216 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1216 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1217 "\n".join(ar) + '\n' or "")
1217 "\n".join(ar) + '\n' or "")
1218 n = repo.commit(None, text, user=None, force=1)
1218 n = repo.commit(None, text, user=None, force=1)
1219 if not n:
1219 if not n:
1220 self.ui.warn("repo commit failed\n")
1220 self.ui.warn("repo commit failed\n")
1221 return 1
1221 return 1
1222 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1222 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1223 self.applied_dirty = 1
1223 self.applied_dirty = 1
1224
1224
1225 def full_series_end(self):
1225 def full_series_end(self):
1226 if len(self.applied) > 0:
1226 if len(self.applied) > 0:
1227 p = self.applied[-1].name
1227 p = self.applied[-1].name
1228 end = self.find_series(p)
1228 end = self.find_series(p)
1229 if end == None:
1229 if end == None:
1230 return len(self.full_series)
1230 return len(self.full_series)
1231 return end + 1
1231 return end + 1
1232 return 0
1232 return 0
1233
1233
1234 def series_end(self, all_patches=False):
1234 def series_end(self, all_patches=False):
1235 end = 0
1235 end = 0
1236 def next(start):
1236 def next(start):
1237 if all_patches:
1237 if all_patches:
1238 return start
1238 return start
1239 i = start
1239 i = start
1240 while i < len(self.series):
1240 while i < len(self.series):
1241 p, reason = self.pushable(i)
1241 p, reason = self.pushable(i)
1242 if p:
1242 if p:
1243 break
1243 break
1244 self.explain_pushable(i)
1244 self.explain_pushable(i)
1245 i += 1
1245 i += 1
1246 return i
1246 return i
1247 if len(self.applied) > 0:
1247 if len(self.applied) > 0:
1248 p = self.applied[-1].name
1248 p = self.applied[-1].name
1249 try:
1249 try:
1250 end = self.series.index(p)
1250 end = self.series.index(p)
1251 except ValueError:
1251 except ValueError:
1252 return 0
1252 return 0
1253 return next(end + 1)
1253 return next(end + 1)
1254 return next(end)
1254 return next(end)
1255
1255
1256 def appliedname(self, index):
1256 def appliedname(self, index):
1257 pname = self.applied[index].name
1257 pname = self.applied[index].name
1258 if not self.ui.verbose:
1258 if not self.ui.verbose:
1259 p = pname
1259 p = pname
1260 else:
1260 else:
1261 p = str(self.series.index(pname)) + " " + pname
1261 p = str(self.series.index(pname)) + " " + pname
1262 return p
1262 return p
1263
1263
1264 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1264 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1265 force=None):
1265 force=None):
1266 def checkseries(patchname):
1266 def checkseries(patchname):
1267 if patchname in self.series:
1267 if patchname in self.series:
1268 raise util.Abort(_('patch %s is already in the series file')
1268 raise util.Abort(_('patch %s is already in the series file')
1269 % patchname)
1269 % patchname)
1270 def checkfile(patchname):
1270 def checkfile(patchname):
1271 if not force and os.path.exists(self.join(patchname)):
1271 if not force and os.path.exists(self.join(patchname)):
1272 raise util.Abort(_('patch "%s" already exists')
1272 raise util.Abort(_('patch "%s" already exists')
1273 % patchname)
1273 % patchname)
1274
1274
1275 if rev:
1275 if rev:
1276 if files:
1276 if files:
1277 raise util.Abort(_('option "-r" not valid when importing '
1277 raise util.Abort(_('option "-r" not valid when importing '
1278 'files'))
1278 'files'))
1279 rev = cmdutil.revrange(self.ui, repo, rev)
1279 rev = cmdutil.revrange(self.ui, repo, rev)
1280 rev.sort(lambda x, y: cmp(y, x))
1280 rev.sort(lambda x, y: cmp(y, x))
1281 if (len(files) > 1 or len(rev) > 1) and patchname:
1281 if (len(files) > 1 or len(rev) > 1) and patchname:
1282 raise util.Abort(_('option "-n" not valid when importing multiple '
1282 raise util.Abort(_('option "-n" not valid when importing multiple '
1283 'patches'))
1283 'patches'))
1284 i = 0
1284 i = 0
1285 added = []
1285 added = []
1286 if rev:
1286 if rev:
1287 # If mq patches are applied, we can only import revisions
1287 # If mq patches are applied, we can only import revisions
1288 # that form a linear path to qbase.
1288 # that form a linear path to qbase.
1289 # Otherwise, they should form a linear path to a head.
1289 # Otherwise, they should form a linear path to a head.
1290 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1290 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1291 if len(heads) > 1:
1291 if len(heads) > 1:
1292 raise util.Abort(_('revision %d is the root of more than one '
1292 raise util.Abort(_('revision %d is the root of more than one '
1293 'branch') % rev[-1])
1293 'branch') % rev[-1])
1294 if self.applied:
1294 if self.applied:
1295 base = revlog.hex(repo.changelog.node(rev[0]))
1295 base = revlog.hex(repo.changelog.node(rev[0]))
1296 if base in [n.rev for n in self.applied]:
1296 if base in [n.rev for n in self.applied]:
1297 raise util.Abort(_('revision %d is already managed')
1297 raise util.Abort(_('revision %d is already managed')
1298 % rev[0])
1298 % rev[0])
1299 if heads != [revlog.bin(self.applied[-1].rev)]:
1299 if heads != [revlog.bin(self.applied[-1].rev)]:
1300 raise util.Abort(_('revision %d is not the parent of '
1300 raise util.Abort(_('revision %d is not the parent of '
1301 'the queue') % rev[0])
1301 'the queue') % rev[0])
1302 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1302 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1303 lastparent = repo.changelog.parentrevs(base)[0]
1303 lastparent = repo.changelog.parentrevs(base)[0]
1304 else:
1304 else:
1305 if heads != [repo.changelog.node(rev[0])]:
1305 if heads != [repo.changelog.node(rev[0])]:
1306 raise util.Abort(_('revision %d has unmanaged children')
1306 raise util.Abort(_('revision %d has unmanaged children')
1307 % rev[0])
1307 % rev[0])
1308 lastparent = None
1308 lastparent = None
1309
1309
1310 for r in rev:
1310 for r in rev:
1311 p1, p2 = repo.changelog.parentrevs(r)
1311 p1, p2 = repo.changelog.parentrevs(r)
1312 n = repo.changelog.node(r)
1312 n = repo.changelog.node(r)
1313 if p2 != -1:
1313 if p2 != -1:
1314 raise util.Abort(_('cannot import merge revision %d') % r)
1314 raise util.Abort(_('cannot import merge revision %d') % r)
1315 if lastparent and lastparent != r:
1315 if lastparent and lastparent != r:
1316 raise util.Abort(_('revision %d is not the parent of %d')
1316 raise util.Abort(_('revision %d is not the parent of %d')
1317 % (r, lastparent))
1317 % (r, lastparent))
1318 lastparent = p1
1318 lastparent = p1
1319
1319
1320 if not patchname:
1320 if not patchname:
1321 patchname = '%d.diff' % r
1321 patchname = '%d.diff' % r
1322 checkseries(patchname)
1322 checkseries(patchname)
1323 checkfile(patchname)
1323 checkfile(patchname)
1324 self.full_series.insert(0, patchname)
1324 self.full_series.insert(0, patchname)
1325
1325
1326 patchf = self.opener(patchname, "w")
1326 patchf = self.opener(patchname, "w")
1327 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1327 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1328 patchf.close()
1328 patchf.close()
1329
1329
1330 se = statusentry(revlog.hex(n), patchname)
1330 se = statusentry(revlog.hex(n), patchname)
1331 self.applied.insert(0, se)
1331 self.applied.insert(0, se)
1332
1332
1333 added.append(patchname)
1333 added.append(patchname)
1334 patchname = None
1334 patchname = None
1335 self.parse_series()
1335 self.parse_series()
1336 self.applied_dirty = 1
1336 self.applied_dirty = 1
1337
1337
1338 for filename in files:
1338 for filename in files:
1339 if existing:
1339 if existing:
1340 if not patchname:
1340 if not patchname:
1341 patchname = filename
1341 patchname = filename
1342 if not os.path.isfile(self.join(patchname)):
1342 if not os.path.isfile(self.join(patchname)):
1343 raise util.Abort(_("patch %s does not exist") % patchname)
1343 raise util.Abort(_("patch %s does not exist") % patchname)
1344 else:
1344 else:
1345 try:
1345 try:
1346 text = file(filename).read()
1346 text = file(filename).read()
1347 except IOError:
1347 except IOError:
1348 raise util.Abort(_("unable to read %s") % patchname)
1348 raise util.Abort(_("unable to read %s") % patchname)
1349 if not patchname:
1349 if not patchname:
1350 patchname = os.path.basename(filename)
1350 patchname = os.path.basename(filename)
1351 checkfile(patchname)
1351 checkfile(patchname)
1352 patchf = self.opener(patchname, "w")
1352 patchf = self.opener(patchname, "w")
1353 patchf.write(text)
1353 patchf.write(text)
1354 checkseries(patchname)
1354 checkseries(patchname)
1355 index = self.full_series_end() + i
1355 index = self.full_series_end() + i
1356 self.full_series[index:index] = [patchname]
1356 self.full_series[index:index] = [patchname]
1357 self.parse_series()
1357 self.parse_series()
1358 self.ui.warn("adding %s to series file\n" % patchname)
1358 self.ui.warn("adding %s to series file\n" % patchname)
1359 i += 1
1359 i += 1
1360 added.append(patchname)
1360 added.append(patchname)
1361 patchname = None
1361 patchname = None
1362 self.series_dirty = 1
1362 self.series_dirty = 1
1363 qrepo = self.qrepo()
1363 qrepo = self.qrepo()
1364 if qrepo:
1364 if qrepo:
1365 qrepo.add(added)
1365 qrepo.add(added)
1366
1366
1367 def delete(ui, repo, *patches, **opts):
1367 def delete(ui, repo, *patches, **opts):
1368 """remove patches from queue
1368 """remove patches from queue
1369
1369
1370 With --rev, mq will stop managing the named revisions. The
1370 With --rev, mq will stop managing the named revisions. The
1371 patches must be applied and at the base of the stack. This option
1371 patches must be applied and at the base of the stack. This option
1372 is useful when the patches have been applied upstream.
1372 is useful when the patches have been applied upstream.
1373
1373
1374 Otherwise, the patches must not be applied.
1374 Otherwise, the patches must not be applied.
1375
1375
1376 With --keep, the patch files are preserved in the patch directory."""
1376 With --keep, the patch files are preserved in the patch directory."""
1377 q = repo.mq
1377 q = repo.mq
1378 q.delete(repo, patches, opts)
1378 q.delete(repo, patches, opts)
1379 q.save_dirty()
1379 q.save_dirty()
1380 return 0
1380 return 0
1381
1381
1382 def applied(ui, repo, patch=None, **opts):
1382 def applied(ui, repo, patch=None, **opts):
1383 """print the patches already applied"""
1383 """print the patches already applied"""
1384 q = repo.mq
1384 q = repo.mq
1385 if patch:
1385 if patch:
1386 if patch not in q.series:
1386 if patch not in q.series:
1387 raise util.Abort(_("patch %s is not in series file") % patch)
1387 raise util.Abort(_("patch %s is not in series file") % patch)
1388 end = q.series.index(patch) + 1
1388 end = q.series.index(patch) + 1
1389 else:
1389 else:
1390 end = len(q.applied)
1390 end = len(q.applied)
1391 if not end:
1391 if not end:
1392 return
1392 return
1393
1393
1394 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1394 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1395
1395
1396 def unapplied(ui, repo, patch=None, **opts):
1396 def unapplied(ui, repo, patch=None, **opts):
1397 """print the patches not yet applied"""
1397 """print the patches not yet applied"""
1398 q = repo.mq
1398 q = repo.mq
1399 if patch:
1399 if patch:
1400 if patch not in q.series:
1400 if patch not in q.series:
1401 raise util.Abort(_("patch %s is not in series file") % patch)
1401 raise util.Abort(_("patch %s is not in series file") % patch)
1402 start = q.series.index(patch) + 1
1402 start = q.series.index(patch) + 1
1403 else:
1403 else:
1404 start = q.series_end()
1404 start = q.series_end()
1405 q.qseries(repo, start=start, summary=opts.get('summary'))
1405 q.qseries(repo, start=start, summary=opts.get('summary'))
1406
1406
1407 def qimport(ui, repo, *filename, **opts):
1407 def qimport(ui, repo, *filename, **opts):
1408 """import a patch
1408 """import a patch
1409
1409
1410 The patch will have the same name as its source file unless you
1410 The patch will have the same name as its source file unless you
1411 give it a new one with --name.
1411 give it a new one with --name.
1412
1412
1413 You can register an existing patch inside the patch directory
1413 You can register an existing patch inside the patch directory
1414 with the --existing flag.
1414 with the --existing flag.
1415
1415
1416 With --force, an existing patch of the same name will be overwritten.
1416 With --force, an existing patch of the same name will be overwritten.
1417
1417
1418 An existing changeset may be placed under mq control with --rev
1418 An existing changeset may be placed under mq control with --rev
1419 (e.g. qimport --rev tip -n patch will place tip under mq control).
1419 (e.g. qimport --rev tip -n patch will place tip under mq control).
1420 """
1420 """
1421 q = repo.mq
1421 q = repo.mq
1422 q.qimport(repo, filename, patchname=opts['name'],
1422 q.qimport(repo, filename, patchname=opts['name'],
1423 existing=opts['existing'], force=opts['force'], rev=opts['rev'])
1423 existing=opts['existing'], force=opts['force'], rev=opts['rev'])
1424 q.save_dirty()
1424 q.save_dirty()
1425 return 0
1425 return 0
1426
1426
1427 def init(ui, repo, **opts):
1427 def init(ui, repo, **opts):
1428 """init a new queue repository
1428 """init a new queue repository
1429
1429
1430 The queue repository is unversioned by default. If -c is
1430 The queue repository is unversioned by default. If -c is
1431 specified, qinit will create a separate nested repository
1431 specified, qinit will create a separate nested repository
1432 for patches. Use qcommit to commit changes to this queue
1432 for patches. Use qcommit to commit changes to this queue
1433 repository."""
1433 repository."""
1434 q = repo.mq
1434 q = repo.mq
1435 r = q.init(repo, create=opts['create_repo'])
1435 r = q.init(repo, create=opts['create_repo'])
1436 q.save_dirty()
1436 q.save_dirty()
1437 if r:
1437 if r:
1438 fp = r.wopener('.hgignore', 'w')
1438 fp = r.wopener('.hgignore', 'w')
1439 print >> fp, 'syntax: glob'
1439 print >> fp, 'syntax: glob'
1440 print >> fp, 'status'
1440 print >> fp, 'status'
1441 fp.close()
1441 fp.close()
1442 r.wopener('series', 'w').close()
1442 r.wopener('series', 'w').close()
1443 r.add(['.hgignore', 'series'])
1443 r.add(['.hgignore', 'series'])
1444 return 0
1444 return 0
1445
1445
1446 def clone(ui, source, dest=None, **opts):
1446 def clone(ui, source, dest=None, **opts):
1447 '''clone main and patch repository at same time
1447 '''clone main and patch repository at same time
1448
1448
1449 If source is local, destination will have no patches applied. If
1449 If source is local, destination will have no patches applied. If
1450 source is remote, this command can not check if patches are
1450 source is remote, this command can not check if patches are
1451 applied in source, so cannot guarantee that patches are not
1451 applied in source, so cannot guarantee that patches are not
1452 applied in destination. If you clone remote repository, be sure
1452 applied in destination. If you clone remote repository, be sure
1453 before that it has no patches applied.
1453 before that it has no patches applied.
1454
1454
1455 Source patch repository is looked for in <src>/.hg/patches by
1455 Source patch repository is looked for in <src>/.hg/patches by
1456 default. Use -p <url> to change.
1456 default. Use -p <url> to change.
1457 '''
1457 '''
1458 commands.setremoteconfig(ui, opts)
1458 commands.setremoteconfig(ui, opts)
1459 if dest is None:
1459 if dest is None:
1460 dest = hg.defaultdest(source)
1460 dest = hg.defaultdest(source)
1461 sr = hg.repository(ui, ui.expandpath(source))
1461 sr = hg.repository(ui, ui.expandpath(source))
1462 qbase, destrev = None, None
1462 qbase, destrev = None, None
1463 if sr.local():
1463 if sr.local():
1464 reposetup(ui, sr)
1464 reposetup(ui, sr)
1465 if sr.mq.applied:
1465 if sr.mq.applied:
1466 qbase = revlog.bin(sr.mq.applied[0].rev)
1466 qbase = revlog.bin(sr.mq.applied[0].rev)
1467 if not hg.islocal(dest):
1467 if not hg.islocal(dest):
1468 destrev = sr.parents(qbase)[0]
1468 destrev = sr.parents(qbase)[0]
1469 ui.note(_('cloning main repo\n'))
1469 ui.note(_('cloning main repo\n'))
1470 sr, dr = hg.clone(ui, sr, dest,
1470 sr, dr = hg.clone(ui, sr, dest,
1471 pull=opts['pull'],
1471 pull=opts['pull'],
1472 rev=destrev,
1472 rev=destrev,
1473 update=False,
1473 update=False,
1474 stream=opts['uncompressed'])
1474 stream=opts['uncompressed'])
1475 ui.note(_('cloning patch repo\n'))
1475 ui.note(_('cloning patch repo\n'))
1476 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1476 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1477 dr.url() + '/.hg/patches',
1477 dr.url() + '/.hg/patches',
1478 pull=opts['pull'],
1478 pull=opts['pull'],
1479 update=not opts['noupdate'],
1479 update=not opts['noupdate'],
1480 stream=opts['uncompressed'])
1480 stream=opts['uncompressed'])
1481 if dr.local():
1481 if dr.local():
1482 if qbase:
1482 if qbase:
1483 ui.note(_('stripping applied patches from destination repo\n'))
1483 ui.note(_('stripping applied patches from destination repo\n'))
1484 reposetup(ui, dr)
1484 reposetup(ui, dr)
1485 dr.mq.strip(dr, qbase, update=False, backup=None)
1485 dr.mq.strip(dr, qbase, update=False, backup=None)
1486 if not opts['noupdate']:
1486 if not opts['noupdate']:
1487 ui.note(_('updating destination repo\n'))
1487 ui.note(_('updating destination repo\n'))
1488 hg.update(dr, dr.changelog.tip())
1488 hg.update(dr, dr.changelog.tip())
1489
1489
1490 def commit(ui, repo, *pats, **opts):
1490 def commit(ui, repo, *pats, **opts):
1491 """commit changes in the queue repository"""
1491 """commit changes in the queue repository"""
1492 q = repo.mq
1492 q = repo.mq
1493 r = q.qrepo()
1493 r = q.qrepo()
1494 if not r: raise util.Abort('no queue repository')
1494 if not r: raise util.Abort('no queue repository')
1495 commands.commit(r.ui, r, *pats, **opts)
1495 commands.commit(r.ui, r, *pats, **opts)
1496
1496
1497 def series(ui, repo, **opts):
1497 def series(ui, repo, **opts):
1498 """print the entire series file"""
1498 """print the entire series file"""
1499 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1499 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1500 return 0
1500 return 0
1501
1501
1502 def top(ui, repo, **opts):
1502 def top(ui, repo, **opts):
1503 """print the name of the current patch"""
1503 """print the name of the current patch"""
1504 q = repo.mq
1504 q = repo.mq
1505 t = len(q.applied)
1505 t = len(q.applied)
1506 if t:
1506 if t:
1507 return q.qseries(repo, start=t-1, length=1, status='A',
1507 return q.qseries(repo, start=t-1, length=1, status='A',
1508 summary=opts.get('summary'))
1508 summary=opts.get('summary'))
1509 else:
1509 else:
1510 ui.write("No patches applied\n")
1510 ui.write("No patches applied\n")
1511 return 1
1511 return 1
1512
1512
1513 def next(ui, repo, **opts):
1513 def next(ui, repo, **opts):
1514 """print the name of the next patch"""
1514 """print the name of the next patch"""
1515 q = repo.mq
1515 q = repo.mq
1516 end = q.series_end()
1516 end = q.series_end()
1517 if end == len(q.series):
1517 if end == len(q.series):
1518 ui.write("All patches applied\n")
1518 ui.write("All patches applied\n")
1519 return 1
1519 return 1
1520 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1520 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1521
1521
1522 def prev(ui, repo, **opts):
1522 def prev(ui, repo, **opts):
1523 """print the name of the previous patch"""
1523 """print the name of the previous patch"""
1524 q = repo.mq
1524 q = repo.mq
1525 l = len(q.applied)
1525 l = len(q.applied)
1526 if l == 1:
1526 if l == 1:
1527 ui.write("Only one patch applied\n")
1527 ui.write("Only one patch applied\n")
1528 return 1
1528 return 1
1529 if not l:
1529 if not l:
1530 ui.write("No patches applied\n")
1530 ui.write("No patches applied\n")
1531 return 1
1531 return 1
1532 return q.qseries(repo, start=l-2, length=1, status='A',
1532 return q.qseries(repo, start=l-2, length=1, status='A',
1533 summary=opts.get('summary'))
1533 summary=opts.get('summary'))
1534
1534
1535 def new(ui, repo, patch, **opts):
1535 def new(ui, repo, patch, **opts):
1536 """create a new patch
1536 """create a new patch
1537
1537
1538 qnew creates a new patch on top of the currently-applied patch
1538 qnew creates a new patch on top of the currently-applied patch
1539 (if any). It will refuse to run if there are any outstanding
1539 (if any). It will refuse to run if there are any outstanding
1540 changes unless -f is specified, in which case the patch will
1540 changes unless -f is specified, in which case the patch will
1541 be initialised with them.
1541 be initialised with them.
1542
1542
1543 -e, -m or -l set the patch header as well as the commit message.
1543 -e, -m or -l set the patch header as well as the commit message.
1544 If none is specified, the patch header is empty and the
1544 If none is specified, the patch header is empty and the
1545 commit message is 'New patch: PATCH'"""
1545 commit message is 'New patch: PATCH'"""
1546 q = repo.mq
1546 q = repo.mq
1547 message = commands.logmessage(opts)
1547 message = commands.logmessage(opts)
1548 if opts['edit']:
1548 if opts['edit']:
1549 message = ui.edit(message, ui.username())
1549 message = ui.edit(message, ui.username())
1550 q.new(repo, patch, msg=message, force=opts['force'])
1550 q.new(repo, patch, msg=message, force=opts['force'])
1551 q.save_dirty()
1551 q.save_dirty()
1552 return 0
1552 return 0
1553
1553
1554 def refresh(ui, repo, *pats, **opts):
1554 def refresh(ui, repo, *pats, **opts):
1555 """update the current patch
1555 """update the current patch
1556
1556
1557 If any file patterns are provided, the refreshed patch will contain only
1557 If any file patterns are provided, the refreshed patch will contain only
1558 the modifications that match those patterns; the remaining modifications
1558 the modifications that match those patterns; the remaining modifications
1559 will remain in the working directory.
1559 will remain in the working directory.
1560 """
1560 """
1561 q = repo.mq
1561 q = repo.mq
1562 message = commands.logmessage(opts)
1562 message = commands.logmessage(opts)
1563 if opts['edit']:
1563 if opts['edit']:
1564 if message:
1564 if message:
1565 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1565 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1566 patch = q.applied[-1].name
1566 patch = q.applied[-1].name
1567 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1567 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1568 message = ui.edit('\n'.join(message), user or ui.username())
1568 message = ui.edit('\n'.join(message), user or ui.username())
1569 ret = q.refresh(repo, pats, msg=message, **opts)
1569 ret = q.refresh(repo, pats, msg=message, **opts)
1570 q.save_dirty()
1570 q.save_dirty()
1571 return ret
1571 return ret
1572
1572
1573 def diff(ui, repo, *pats, **opts):
1573 def diff(ui, repo, *pats, **opts):
1574 """diff of the current patch"""
1574 """diff of the current patch"""
1575 repo.mq.diff(repo, pats, opts)
1575 repo.mq.diff(repo, pats, opts)
1576 return 0
1576 return 0
1577
1577
1578 def fold(ui, repo, *files, **opts):
1578 def fold(ui, repo, *files, **opts):
1579 """fold the named patches into the current patch
1579 """fold the named patches into the current patch
1580
1580
1581 Patches must not yet be applied. Each patch will be successively
1581 Patches must not yet be applied. Each patch will be successively
1582 applied to the current patch in the order given. If all the
1582 applied to the current patch in the order given. If all the
1583 patches apply successfully, the current patch will be refreshed
1583 patches apply successfully, the current patch will be refreshed
1584 with the new cumulative patch, and the folded patches will
1584 with the new cumulative patch, and the folded patches will
1585 be deleted. With -k/--keep, the folded patch files will not
1585 be deleted. With -k/--keep, the folded patch files will not
1586 be removed afterwards.
1586 be removed afterwards.
1587
1587
1588 The header for each folded patch will be concatenated with
1588 The header for each folded patch will be concatenated with
1589 the current patch header, separated by a line of '* * *'."""
1589 the current patch header, separated by a line of '* * *'."""
1590
1590
1591 q = repo.mq
1591 q = repo.mq
1592
1592
1593 if not files:
1593 if not files:
1594 raise util.Abort(_('qfold requires at least one patch name'))
1594 raise util.Abort(_('qfold requires at least one patch name'))
1595 if not q.check_toppatch(repo):
1595 if not q.check_toppatch(repo):
1596 raise util.Abort(_('No patches applied'))
1596 raise util.Abort(_('No patches applied'))
1597
1597
1598 message = commands.logmessage(opts)
1598 message = commands.logmessage(opts)
1599 if opts['edit']:
1599 if opts['edit']:
1600 if message:
1600 if message:
1601 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1601 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1602
1602
1603 parent = q.lookup('qtip')
1603 parent = q.lookup('qtip')
1604 patches = []
1604 patches = []
1605 messages = []
1605 messages = []
1606 for f in files:
1606 for f in files:
1607 p = q.lookup(f)
1607 p = q.lookup(f)
1608 if p in patches or p == parent:
1608 if p in patches or p == parent:
1609 ui.warn(_('Skipping already folded patch %s') % p)
1609 ui.warn(_('Skipping already folded patch %s') % p)
1610 if q.isapplied(p):
1610 if q.isapplied(p):
1611 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1611 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1612 patches.append(p)
1612 patches.append(p)
1613
1613
1614 for p in patches:
1614 for p in patches:
1615 if not message:
1615 if not message:
1616 messages.append(q.readheaders(p)[0])
1616 messages.append(q.readheaders(p)[0])
1617 pf = q.join(p)
1617 pf = q.join(p)
1618 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1618 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1619 if not patchsuccess:
1619 if not patchsuccess:
1620 raise util.Abort(_('Error folding patch %s') % p)
1620 raise util.Abort(_('Error folding patch %s') % p)
1621 patch.updatedir(ui, repo, files)
1621 patch.updatedir(ui, repo, files)
1622
1622
1623 if not message:
1623 if not message:
1624 message, comments, user = q.readheaders(parent)[0:3]
1624 message, comments, user = q.readheaders(parent)[0:3]
1625 for msg in messages:
1625 for msg in messages:
1626 message.append('* * *')
1626 message.append('* * *')
1627 message.extend(msg)
1627 message.extend(msg)
1628 message = '\n'.join(message)
1628 message = '\n'.join(message)
1629
1629
1630 if opts['edit']:
1630 if opts['edit']:
1631 message = ui.edit(message, user or ui.username())
1631 message = ui.edit(message, user or ui.username())
1632
1632
1633 q.refresh(repo, msg=message)
1633 q.refresh(repo, msg=message)
1634 q.delete(repo, patches, opts)
1634 q.delete(repo, patches, opts)
1635 q.save_dirty()
1635 q.save_dirty()
1636
1636
1637 def guard(ui, repo, *args, **opts):
1637 def guard(ui, repo, *args, **opts):
1638 '''set or print guards for a patch
1638 '''set or print guards for a patch
1639
1639
1640 Guards control whether a patch can be pushed. A patch with no
1640 Guards control whether a patch can be pushed. A patch with no
1641 guards is always pushed. A patch with a positive guard ("+foo") is
1641 guards is always pushed. A patch with a positive guard ("+foo") is
1642 pushed only if the qselect command has activated it. A patch with
1642 pushed only if the qselect command has activated it. A patch with
1643 a negative guard ("-foo") is never pushed if the qselect command
1643 a negative guard ("-foo") is never pushed if the qselect command
1644 has activated it.
1644 has activated it.
1645
1645
1646 With no arguments, print the currently active guards.
1646 With no arguments, print the currently active guards.
1647 With arguments, set guards for the named patch.
1647 With arguments, set guards for the named patch.
1648
1648
1649 To set a negative guard "-foo" on topmost patch ("--" is needed so
1649 To set a negative guard "-foo" on topmost patch ("--" is needed so
1650 hg will not interpret "-foo" as an option):
1650 hg will not interpret "-foo" as an option):
1651 hg qguard -- -foo
1651 hg qguard -- -foo
1652
1652
1653 To set guards on another patch:
1653 To set guards on another patch:
1654 hg qguard other.patch +2.6.17 -stable
1654 hg qguard other.patch +2.6.17 -stable
1655 '''
1655 '''
1656 def status(idx):
1656 def status(idx):
1657 guards = q.series_guards[idx] or ['unguarded']
1657 guards = q.series_guards[idx] or ['unguarded']
1658 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1658 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1659 q = repo.mq
1659 q = repo.mq
1660 patch = None
1660 patch = None
1661 args = list(args)
1661 args = list(args)
1662 if opts['list']:
1662 if opts['list']:
1663 if args or opts['none']:
1663 if args or opts['none']:
1664 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1664 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1665 for i in xrange(len(q.series)):
1665 for i in xrange(len(q.series)):
1666 status(i)
1666 status(i)
1667 return
1667 return
1668 if not args or args[0][0:1] in '-+':
1668 if not args or args[0][0:1] in '-+':
1669 if not q.applied:
1669 if not q.applied:
1670 raise util.Abort(_('no patches applied'))
1670 raise util.Abort(_('no patches applied'))
1671 patch = q.applied[-1].name
1671 patch = q.applied[-1].name
1672 if patch is None and args[0][0:1] not in '-+':
1672 if patch is None and args[0][0:1] not in '-+':
1673 patch = args.pop(0)
1673 patch = args.pop(0)
1674 if patch is None:
1674 if patch is None:
1675 raise util.Abort(_('no patch to work with'))
1675 raise util.Abort(_('no patch to work with'))
1676 if args or opts['none']:
1676 if args or opts['none']:
1677 q.set_guards(q.find_series(patch), args)
1677 q.set_guards(q.find_series(patch), args)
1678 q.save_dirty()
1678 q.save_dirty()
1679 else:
1679 else:
1680 status(q.series.index(q.lookup(patch)))
1680 status(q.series.index(q.lookup(patch)))
1681
1681
1682 def header(ui, repo, patch=None):
1682 def header(ui, repo, patch=None):
1683 """Print the header of the topmost or specified patch"""
1683 """Print the header of the topmost or specified patch"""
1684 q = repo.mq
1684 q = repo.mq
1685
1685
1686 if patch:
1686 if patch:
1687 patch = q.lookup(patch)
1687 patch = q.lookup(patch)
1688 else:
1688 else:
1689 if not q.applied:
1689 if not q.applied:
1690 ui.write('No patches applied\n')
1690 ui.write('No patches applied\n')
1691 return 1
1691 return 1
1692 patch = q.lookup('qtip')
1692 patch = q.lookup('qtip')
1693 message = repo.mq.readheaders(patch)[0]
1693 message = repo.mq.readheaders(patch)[0]
1694
1694
1695 ui.write('\n'.join(message) + '\n')
1695 ui.write('\n'.join(message) + '\n')
1696
1696
1697 def lastsavename(path):
1697 def lastsavename(path):
1698 (directory, base) = os.path.split(path)
1698 (directory, base) = os.path.split(path)
1699 names = os.listdir(directory)
1699 names = os.listdir(directory)
1700 namere = re.compile("%s.([0-9]+)" % base)
1700 namere = re.compile("%s.([0-9]+)" % base)
1701 maxindex = None
1701 maxindex = None
1702 maxname = None
1702 maxname = None
1703 for f in names:
1703 for f in names:
1704 m = namere.match(f)
1704 m = namere.match(f)
1705 if m:
1705 if m:
1706 index = int(m.group(1))
1706 index = int(m.group(1))
1707 if maxindex == None or index > maxindex:
1707 if maxindex == None or index > maxindex:
1708 maxindex = index
1708 maxindex = index
1709 maxname = f
1709 maxname = f
1710 if maxname:
1710 if maxname:
1711 return (os.path.join(directory, maxname), maxindex)
1711 return (os.path.join(directory, maxname), maxindex)
1712 return (None, None)
1712 return (None, None)
1713
1713
1714 def savename(path):
1714 def savename(path):
1715 (last, index) = lastsavename(path)
1715 (last, index) = lastsavename(path)
1716 if last is None:
1716 if last is None:
1717 index = 0
1717 index = 0
1718 newpath = path + ".%d" % (index + 1)
1718 newpath = path + ".%d" % (index + 1)
1719 return newpath
1719 return newpath
1720
1720
1721 def push(ui, repo, patch=None, **opts):
1721 def push(ui, repo, patch=None, **opts):
1722 """push the next patch onto the stack"""
1722 """push the next patch onto the stack"""
1723 q = repo.mq
1723 q = repo.mq
1724 mergeq = None
1724 mergeq = None
1725
1725
1726 if opts['all']:
1726 if opts['all']:
1727 patch = q.series[-1]
1727 patch = q.series[-1]
1728 if opts['merge']:
1728 if opts['merge']:
1729 if opts['name']:
1729 if opts['name']:
1730 newpath = opts['name']
1730 newpath = opts['name']
1731 else:
1731 else:
1732 newpath, i = lastsavename(q.path)
1732 newpath, i = lastsavename(q.path)
1733 if not newpath:
1733 if not newpath:
1734 ui.warn("no saved queues found, please use -n\n")
1734 ui.warn("no saved queues found, please use -n\n")
1735 return 1
1735 return 1
1736 mergeq = queue(ui, repo.join(""), newpath)
1736 mergeq = queue(ui, repo.join(""), newpath)
1737 ui.warn("merging with queue at: %s\n" % mergeq.path)
1737 ui.warn("merging with queue at: %s\n" % mergeq.path)
1738 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1738 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1739 mergeq=mergeq)
1739 mergeq=mergeq)
1740 q.save_dirty()
1740 q.save_dirty()
1741 return ret
1741 return ret
1742
1742
1743 def pop(ui, repo, patch=None, **opts):
1743 def pop(ui, repo, patch=None, **opts):
1744 """pop the current patch off the stack"""
1744 """pop the current patch off the stack"""
1745 localupdate = True
1745 localupdate = True
1746 if opts['name']:
1746 if opts['name']:
1747 q = queue(ui, repo.join(""), repo.join(opts['name']))
1747 q = queue(ui, repo.join(""), repo.join(opts['name']))
1748 ui.warn('using patch queue: %s\n' % q.path)
1748 ui.warn('using patch queue: %s\n' % q.path)
1749 localupdate = False
1749 localupdate = False
1750 else:
1750 else:
1751 q = repo.mq
1751 q = repo.mq
1752 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1752 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1753 q.save_dirty()
1753 q.save_dirty()
1754 return 0
1754 return 0
1755
1755
1756 def rename(ui, repo, patch, name=None, **opts):
1756 def rename(ui, repo, patch, name=None, **opts):
1757 """rename a patch
1757 """rename a patch
1758
1758
1759 With one argument, renames the current patch to PATCH1.
1759 With one argument, renames the current patch to PATCH1.
1760 With two arguments, renames PATCH1 to PATCH2."""
1760 With two arguments, renames PATCH1 to PATCH2."""
1761
1761
1762 q = repo.mq
1762 q = repo.mq
1763
1763
1764 if not name:
1764 if not name:
1765 name = patch
1765 name = patch
1766 patch = None
1766 patch = None
1767
1767
1768 if patch:
1768 if patch:
1769 patch = q.lookup(patch)
1769 patch = q.lookup(patch)
1770 else:
1770 else:
1771 if not q.applied:
1771 if not q.applied:
1772 ui.write(_('No patches applied\n'))
1772 ui.write(_('No patches applied\n'))
1773 return
1773 return
1774 patch = q.lookup('qtip')
1774 patch = q.lookup('qtip')
1775 absdest = q.join(name)
1775 absdest = q.join(name)
1776 if os.path.isdir(absdest):
1776 if os.path.isdir(absdest):
1777 name = os.path.join(name, os.path.basename(patch))
1777 name = os.path.join(name, os.path.basename(patch))
1778 absdest = q.join(name)
1778 absdest = q.join(name)
1779 if os.path.exists(absdest):
1779 if os.path.exists(absdest):
1780 raise util.Abort(_('%s already exists') % absdest)
1780 raise util.Abort(_('%s already exists') % absdest)
1781
1781
1782 if name in q.series:
1782 if name in q.series:
1783 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1783 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1784
1784
1785 if ui.verbose:
1785 if ui.verbose:
1786 ui.write('Renaming %s to %s\n' % (patch, name))
1786 ui.write('Renaming %s to %s\n' % (patch, name))
1787 i = q.find_series(patch)
1787 i = q.find_series(patch)
1788 q.full_series[i] = name
1788 q.full_series[i] = name
1789 q.parse_series()
1789 q.parse_series()
1790 q.series_dirty = 1
1790 q.series_dirty = 1
1791
1791
1792 info = q.isapplied(patch)
1792 info = q.isapplied(patch)
1793 if info:
1793 if info:
1794 q.applied[info[0]] = statusentry(info[1], name)
1794 q.applied[info[0]] = statusentry(info[1], name)
1795 q.applied_dirty = 1
1795 q.applied_dirty = 1
1796
1796
1797 util.rename(q.join(patch), absdest)
1797 util.rename(q.join(patch), absdest)
1798 r = q.qrepo()
1798 r = q.qrepo()
1799 if r:
1799 if r:
1800 wlock = r.wlock()
1800 wlock = r.wlock()
1801 if r.dirstate.state(name) == 'r':
1801 if r.dirstate.state(name) == 'r':
1802 r.undelete([name], wlock)
1802 r.undelete([name], wlock)
1803 r.copy(patch, name, wlock)
1803 r.copy(patch, name, wlock)
1804 r.remove([patch], False, wlock)
1804 r.remove([patch], False, wlock)
1805
1805
1806 q.save_dirty()
1806 q.save_dirty()
1807
1807
1808 def restore(ui, repo, rev, **opts):
1808 def restore(ui, repo, rev, **opts):
1809 """restore the queue state saved by a rev"""
1809 """restore the queue state saved by a rev"""
1810 rev = repo.lookup(rev)
1810 rev = repo.lookup(rev)
1811 q = repo.mq
1811 q = repo.mq
1812 q.restore(repo, rev, delete=opts['delete'],
1812 q.restore(repo, rev, delete=opts['delete'],
1813 qupdate=opts['update'])
1813 qupdate=opts['update'])
1814 q.save_dirty()
1814 q.save_dirty()
1815 return 0
1815 return 0
1816
1816
1817 def save(ui, repo, **opts):
1817 def save(ui, repo, **opts):
1818 """save current queue state"""
1818 """save current queue state"""
1819 q = repo.mq
1819 q = repo.mq
1820 message = commands.logmessage(opts)
1820 message = commands.logmessage(opts)
1821 ret = q.save(repo, msg=message)
1821 ret = q.save(repo, msg=message)
1822 if ret:
1822 if ret:
1823 return ret
1823 return ret
1824 q.save_dirty()
1824 q.save_dirty()
1825 if opts['copy']:
1825 if opts['copy']:
1826 path = q.path
1826 path = q.path
1827 if opts['name']:
1827 if opts['name']:
1828 newpath = os.path.join(q.basepath, opts['name'])
1828 newpath = os.path.join(q.basepath, opts['name'])
1829 if os.path.exists(newpath):
1829 if os.path.exists(newpath):
1830 if not os.path.isdir(newpath):
1830 if not os.path.isdir(newpath):
1831 raise util.Abort(_('destination %s exists and is not '
1831 raise util.Abort(_('destination %s exists and is not '
1832 'a directory') % newpath)
1832 'a directory') % newpath)
1833 if not opts['force']:
1833 if not opts['force']:
1834 raise util.Abort(_('destination %s exists, '
1834 raise util.Abort(_('destination %s exists, '
1835 'use -f to force') % newpath)
1835 'use -f to force') % newpath)
1836 else:
1836 else:
1837 newpath = savename(path)
1837 newpath = savename(path)
1838 ui.warn("copy %s to %s\n" % (path, newpath))
1838 ui.warn("copy %s to %s\n" % (path, newpath))
1839 util.copyfiles(path, newpath)
1839 util.copyfiles(path, newpath)
1840 if opts['empty']:
1840 if opts['empty']:
1841 try:
1841 try:
1842 os.unlink(q.join(q.status_path))
1842 os.unlink(q.join(q.status_path))
1843 except:
1843 except:
1844 pass
1844 pass
1845 return 0
1845 return 0
1846
1846
1847 def strip(ui, repo, rev, **opts):
1847 def strip(ui, repo, rev, **opts):
1848 """strip a revision and all later revs on the same branch"""
1848 """strip a revision and all later revs on the same branch"""
1849 rev = repo.lookup(rev)
1849 rev = repo.lookup(rev)
1850 backup = 'all'
1850 backup = 'all'
1851 if opts['backup']:
1851 if opts['backup']:
1852 backup = 'strip'
1852 backup = 'strip'
1853 elif opts['nobackup']:
1853 elif opts['nobackup']:
1854 backup = 'none'
1854 backup = 'none'
1855 update = repo.dirstate.parents()[0] != revlog.nullid
1855 update = repo.dirstate.parents()[0] != revlog.nullid
1856 repo.mq.strip(repo, rev, backup=backup, update=update)
1856 repo.mq.strip(repo, rev, backup=backup, update=update)
1857 return 0
1857 return 0
1858
1858
1859 def select(ui, repo, *args, **opts):
1859 def select(ui, repo, *args, **opts):
1860 '''set or print guarded patches to push
1860 '''set or print guarded patches to push
1861
1861
1862 Use the qguard command to set or print guards on patch, then use
1862 Use the qguard command to set or print guards on patch, then use
1863 qselect to tell mq which guards to use. A patch will be pushed if it
1863 qselect to tell mq which guards to use. A patch will be pushed if it
1864 has no guards or any positive guards match the currently selected guard,
1864 has no guards or any positive guards match the currently selected guard,
1865 but will not be pushed if any negative guards match the current guard.
1865 but will not be pushed if any negative guards match the current guard.
1866 For example:
1866 For example:
1867
1867
1868 qguard foo.patch -stable (negative guard)
1868 qguard foo.patch -stable (negative guard)
1869 qguard bar.patch +stable (positive guard)
1869 qguard bar.patch +stable (positive guard)
1870 qselect stable
1870 qselect stable
1871
1871
1872 This activates the "stable" guard. mq will skip foo.patch (because
1872 This activates the "stable" guard. mq will skip foo.patch (because
1873 it has a negative match) but push bar.patch (because it
1873 it has a negative match) but push bar.patch (because it
1874 has a positive match).
1874 has a positive match).
1875
1875
1876 With no arguments, prints the currently active guards.
1876 With no arguments, prints the currently active guards.
1877 With one argument, sets the active guard.
1877 With one argument, sets the active guard.
1878
1878
1879 Use -n/--none to deactivate guards (no other arguments needed).
1879 Use -n/--none to deactivate guards (no other arguments needed).
1880 When no guards are active, patches with positive guards are skipped
1880 When no guards are active, patches with positive guards are skipped
1881 and patches with negative guards are pushed.
1881 and patches with negative guards are pushed.
1882
1882
1883 qselect can change the guards on applied patches. It does not pop
1883 qselect can change the guards on applied patches. It does not pop
1884 guarded patches by default. Use --pop to pop back to the last applied
1884 guarded patches by default. Use --pop to pop back to the last applied
1885 patch that is not guarded. Use --reapply (which implies --pop) to push
1885 patch that is not guarded. Use --reapply (which implies --pop) to push
1886 back to the current patch afterwards, but skip guarded patches.
1886 back to the current patch afterwards, but skip guarded patches.
1887
1887
1888 Use -s/--series to print a list of all guards in the series file (no
1888 Use -s/--series to print a list of all guards in the series file (no
1889 other arguments needed). Use -v for more information.'''
1889 other arguments needed). Use -v for more information.'''
1890
1890
1891 q = repo.mq
1891 q = repo.mq
1892 guards = q.active()
1892 guards = q.active()
1893 if args or opts['none']:
1893 if args or opts['none']:
1894 old_unapplied = q.unapplied(repo)
1894 old_unapplied = q.unapplied(repo)
1895 old_guarded = [i for i in xrange(len(q.applied)) if
1895 old_guarded = [i for i in xrange(len(q.applied)) if
1896 not q.pushable(i)[0]]
1896 not q.pushable(i)[0]]
1897 q.set_active(args)
1897 q.set_active(args)
1898 q.save_dirty()
1898 q.save_dirty()
1899 if not args:
1899 if not args:
1900 ui.status(_('guards deactivated\n'))
1900 ui.status(_('guards deactivated\n'))
1901 if not opts['pop'] and not opts['reapply']:
1901 if not opts['pop'] and not opts['reapply']:
1902 unapplied = q.unapplied(repo)
1902 unapplied = q.unapplied(repo)
1903 guarded = [i for i in xrange(len(q.applied))
1903 guarded = [i for i in xrange(len(q.applied))
1904 if not q.pushable(i)[0]]
1904 if not q.pushable(i)[0]]
1905 if len(unapplied) != len(old_unapplied):
1905 if len(unapplied) != len(old_unapplied):
1906 ui.status(_('number of unguarded, unapplied patches has '
1906 ui.status(_('number of unguarded, unapplied patches has '
1907 'changed from %d to %d\n') %
1907 'changed from %d to %d\n') %
1908 (len(old_unapplied), len(unapplied)))
1908 (len(old_unapplied), len(unapplied)))
1909 if len(guarded) != len(old_guarded):
1909 if len(guarded) != len(old_guarded):
1910 ui.status(_('number of guarded, applied patches has changed '
1910 ui.status(_('number of guarded, applied patches has changed '
1911 'from %d to %d\n') %
1911 'from %d to %d\n') %
1912 (len(old_guarded), len(guarded)))
1912 (len(old_guarded), len(guarded)))
1913 elif opts['series']:
1913 elif opts['series']:
1914 guards = {}
1914 guards = {}
1915 noguards = 0
1915 noguards = 0
1916 for gs in q.series_guards:
1916 for gs in q.series_guards:
1917 if not gs:
1917 if not gs:
1918 noguards += 1
1918 noguards += 1
1919 for g in gs:
1919 for g in gs:
1920 guards.setdefault(g, 0)
1920 guards.setdefault(g, 0)
1921 guards[g] += 1
1921 guards[g] += 1
1922 if ui.verbose:
1922 if ui.verbose:
1923 guards['NONE'] = noguards
1923 guards['NONE'] = noguards
1924 guards = guards.items()
1924 guards = guards.items()
1925 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1925 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1926 if guards:
1926 if guards:
1927 ui.note(_('guards in series file:\n'))
1927 ui.note(_('guards in series file:\n'))
1928 for guard, count in guards:
1928 for guard, count in guards:
1929 ui.note('%2d ' % count)
1929 ui.note('%2d ' % count)
1930 ui.write(guard, '\n')
1930 ui.write(guard, '\n')
1931 else:
1931 else:
1932 ui.note(_('no guards in series file\n'))
1932 ui.note(_('no guards in series file\n'))
1933 else:
1933 else:
1934 if guards:
1934 if guards:
1935 ui.note(_('active guards:\n'))
1935 ui.note(_('active guards:\n'))
1936 for g in guards:
1936 for g in guards:
1937 ui.write(g, '\n')
1937 ui.write(g, '\n')
1938 else:
1938 else:
1939 ui.write(_('no active guards\n'))
1939 ui.write(_('no active guards\n'))
1940 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1940 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1941 popped = False
1941 popped = False
1942 if opts['pop'] or opts['reapply']:
1942 if opts['pop'] or opts['reapply']:
1943 for i in xrange(len(q.applied)):
1943 for i in xrange(len(q.applied)):
1944 pushable, reason = q.pushable(i)
1944 pushable, reason = q.pushable(i)
1945 if not pushable:
1945 if not pushable:
1946 ui.status(_('popping guarded patches\n'))
1946 ui.status(_('popping guarded patches\n'))
1947 popped = True
1947 popped = True
1948 if i == 0:
1948 if i == 0:
1949 q.pop(repo, all=True)
1949 q.pop(repo, all=True)
1950 else:
1950 else:
1951 q.pop(repo, i-1)
1951 q.pop(repo, i-1)
1952 break
1952 break
1953 if popped:
1953 if popped:
1954 try:
1954 try:
1955 if reapply:
1955 if reapply:
1956 ui.status(_('reapplying unguarded patches\n'))
1956 ui.status(_('reapplying unguarded patches\n'))
1957 q.push(repo, reapply)
1957 q.push(repo, reapply)
1958 finally:
1958 finally:
1959 q.save_dirty()
1959 q.save_dirty()
1960
1960
1961 def reposetup(ui, repo):
1961 def reposetup(ui, repo):
1962 class mqrepo(repo.__class__):
1962 class mqrepo(repo.__class__):
1963 def abort_if_wdir_patched(self, errmsg, force=False):
1963 def abort_if_wdir_patched(self, errmsg, force=False):
1964 if self.mq.applied and not force:
1964 if self.mq.applied and not force:
1965 parent = revlog.hex(self.dirstate.parents()[0])
1965 parent = revlog.hex(self.dirstate.parents()[0])
1966 if parent in [s.rev for s in self.mq.applied]:
1966 if parent in [s.rev for s in self.mq.applied]:
1967 raise util.Abort(errmsg)
1967 raise util.Abort(errmsg)
1968
1968
1969 def commit(self, *args, **opts):
1969 def commit(self, *args, **opts):
1970 if len(args) >= 6:
1970 if len(args) >= 6:
1971 force = args[5]
1971 force = args[5]
1972 else:
1972 else:
1973 force = opts.get('force')
1973 force = opts.get('force')
1974 self.abort_if_wdir_patched(
1974 self.abort_if_wdir_patched(
1975 _('cannot commit over an applied mq patch'),
1975 _('cannot commit over an applied mq patch'),
1976 force)
1976 force)
1977
1977
1978 return super(mqrepo, self).commit(*args, **opts)
1978 return super(mqrepo, self).commit(*args, **opts)
1979
1979
1980 def push(self, remote, force=False, revs=None):
1980 def push(self, remote, force=False, revs=None):
1981 if self.mq.applied and not force:
1981 if self.mq.applied and not force:
1982 raise util.Abort(_('source has mq patches applied'))
1982 raise util.Abort(_('source has mq patches applied'))
1983 return super(mqrepo, self).push(remote, force, revs)
1983 return super(mqrepo, self).push(remote, force, revs)
1984
1984
1985 def tags(self):
1985 def tags(self):
1986 if self.tagscache:
1986 if self.tagscache:
1987 return self.tagscache
1987 return self.tagscache
1988
1988
1989 tagscache = super(mqrepo, self).tags()
1989 tagscache = super(mqrepo, self).tags()
1990
1990
1991 q = self.mq
1991 q = self.mq
1992 if not q.applied:
1992 if not q.applied:
1993 return tagscache
1993 return tagscache
1994
1994
1995 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1995 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1996 mqtags.append((mqtags[-1][0], 'qtip'))
1996 mqtags.append((mqtags[-1][0], 'qtip'))
1997 mqtags.append((mqtags[0][0], 'qbase'))
1997 mqtags.append((mqtags[0][0], 'qbase'))
1998 for patch in mqtags:
1998 for patch in mqtags:
1999 if patch[1] in tagscache:
1999 if patch[1] in tagscache:
2000 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2000 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2001 else:
2001 else:
2002 tagscache[patch[1]] = revlog.bin(patch[0])
2002 tagscache[patch[1]] = revlog.bin(patch[0])
2003
2003
2004 return tagscache
2004 return tagscache
2005
2005
2006 def branchtags(self):
2006 def branchtags(self):
2007 if self.branchcache != None:
2007 if self.branchcache != None:
2008 return self.branchcache
2008 return self.branchcache
2009
2009
2010 q = self.mq
2010 q = self.mq
2011 if not q.applied:
2011 if not q.applied:
2012 return super(mqrepo, self).branchtags()
2012 return super(mqrepo, self).branchtags()
2013
2013
2014 self.branchcache = {} # avoid recursion in changectx
2014 self.branchcache = {} # avoid recursion in changectx
2015 cl = self.changelog
2015 cl = self.changelog
2016 partial, last, lrev = self._readbranchcache()
2016 partial, last, lrev = self._readbranchcache()
2017
2017
2018 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2018 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2019 start = lrev + 1
2019 start = lrev + 1
2020 if start < qbase:
2020 if start < qbase:
2021 # update the cache (excluding the patches) and save it
2021 # update the cache (excluding the patches) and save it
2022 self._updatebranchcache(partial, lrev+1, qbase)
2022 self._updatebranchcache(partial, lrev+1, qbase)
2023 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2023 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2024 start = qbase
2024 start = qbase
2025 # if start = qbase, the cache is as updated as it should be.
2025 # if start = qbase, the cache is as updated as it should be.
2026 # if start > qbase, the cache includes (part of) the patches.
2026 # if start > qbase, the cache includes (part of) the patches.
2027 # we might as well use it, but we won't save it.
2027 # we might as well use it, but we won't save it.
2028
2028
2029 # update the cache up to the tip
2029 # update the cache up to the tip
2030 self._updatebranchcache(partial, start, cl.count())
2030 self._updatebranchcache(partial, start, cl.count())
2031
2031
2032 self.branchcache = partial
2032 self.branchcache = partial
2033 return self.branchcache
2033 return self.branchcache
2034
2034
2035 if repo.local():
2035 if repo.local():
2036 repo.__class__ = mqrepo
2036 repo.__class__ = mqrepo
2037 repo.mq = queue(ui, repo.join(""))
2037 repo.mq = queue(ui, repo.join(""))
2038
2038
2039 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2039 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2040
2040
2041 cmdtable = {
2041 cmdtable = {
2042 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2042 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2043 "qclone": (clone,
2043 "qclone": (clone,
2044 [('', 'pull', None, _('use pull protocol to copy metadata')),
2044 [('', 'pull', None, _('use pull protocol to copy metadata')),
2045 ('U', 'noupdate', None, _('do not update the new working directories')),
2045 ('U', 'noupdate', None, _('do not update the new working directories')),
2046 ('', 'uncompressed', None,
2046 ('', 'uncompressed', None,
2047 _('use uncompressed transfer (fast over LAN)')),
2047 _('use uncompressed transfer (fast over LAN)')),
2048 ('e', 'ssh', '', _('specify ssh command to use')),
2048 ('e', 'ssh', '', _('specify ssh command to use')),
2049 ('p', 'patches', '', _('location of source patch repo')),
2049 ('p', 'patches', '', _('location of source patch repo')),
2050 ('', 'remotecmd', '',
2050 ('', 'remotecmd', '',
2051 _('specify hg command to run on the remote side'))],
2051 _('specify hg command to run on the remote side'))],
2052 'hg qclone [OPTION]... SOURCE [DEST]'),
2052 'hg qclone [OPTION]... SOURCE [DEST]'),
2053 "qcommit|qci":
2053 "qcommit|qci":
2054 (commit,
2054 (commit,
2055 commands.table["^commit|ci"][1],
2055 commands.table["^commit|ci"][1],
2056 'hg qcommit [OPTION]... [FILE]...'),
2056 'hg qcommit [OPTION]... [FILE]...'),
2057 "^qdiff": (diff,
2057 "^qdiff": (diff,
2058 [('I', 'include', [], _('include names matching the given patterns')),
2058 [('I', 'include', [], _('include names matching the given patterns')),
2059 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2059 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2060 'hg qdiff [-I] [-X] [FILE]...'),
2060 'hg qdiff [-I] [-X] [FILE]...'),
2061 "qdelete|qremove|qrm":
2061 "qdelete|qremove|qrm":
2062 (delete,
2062 (delete,
2063 [('k', 'keep', None, _('keep patch file')),
2063 [('k', 'keep', None, _('keep patch file')),
2064 ('r', 'rev', [], _('stop managing a revision'))],
2064 ('r', 'rev', [], _('stop managing a revision'))],
2065 'hg qdelete [-k] [-r REV]... PATCH...'),
2065 'hg qdelete [-k] [-r REV]... PATCH...'),
2066 'qfold':
2066 'qfold':
2067 (fold,
2067 (fold,
2068 [('e', 'edit', None, _('edit patch header')),
2068 [('e', 'edit', None, _('edit patch header')),
2069 ('k', 'keep', None, _('keep folded patch files')),
2069 ('k', 'keep', None, _('keep folded patch files')),
2070 ('m', 'message', '', _('set patch header to <text>')),
2070 ('m', 'message', '', _('set patch header to <text>')),
2071 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
2071 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
2072 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2072 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2073 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2073 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2074 ('n', 'none', None, _('drop all guards'))],
2074 ('n', 'none', None, _('drop all guards'))],
2075 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
2075 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
2076 'qheader': (header, [],
2076 'qheader': (header, [],
2077 _('hg qheader [PATCH]')),
2077 _('hg qheader [PATCH]')),
2078 "^qimport":
2078 "^qimport":
2079 (qimport,
2079 (qimport,
2080 [('e', 'existing', None, 'import file in patch dir'),
2080 [('e', 'existing', None, 'import file in patch dir'),
2081 ('n', 'name', '', 'patch file name'),
2081 ('n', 'name', '', 'patch file name'),
2082 ('f', 'force', None, 'overwrite existing files'),
2082 ('f', 'force', None, 'overwrite existing files'),
2083 ('r', 'rev', [], 'place existing revisions under mq control')],
2083 ('r', 'rev', [], 'place existing revisions under mq control')],
2084 'hg qimport [-e] [-n NAME] [-f] [-r REV]... FILE...'),
2084 'hg qimport [-e] [-n NAME] [-f] [-r REV]... FILE...'),
2085 "^qinit":
2085 "^qinit":
2086 (init,
2086 (init,
2087 [('c', 'create-repo', None, 'create queue repository')],
2087 [('c', 'create-repo', None, 'create queue repository')],
2088 'hg qinit [-c]'),
2088 'hg qinit [-c]'),
2089 "qnew":
2089 "qnew":
2090 (new,
2090 (new,
2091 [('e', 'edit', None, _('edit commit message')),
2091 [('e', 'edit', None, _('edit commit message')),
2092 ('m', 'message', '', _('use <text> as commit message')),
2092 ('m', 'message', '', _('use <text> as commit message')),
2093 ('l', 'logfile', '', _('read the commit message from <file>')),
2093 ('l', 'logfile', '', _('read the commit message from <file>')),
2094 ('f', 'force', None, _('import uncommitted changes into patch'))],
2094 ('f', 'force', None, _('import uncommitted changes into patch'))],
2095 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2095 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2096 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2096 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2097 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2097 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2098 "^qpop":
2098 "^qpop":
2099 (pop,
2099 (pop,
2100 [('a', 'all', None, 'pop all patches'),
2100 [('a', 'all', None, 'pop all patches'),
2101 ('n', 'name', '', 'queue name to pop'),
2101 ('n', 'name', '', 'queue name to pop'),
2102 ('f', 'force', None, 'forget any local changes')],
2102 ('f', 'force', None, 'forget any local changes')],
2103 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2103 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2104 "^qpush":
2104 "^qpush":
2105 (push,
2105 (push,
2106 [('f', 'force', None, 'apply if the patch has rejects'),
2106 [('f', 'force', None, 'apply if the patch has rejects'),
2107 ('l', 'list', None, 'list patch name in commit text'),
2107 ('l', 'list', None, 'list patch name in commit text'),
2108 ('a', 'all', None, 'apply all patches'),
2108 ('a', 'all', None, 'apply all patches'),
2109 ('m', 'merge', None, 'merge from another queue'),
2109 ('m', 'merge', None, 'merge from another queue'),
2110 ('n', 'name', '', 'merge queue name')],
2110 ('n', 'name', '', 'merge queue name')],
2111 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2111 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2112 "^qrefresh":
2112 "^qrefresh":
2113 (refresh,
2113 (refresh,
2114 [('e', 'edit', None, _('edit commit message')),
2114 [('e', 'edit', None, _('edit commit message')),
2115 ('m', 'message', '', _('change commit message with <text>')),
2115 ('m', 'message', '', _('change commit message with <text>')),
2116 ('l', 'logfile', '', _('change commit message with <file> content')),
2116 ('l', 'logfile', '', _('change commit message with <file> content')),
2117 ('g', 'git', None, _('use git extended diff format')),
2117 ('g', 'git', None, _('use git extended diff format')),
2118 ('s', 'short', None, 'short refresh'),
2118 ('s', 'short', None, 'short refresh'),
2119 ('I', 'include', [], _('include names matching the given patterns')),
2119 ('I', 'include', [], _('include names matching the given patterns')),
2120 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2120 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2121 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
2121 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
2122 'qrename|qmv':
2122 'qrename|qmv':
2123 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2123 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2124 "qrestore":
2124 "qrestore":
2125 (restore,
2125 (restore,
2126 [('d', 'delete', None, 'delete save entry'),
2126 [('d', 'delete', None, 'delete save entry'),
2127 ('u', 'update', None, 'update queue working dir')],
2127 ('u', 'update', None, 'update queue working dir')],
2128 'hg qrestore [-d] [-u] REV'),
2128 'hg qrestore [-d] [-u] REV'),
2129 "qsave":
2129 "qsave":
2130 (save,
2130 (save,
2131 [('m', 'message', '', _('use <text> as commit message')),
2131 [('m', 'message', '', _('use <text> as commit message')),
2132 ('l', 'logfile', '', _('read the commit message from <file>')),
2132 ('l', 'logfile', '', _('read the commit message from <file>')),
2133 ('c', 'copy', None, 'copy patch directory'),
2133 ('c', 'copy', None, 'copy patch directory'),
2134 ('n', 'name', '', 'copy directory name'),
2134 ('n', 'name', '', 'copy directory name'),
2135 ('e', 'empty', None, 'clear queue status file'),
2135 ('e', 'empty', None, 'clear queue status file'),
2136 ('f', 'force', None, 'force copy')],
2136 ('f', 'force', None, 'force copy')],
2137 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2137 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2138 "qselect": (select,
2138 "qselect": (select,
2139 [('n', 'none', None, _('disable all guards')),
2139 [('n', 'none', None, _('disable all guards')),
2140 ('s', 'series', None, _('list all guards in series file')),
2140 ('s', 'series', None, _('list all guards in series file')),
2141 ('', 'pop', None,
2141 ('', 'pop', None,
2142 _('pop to before first guarded applied patch')),
2142 _('pop to before first guarded applied patch')),
2143 ('', 'reapply', None, _('pop, then reapply patches'))],
2143 ('', 'reapply', None, _('pop, then reapply patches'))],
2144 'hg qselect [OPTION...] [GUARD...]'),
2144 'hg qselect [OPTION...] [GUARD...]'),
2145 "qseries":
2145 "qseries":
2146 (series,
2146 (series,
2147 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2147 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2148 'hg qseries [-ms]'),
2148 'hg qseries [-ms]'),
2149 "^strip":
2149 "^strip":
2150 (strip,
2150 (strip,
2151 [('f', 'force', None, 'force multi-head removal'),
2151 [('f', 'force', None, 'force multi-head removal'),
2152 ('b', 'backup', None, 'bundle unrelated changesets'),
2152 ('b', 'backup', None, 'bundle unrelated changesets'),
2153 ('n', 'nobackup', None, 'no backups')],
2153 ('n', 'nobackup', None, 'no backups')],
2154 'hg strip [-f] [-b] [-n] REV'),
2154 'hg strip [-f] [-b] [-n] REV'),
2155 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2155 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2156 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2156 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2157 }
2157 }
@@ -1,1024 +1,1026 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import gettext as _
15 from i18n import gettext as _
16 from demandload import *
16 from demandload import *
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 demandload(globals(), "os threading time calendar ConfigParser")
18 demandload(globals(), "os threading time calendar ConfigParser")
19
19
20 # used by parsedate
20 # used by parsedate
21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
22 '%a %b %d %H:%M:%S %Y')
22 '%a %b %d %H:%M:%S %Y')
23
23
24 class SignalInterrupt(Exception):
24 class SignalInterrupt(Exception):
25 """Exception raised on SIGTERM and SIGHUP."""
25 """Exception raised on SIGTERM and SIGHUP."""
26
26
27 # like SafeConfigParser but with case-sensitive keys
27 # like SafeConfigParser but with case-sensitive keys
28 class configparser(ConfigParser.SafeConfigParser):
28 class configparser(ConfigParser.SafeConfigParser):
29 def optionxform(self, optionstr):
29 def optionxform(self, optionstr):
30 return optionstr
30 return optionstr
31
31
32 def cachefunc(func):
32 def cachefunc(func):
33 '''cache the result of function calls'''
33 '''cache the result of function calls'''
34 # XXX doesn't handle keywords args
34 # XXX doesn't handle keywords args
35 cache = {}
35 cache = {}
36 if func.func_code.co_argcount == 1:
36 if func.func_code.co_argcount == 1:
37 # we gain a small amount of time because
37 # we gain a small amount of time because
38 # we don't need to pack/unpack the list
38 # we don't need to pack/unpack the list
39 def f(arg):
39 def f(arg):
40 if arg not in cache:
40 if arg not in cache:
41 cache[arg] = func(arg)
41 cache[arg] = func(arg)
42 return cache[arg]
42 return cache[arg]
43 else:
43 else:
44 def f(*args):
44 def f(*args):
45 if args not in cache:
45 if args not in cache:
46 cache[args] = func(*args)
46 cache[args] = func(*args)
47 return cache[args]
47 return cache[args]
48
48
49 return f
49 return f
50
50
51 def pipefilter(s, cmd):
51 def pipefilter(s, cmd):
52 '''filter string S through command CMD, returning its output'''
52 '''filter string S through command CMD, returning its output'''
53 (pout, pin) = popen2.popen2(cmd, -1, 'b')
53 (pout, pin) = popen2.popen2(cmd, -1, 'b')
54 def writer():
54 def writer():
55 try:
55 try:
56 pin.write(s)
56 pin.write(s)
57 pin.close()
57 pin.close()
58 except IOError, inst:
58 except IOError, inst:
59 if inst.errno != errno.EPIPE:
59 if inst.errno != errno.EPIPE:
60 raise
60 raise
61
61
62 # we should use select instead on UNIX, but this will work on most
62 # we should use select instead on UNIX, but this will work on most
63 # systems, including Windows
63 # systems, including Windows
64 w = threading.Thread(target=writer)
64 w = threading.Thread(target=writer)
65 w.start()
65 w.start()
66 f = pout.read()
66 f = pout.read()
67 pout.close()
67 pout.close()
68 w.join()
68 w.join()
69 return f
69 return f
70
70
71 def tempfilter(s, cmd):
71 def tempfilter(s, cmd):
72 '''filter string S through a pair of temporary files with CMD.
72 '''filter string S through a pair of temporary files with CMD.
73 CMD is used as a template to create the real command to be run,
73 CMD is used as a template to create the real command to be run,
74 with the strings INFILE and OUTFILE replaced by the real names of
74 with the strings INFILE and OUTFILE replaced by the real names of
75 the temporary files generated.'''
75 the temporary files generated.'''
76 inname, outname = None, None
76 inname, outname = None, None
77 try:
77 try:
78 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
78 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
79 fp = os.fdopen(infd, 'wb')
79 fp = os.fdopen(infd, 'wb')
80 fp.write(s)
80 fp.write(s)
81 fp.close()
81 fp.close()
82 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
82 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
83 os.close(outfd)
83 os.close(outfd)
84 cmd = cmd.replace('INFILE', inname)
84 cmd = cmd.replace('INFILE', inname)
85 cmd = cmd.replace('OUTFILE', outname)
85 cmd = cmd.replace('OUTFILE', outname)
86 code = os.system(cmd)
86 code = os.system(cmd)
87 if code: raise Abort(_("command '%s' failed: %s") %
87 if code: raise Abort(_("command '%s' failed: %s") %
88 (cmd, explain_exit(code)))
88 (cmd, explain_exit(code)))
89 return open(outname, 'rb').read()
89 return open(outname, 'rb').read()
90 finally:
90 finally:
91 try:
91 try:
92 if inname: os.unlink(inname)
92 if inname: os.unlink(inname)
93 except: pass
93 except: pass
94 try:
94 try:
95 if outname: os.unlink(outname)
95 if outname: os.unlink(outname)
96 except: pass
96 except: pass
97
97
98 filtertable = {
98 filtertable = {
99 'tempfile:': tempfilter,
99 'tempfile:': tempfilter,
100 'pipe:': pipefilter,
100 'pipe:': pipefilter,
101 }
101 }
102
102
103 def filter(s, cmd):
103 def filter(s, cmd):
104 "filter a string through a command that transforms its input to its output"
104 "filter a string through a command that transforms its input to its output"
105 for name, fn in filtertable.iteritems():
105 for name, fn in filtertable.iteritems():
106 if cmd.startswith(name):
106 if cmd.startswith(name):
107 return fn(s, cmd[len(name):].lstrip())
107 return fn(s, cmd[len(name):].lstrip())
108 return pipefilter(s, cmd)
108 return pipefilter(s, cmd)
109
109
110 def find_in_path(name, path, default=None):
110 def find_in_path(name, path, default=None):
111 '''find name in search path. path can be string (will be split
111 '''find name in search path. path can be string (will be split
112 with os.pathsep), or iterable thing that returns strings. if name
112 with os.pathsep), or iterable thing that returns strings. if name
113 found, return path to name. else return default.'''
113 found, return path to name. else return default.'''
114 if isinstance(path, str):
114 if isinstance(path, str):
115 path = path.split(os.pathsep)
115 path = path.split(os.pathsep)
116 for p in path:
116 for p in path:
117 p_name = os.path.join(p, name)
117 p_name = os.path.join(p, name)
118 if os.path.exists(p_name):
118 if os.path.exists(p_name):
119 return p_name
119 return p_name
120 return default
120 return default
121
121
122 def binary(s):
122 def binary(s):
123 """return true if a string is binary data using diff's heuristic"""
123 """return true if a string is binary data using diff's heuristic"""
124 if s and '\0' in s[:4096]:
124 if s and '\0' in s[:4096]:
125 return True
125 return True
126 return False
126 return False
127
127
128 def unique(g):
128 def unique(g):
129 """return the uniq elements of iterable g"""
129 """return the uniq elements of iterable g"""
130 seen = {}
130 seen = {}
131 l = []
131 for f in g:
132 for f in g:
132 if f not in seen:
133 if f not in seen:
133 seen[f] = 1
134 seen[f] = 1
134 yield f
135 l.append(f)
136 return l
135
137
136 class Abort(Exception):
138 class Abort(Exception):
137 """Raised if a command needs to print an error and exit."""
139 """Raised if a command needs to print an error and exit."""
138
140
139 def always(fn): return True
141 def always(fn): return True
140 def never(fn): return False
142 def never(fn): return False
141
143
142 def patkind(name, dflt_pat='glob'):
144 def patkind(name, dflt_pat='glob'):
143 """Split a string into an optional pattern kind prefix and the
145 """Split a string into an optional pattern kind prefix and the
144 actual pattern."""
146 actual pattern."""
145 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
147 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
146 if name.startswith(prefix + ':'): return name.split(':', 1)
148 if name.startswith(prefix + ':'): return name.split(':', 1)
147 return dflt_pat, name
149 return dflt_pat, name
148
150
149 def globre(pat, head='^', tail='$'):
151 def globre(pat, head='^', tail='$'):
150 "convert a glob pattern into a regexp"
152 "convert a glob pattern into a regexp"
151 i, n = 0, len(pat)
153 i, n = 0, len(pat)
152 res = ''
154 res = ''
153 group = False
155 group = False
154 def peek(): return i < n and pat[i]
156 def peek(): return i < n and pat[i]
155 while i < n:
157 while i < n:
156 c = pat[i]
158 c = pat[i]
157 i = i+1
159 i = i+1
158 if c == '*':
160 if c == '*':
159 if peek() == '*':
161 if peek() == '*':
160 i += 1
162 i += 1
161 res += '.*'
163 res += '.*'
162 else:
164 else:
163 res += '[^/]*'
165 res += '[^/]*'
164 elif c == '?':
166 elif c == '?':
165 res += '.'
167 res += '.'
166 elif c == '[':
168 elif c == '[':
167 j = i
169 j = i
168 if j < n and pat[j] in '!]':
170 if j < n and pat[j] in '!]':
169 j += 1
171 j += 1
170 while j < n and pat[j] != ']':
172 while j < n and pat[j] != ']':
171 j += 1
173 j += 1
172 if j >= n:
174 if j >= n:
173 res += '\\['
175 res += '\\['
174 else:
176 else:
175 stuff = pat[i:j].replace('\\','\\\\')
177 stuff = pat[i:j].replace('\\','\\\\')
176 i = j + 1
178 i = j + 1
177 if stuff[0] == '!':
179 if stuff[0] == '!':
178 stuff = '^' + stuff[1:]
180 stuff = '^' + stuff[1:]
179 elif stuff[0] == '^':
181 elif stuff[0] == '^':
180 stuff = '\\' + stuff
182 stuff = '\\' + stuff
181 res = '%s[%s]' % (res, stuff)
183 res = '%s[%s]' % (res, stuff)
182 elif c == '{':
184 elif c == '{':
183 group = True
185 group = True
184 res += '(?:'
186 res += '(?:'
185 elif c == '}' and group:
187 elif c == '}' and group:
186 res += ')'
188 res += ')'
187 group = False
189 group = False
188 elif c == ',' and group:
190 elif c == ',' and group:
189 res += '|'
191 res += '|'
190 elif c == '\\':
192 elif c == '\\':
191 p = peek()
193 p = peek()
192 if p:
194 if p:
193 i += 1
195 i += 1
194 res += re.escape(p)
196 res += re.escape(p)
195 else:
197 else:
196 res += re.escape(c)
198 res += re.escape(c)
197 else:
199 else:
198 res += re.escape(c)
200 res += re.escape(c)
199 return head + res + tail
201 return head + res + tail
200
202
201 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
203 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
202
204
203 def pathto(n1, n2):
205 def pathto(n1, n2):
204 '''return the relative path from one place to another.
206 '''return the relative path from one place to another.
205 this returns a path in the form used by the local filesystem, not hg.'''
207 this returns a path in the form used by the local filesystem, not hg.'''
206 if not n1: return localpath(n2)
208 if not n1: return localpath(n2)
207 a, b = n1.split('/'), n2.split('/')
209 a, b = n1.split('/'), n2.split('/')
208 a.reverse()
210 a.reverse()
209 b.reverse()
211 b.reverse()
210 while a and b and a[-1] == b[-1]:
212 while a and b and a[-1] == b[-1]:
211 a.pop()
213 a.pop()
212 b.pop()
214 b.pop()
213 b.reverse()
215 b.reverse()
214 return os.sep.join((['..'] * len(a)) + b)
216 return os.sep.join((['..'] * len(a)) + b)
215
217
216 def canonpath(root, cwd, myname):
218 def canonpath(root, cwd, myname):
217 """return the canonical path of myname, given cwd and root"""
219 """return the canonical path of myname, given cwd and root"""
218 if root == os.sep:
220 if root == os.sep:
219 rootsep = os.sep
221 rootsep = os.sep
220 elif root.endswith(os.sep):
222 elif root.endswith(os.sep):
221 rootsep = root
223 rootsep = root
222 else:
224 else:
223 rootsep = root + os.sep
225 rootsep = root + os.sep
224 name = myname
226 name = myname
225 if not os.path.isabs(name):
227 if not os.path.isabs(name):
226 name = os.path.join(root, cwd, name)
228 name = os.path.join(root, cwd, name)
227 name = os.path.normpath(name)
229 name = os.path.normpath(name)
228 if name != rootsep and name.startswith(rootsep):
230 if name != rootsep and name.startswith(rootsep):
229 name = name[len(rootsep):]
231 name = name[len(rootsep):]
230 audit_path(name)
232 audit_path(name)
231 return pconvert(name)
233 return pconvert(name)
232 elif name == root:
234 elif name == root:
233 return ''
235 return ''
234 else:
236 else:
235 # Determine whether `name' is in the hierarchy at or beneath `root',
237 # Determine whether `name' is in the hierarchy at or beneath `root',
236 # by iterating name=dirname(name) until that causes no change (can't
238 # by iterating name=dirname(name) until that causes no change (can't
237 # check name == '/', because that doesn't work on windows). For each
239 # check name == '/', because that doesn't work on windows). For each
238 # `name', compare dev/inode numbers. If they match, the list `rel'
240 # `name', compare dev/inode numbers. If they match, the list `rel'
239 # holds the reversed list of components making up the relative file
241 # holds the reversed list of components making up the relative file
240 # name we want.
242 # name we want.
241 root_st = os.stat(root)
243 root_st = os.stat(root)
242 rel = []
244 rel = []
243 while True:
245 while True:
244 try:
246 try:
245 name_st = os.stat(name)
247 name_st = os.stat(name)
246 except OSError:
248 except OSError:
247 break
249 break
248 if samestat(name_st, root_st):
250 if samestat(name_st, root_st):
249 rel.reverse()
251 rel.reverse()
250 name = os.path.join(*rel)
252 name = os.path.join(*rel)
251 audit_path(name)
253 audit_path(name)
252 return pconvert(name)
254 return pconvert(name)
253 dirname, basename = os.path.split(name)
255 dirname, basename = os.path.split(name)
254 rel.append(basename)
256 rel.append(basename)
255 if dirname == name:
257 if dirname == name:
256 break
258 break
257 name = dirname
259 name = dirname
258
260
259 raise Abort('%s not under root' % myname)
261 raise Abort('%s not under root' % myname)
260
262
261 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
263 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
262 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
264 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
263
265
264 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
266 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
265 if os.name == 'nt':
267 if os.name == 'nt':
266 dflt_pat = 'glob'
268 dflt_pat = 'glob'
267 else:
269 else:
268 dflt_pat = 'relpath'
270 dflt_pat = 'relpath'
269 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
271 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
270
272
271 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
273 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
272 """build a function to match a set of file patterns
274 """build a function to match a set of file patterns
273
275
274 arguments:
276 arguments:
275 canonroot - the canonical root of the tree you're matching against
277 canonroot - the canonical root of the tree you're matching against
276 cwd - the current working directory, if relevant
278 cwd - the current working directory, if relevant
277 names - patterns to find
279 names - patterns to find
278 inc - patterns to include
280 inc - patterns to include
279 exc - patterns to exclude
281 exc - patterns to exclude
280 head - a regex to prepend to patterns to control whether a match is rooted
282 head - a regex to prepend to patterns to control whether a match is rooted
281
283
282 a pattern is one of:
284 a pattern is one of:
283 'glob:<rooted glob>'
285 'glob:<rooted glob>'
284 're:<rooted regexp>'
286 're:<rooted regexp>'
285 'path:<rooted path>'
287 'path:<rooted path>'
286 'relglob:<relative glob>'
288 'relglob:<relative glob>'
287 'relpath:<relative path>'
289 'relpath:<relative path>'
288 'relre:<relative regexp>'
290 'relre:<relative regexp>'
289 '<rooted path or regexp>'
291 '<rooted path or regexp>'
290
292
291 returns:
293 returns:
292 a 3-tuple containing
294 a 3-tuple containing
293 - list of explicit non-pattern names passed in
295 - list of explicit non-pattern names passed in
294 - a bool match(filename) function
296 - a bool match(filename) function
295 - a bool indicating if any patterns were passed in
297 - a bool indicating if any patterns were passed in
296
298
297 todo:
299 todo:
298 make head regex a rooted bool
300 make head regex a rooted bool
299 """
301 """
300
302
301 def contains_glob(name):
303 def contains_glob(name):
302 for c in name:
304 for c in name:
303 if c in _globchars: return True
305 if c in _globchars: return True
304 return False
306 return False
305
307
306 def regex(kind, name, tail):
308 def regex(kind, name, tail):
307 '''convert a pattern into a regular expression'''
309 '''convert a pattern into a regular expression'''
308 if kind == 're':
310 if kind == 're':
309 return name
311 return name
310 elif kind == 'path':
312 elif kind == 'path':
311 return '^' + re.escape(name) + '(?:/|$)'
313 return '^' + re.escape(name) + '(?:/|$)'
312 elif kind == 'relglob':
314 elif kind == 'relglob':
313 return head + globre(name, '(?:|.*/)', tail)
315 return head + globre(name, '(?:|.*/)', tail)
314 elif kind == 'relpath':
316 elif kind == 'relpath':
315 return head + re.escape(name) + tail
317 return head + re.escape(name) + tail
316 elif kind == 'relre':
318 elif kind == 'relre':
317 if name.startswith('^'):
319 if name.startswith('^'):
318 return name
320 return name
319 return '.*' + name
321 return '.*' + name
320 return head + globre(name, '', tail)
322 return head + globre(name, '', tail)
321
323
322 def matchfn(pats, tail):
324 def matchfn(pats, tail):
323 """build a matching function from a set of patterns"""
325 """build a matching function from a set of patterns"""
324 if not pats:
326 if not pats:
325 return
327 return
326 matches = []
328 matches = []
327 for k, p in pats:
329 for k, p in pats:
328 try:
330 try:
329 pat = '(?:%s)' % regex(k, p, tail)
331 pat = '(?:%s)' % regex(k, p, tail)
330 matches.append(re.compile(pat).match)
332 matches.append(re.compile(pat).match)
331 except re.error:
333 except re.error:
332 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
334 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
333 else: raise Abort("invalid pattern (%s): %s" % (k, p))
335 else: raise Abort("invalid pattern (%s): %s" % (k, p))
334
336
335 def buildfn(text):
337 def buildfn(text):
336 for m in matches:
338 for m in matches:
337 r = m(text)
339 r = m(text)
338 if r:
340 if r:
339 return r
341 return r
340
342
341 return buildfn
343 return buildfn
342
344
343 def globprefix(pat):
345 def globprefix(pat):
344 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
346 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
345 root = []
347 root = []
346 for p in pat.split(os.sep):
348 for p in pat.split(os.sep):
347 if contains_glob(p): break
349 if contains_glob(p): break
348 root.append(p)
350 root.append(p)
349 return '/'.join(root)
351 return '/'.join(root)
350
352
351 pats = []
353 pats = []
352 files = []
354 files = []
353 roots = []
355 roots = []
354 for kind, name in [patkind(p, dflt_pat) for p in names]:
356 for kind, name in [patkind(p, dflt_pat) for p in names]:
355 if kind in ('glob', 'relpath'):
357 if kind in ('glob', 'relpath'):
356 name = canonpath(canonroot, cwd, name)
358 name = canonpath(canonroot, cwd, name)
357 if name == '':
359 if name == '':
358 kind, name = 'glob', '**'
360 kind, name = 'glob', '**'
359 if kind in ('glob', 'path', 're'):
361 if kind in ('glob', 'path', 're'):
360 pats.append((kind, name))
362 pats.append((kind, name))
361 if kind == 'glob':
363 if kind == 'glob':
362 root = globprefix(name)
364 root = globprefix(name)
363 if root: roots.append(root)
365 if root: roots.append(root)
364 elif kind == 'relpath':
366 elif kind == 'relpath':
365 files.append((kind, name))
367 files.append((kind, name))
366 roots.append(name)
368 roots.append(name)
367
369
368 patmatch = matchfn(pats, '$') or always
370 patmatch = matchfn(pats, '$') or always
369 filematch = matchfn(files, '(?:/|$)') or always
371 filematch = matchfn(files, '(?:/|$)') or always
370 incmatch = always
372 incmatch = always
371 if inc:
373 if inc:
372 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
374 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
373 incmatch = matchfn(inckinds, '(?:/|$)')
375 incmatch = matchfn(inckinds, '(?:/|$)')
374 excmatch = lambda fn: False
376 excmatch = lambda fn: False
375 if exc:
377 if exc:
376 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
378 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
377 excmatch = matchfn(exckinds, '(?:/|$)')
379 excmatch = matchfn(exckinds, '(?:/|$)')
378
380
379 return (roots,
381 return (roots,
380 lambda fn: (incmatch(fn) and not excmatch(fn) and
382 lambda fn: (incmatch(fn) and not excmatch(fn) and
381 (fn.endswith('/') or
383 (fn.endswith('/') or
382 (not pats and not files) or
384 (not pats and not files) or
383 (pats and patmatch(fn)) or
385 (pats and patmatch(fn)) or
384 (files and filematch(fn)))),
386 (files and filematch(fn)))),
385 (inc or exc or (pats and pats != [('glob', '**')])) and True)
387 (inc or exc or (pats and pats != [('glob', '**')])) and True)
386
388
387 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
389 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
388 '''enhanced shell command execution.
390 '''enhanced shell command execution.
389 run with environment maybe modified, maybe in different dir.
391 run with environment maybe modified, maybe in different dir.
390
392
391 if command fails and onerr is None, return status. if ui object,
393 if command fails and onerr is None, return status. if ui object,
392 print error message and return status, else raise onerr object as
394 print error message and return status, else raise onerr object as
393 exception.'''
395 exception.'''
394 def py2shell(val):
396 def py2shell(val):
395 'convert python object into string that is useful to shell'
397 'convert python object into string that is useful to shell'
396 if val in (None, False):
398 if val in (None, False):
397 return '0'
399 return '0'
398 if val == True:
400 if val == True:
399 return '1'
401 return '1'
400 return str(val)
402 return str(val)
401 oldenv = {}
403 oldenv = {}
402 for k in environ:
404 for k in environ:
403 oldenv[k] = os.environ.get(k)
405 oldenv[k] = os.environ.get(k)
404 if cwd is not None:
406 if cwd is not None:
405 oldcwd = os.getcwd()
407 oldcwd = os.getcwd()
406 try:
408 try:
407 for k, v in environ.iteritems():
409 for k, v in environ.iteritems():
408 os.environ[k] = py2shell(v)
410 os.environ[k] = py2shell(v)
409 if cwd is not None and oldcwd != cwd:
411 if cwd is not None and oldcwd != cwd:
410 os.chdir(cwd)
412 os.chdir(cwd)
411 rc = os.system(cmd)
413 rc = os.system(cmd)
412 if rc and onerr:
414 if rc and onerr:
413 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
415 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
414 explain_exit(rc)[0])
416 explain_exit(rc)[0])
415 if errprefix:
417 if errprefix:
416 errmsg = '%s: %s' % (errprefix, errmsg)
418 errmsg = '%s: %s' % (errprefix, errmsg)
417 try:
419 try:
418 onerr.warn(errmsg + '\n')
420 onerr.warn(errmsg + '\n')
419 except AttributeError:
421 except AttributeError:
420 raise onerr(errmsg)
422 raise onerr(errmsg)
421 return rc
423 return rc
422 finally:
424 finally:
423 for k, v in oldenv.iteritems():
425 for k, v in oldenv.iteritems():
424 if v is None:
426 if v is None:
425 del os.environ[k]
427 del os.environ[k]
426 else:
428 else:
427 os.environ[k] = v
429 os.environ[k] = v
428 if cwd is not None and oldcwd != cwd:
430 if cwd is not None and oldcwd != cwd:
429 os.chdir(oldcwd)
431 os.chdir(oldcwd)
430
432
431 def rename(src, dst):
433 def rename(src, dst):
432 """forcibly rename a file"""
434 """forcibly rename a file"""
433 try:
435 try:
434 os.rename(src, dst)
436 os.rename(src, dst)
435 except OSError, err:
437 except OSError, err:
436 # on windows, rename to existing file is not allowed, so we
438 # on windows, rename to existing file is not allowed, so we
437 # must delete destination first. but if file is open, unlink
439 # must delete destination first. but if file is open, unlink
438 # schedules it for delete but does not delete it. rename
440 # schedules it for delete but does not delete it. rename
439 # happens immediately even for open files, so we create
441 # happens immediately even for open files, so we create
440 # temporary file, delete it, rename destination to that name,
442 # temporary file, delete it, rename destination to that name,
441 # then delete that. then rename is safe to do.
443 # then delete that. then rename is safe to do.
442 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
444 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
443 os.close(fd)
445 os.close(fd)
444 os.unlink(temp)
446 os.unlink(temp)
445 os.rename(dst, temp)
447 os.rename(dst, temp)
446 os.unlink(temp)
448 os.unlink(temp)
447 os.rename(src, dst)
449 os.rename(src, dst)
448
450
449 def unlink(f):
451 def unlink(f):
450 """unlink and remove the directory if it is empty"""
452 """unlink and remove the directory if it is empty"""
451 os.unlink(f)
453 os.unlink(f)
452 # try removing directories that might now be empty
454 # try removing directories that might now be empty
453 try:
455 try:
454 os.removedirs(os.path.dirname(f))
456 os.removedirs(os.path.dirname(f))
455 except OSError:
457 except OSError:
456 pass
458 pass
457
459
458 def copyfiles(src, dst, hardlink=None):
460 def copyfiles(src, dst, hardlink=None):
459 """Copy a directory tree using hardlinks if possible"""
461 """Copy a directory tree using hardlinks if possible"""
460
462
461 if hardlink is None:
463 if hardlink is None:
462 hardlink = (os.stat(src).st_dev ==
464 hardlink = (os.stat(src).st_dev ==
463 os.stat(os.path.dirname(dst)).st_dev)
465 os.stat(os.path.dirname(dst)).st_dev)
464
466
465 if os.path.isdir(src):
467 if os.path.isdir(src):
466 os.mkdir(dst)
468 os.mkdir(dst)
467 for name in os.listdir(src):
469 for name in os.listdir(src):
468 srcname = os.path.join(src, name)
470 srcname = os.path.join(src, name)
469 dstname = os.path.join(dst, name)
471 dstname = os.path.join(dst, name)
470 copyfiles(srcname, dstname, hardlink)
472 copyfiles(srcname, dstname, hardlink)
471 else:
473 else:
472 if hardlink:
474 if hardlink:
473 try:
475 try:
474 os_link(src, dst)
476 os_link(src, dst)
475 except (IOError, OSError):
477 except (IOError, OSError):
476 hardlink = False
478 hardlink = False
477 shutil.copy(src, dst)
479 shutil.copy(src, dst)
478 else:
480 else:
479 shutil.copy(src, dst)
481 shutil.copy(src, dst)
480
482
481 def audit_path(path):
483 def audit_path(path):
482 """Abort if path contains dangerous components"""
484 """Abort if path contains dangerous components"""
483 parts = os.path.normcase(path).split(os.sep)
485 parts = os.path.normcase(path).split(os.sep)
484 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
486 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
485 or os.pardir in parts):
487 or os.pardir in parts):
486 raise Abort(_("path contains illegal component: %s\n") % path)
488 raise Abort(_("path contains illegal component: %s\n") % path)
487
489
488 def _makelock_file(info, pathname):
490 def _makelock_file(info, pathname):
489 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
491 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
490 os.write(ld, info)
492 os.write(ld, info)
491 os.close(ld)
493 os.close(ld)
492
494
493 def _readlock_file(pathname):
495 def _readlock_file(pathname):
494 return posixfile(pathname).read()
496 return posixfile(pathname).read()
495
497
496 def nlinks(pathname):
498 def nlinks(pathname):
497 """Return number of hardlinks for the given file."""
499 """Return number of hardlinks for the given file."""
498 return os.lstat(pathname).st_nlink
500 return os.lstat(pathname).st_nlink
499
501
500 if hasattr(os, 'link'):
502 if hasattr(os, 'link'):
501 os_link = os.link
503 os_link = os.link
502 else:
504 else:
503 def os_link(src, dst):
505 def os_link(src, dst):
504 raise OSError(0, _("Hardlinks not supported"))
506 raise OSError(0, _("Hardlinks not supported"))
505
507
506 def fstat(fp):
508 def fstat(fp):
507 '''stat file object that may not have fileno method.'''
509 '''stat file object that may not have fileno method.'''
508 try:
510 try:
509 return os.fstat(fp.fileno())
511 return os.fstat(fp.fileno())
510 except AttributeError:
512 except AttributeError:
511 return os.stat(fp.name)
513 return os.stat(fp.name)
512
514
513 posixfile = file
515 posixfile = file
514
516
515 def is_win_9x():
517 def is_win_9x():
516 '''return true if run on windows 95, 98 or me.'''
518 '''return true if run on windows 95, 98 or me.'''
517 try:
519 try:
518 return sys.getwindowsversion()[3] == 1
520 return sys.getwindowsversion()[3] == 1
519 except AttributeError:
521 except AttributeError:
520 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
522 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
521
523
522 # Platform specific variants
524 # Platform specific variants
523 if os.name == 'nt':
525 if os.name == 'nt':
524 demandload(globals(), "msvcrt")
526 demandload(globals(), "msvcrt")
525 nulldev = 'NUL:'
527 nulldev = 'NUL:'
526
528
527 class winstdout:
529 class winstdout:
528 '''stdout on windows misbehaves if sent through a pipe'''
530 '''stdout on windows misbehaves if sent through a pipe'''
529
531
530 def __init__(self, fp):
532 def __init__(self, fp):
531 self.fp = fp
533 self.fp = fp
532
534
533 def __getattr__(self, key):
535 def __getattr__(self, key):
534 return getattr(self.fp, key)
536 return getattr(self.fp, key)
535
537
536 def close(self):
538 def close(self):
537 try:
539 try:
538 self.fp.close()
540 self.fp.close()
539 except: pass
541 except: pass
540
542
541 def write(self, s):
543 def write(self, s):
542 try:
544 try:
543 return self.fp.write(s)
545 return self.fp.write(s)
544 except IOError, inst:
546 except IOError, inst:
545 if inst.errno != 0: raise
547 if inst.errno != 0: raise
546 self.close()
548 self.close()
547 raise IOError(errno.EPIPE, 'Broken pipe')
549 raise IOError(errno.EPIPE, 'Broken pipe')
548
550
549 sys.stdout = winstdout(sys.stdout)
551 sys.stdout = winstdout(sys.stdout)
550
552
551 def system_rcpath():
553 def system_rcpath():
552 try:
554 try:
553 return system_rcpath_win32()
555 return system_rcpath_win32()
554 except:
556 except:
555 return [r'c:\mercurial\mercurial.ini']
557 return [r'c:\mercurial\mercurial.ini']
556
558
557 def os_rcpath():
559 def os_rcpath():
558 '''return default os-specific hgrc search path'''
560 '''return default os-specific hgrc search path'''
559 path = system_rcpath()
561 path = system_rcpath()
560 path.append(user_rcpath())
562 path.append(user_rcpath())
561 userprofile = os.environ.get('USERPROFILE')
563 userprofile = os.environ.get('USERPROFILE')
562 if userprofile:
564 if userprofile:
563 path.append(os.path.join(userprofile, 'mercurial.ini'))
565 path.append(os.path.join(userprofile, 'mercurial.ini'))
564 return path
566 return path
565
567
566 def user_rcpath():
568 def user_rcpath():
567 '''return os-specific hgrc search path to the user dir'''
569 '''return os-specific hgrc search path to the user dir'''
568 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
570 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
569
571
570 def parse_patch_output(output_line):
572 def parse_patch_output(output_line):
571 """parses the output produced by patch and returns the file name"""
573 """parses the output produced by patch and returns the file name"""
572 pf = output_line[14:]
574 pf = output_line[14:]
573 if pf[0] == '`':
575 if pf[0] == '`':
574 pf = pf[1:-1] # Remove the quotes
576 pf = pf[1:-1] # Remove the quotes
575 return pf
577 return pf
576
578
577 def testpid(pid):
579 def testpid(pid):
578 '''return False if pid dead, True if running or not known'''
580 '''return False if pid dead, True if running or not known'''
579 return True
581 return True
580
582
581 def is_exec(f, last):
583 def is_exec(f, last):
582 return last
584 return last
583
585
584 def set_exec(f, mode):
586 def set_exec(f, mode):
585 pass
587 pass
586
588
587 def set_binary(fd):
589 def set_binary(fd):
588 msvcrt.setmode(fd.fileno(), os.O_BINARY)
590 msvcrt.setmode(fd.fileno(), os.O_BINARY)
589
591
590 def pconvert(path):
592 def pconvert(path):
591 return path.replace("\\", "/")
593 return path.replace("\\", "/")
592
594
593 def localpath(path):
595 def localpath(path):
594 return path.replace('/', '\\')
596 return path.replace('/', '\\')
595
597
596 def normpath(path):
598 def normpath(path):
597 return pconvert(os.path.normpath(path))
599 return pconvert(os.path.normpath(path))
598
600
599 makelock = _makelock_file
601 makelock = _makelock_file
600 readlock = _readlock_file
602 readlock = _readlock_file
601
603
602 def samestat(s1, s2):
604 def samestat(s1, s2):
603 return False
605 return False
604
606
605 def shellquote(s):
607 def shellquote(s):
606 return '"%s"' % s.replace('"', '\\"')
608 return '"%s"' % s.replace('"', '\\"')
607
609
608 def explain_exit(code):
610 def explain_exit(code):
609 return _("exited with status %d") % code, code
611 return _("exited with status %d") % code, code
610
612
611 try:
613 try:
612 # override functions with win32 versions if possible
614 # override functions with win32 versions if possible
613 from util_win32 import *
615 from util_win32 import *
614 if not is_win_9x():
616 if not is_win_9x():
615 posixfile = posixfile_nt
617 posixfile = posixfile_nt
616 except ImportError:
618 except ImportError:
617 pass
619 pass
618
620
619 else:
621 else:
620 nulldev = '/dev/null'
622 nulldev = '/dev/null'
621
623
622 def rcfiles(path):
624 def rcfiles(path):
623 rcs = [os.path.join(path, 'hgrc')]
625 rcs = [os.path.join(path, 'hgrc')]
624 rcdir = os.path.join(path, 'hgrc.d')
626 rcdir = os.path.join(path, 'hgrc.d')
625 try:
627 try:
626 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
628 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
627 if f.endswith(".rc")])
629 if f.endswith(".rc")])
628 except OSError:
630 except OSError:
629 pass
631 pass
630 return rcs
632 return rcs
631
633
632 def os_rcpath():
634 def os_rcpath():
633 '''return default os-specific hgrc search path'''
635 '''return default os-specific hgrc search path'''
634 path = []
636 path = []
635 # old mod_python does not set sys.argv
637 # old mod_python does not set sys.argv
636 if len(getattr(sys, 'argv', [])) > 0:
638 if len(getattr(sys, 'argv', [])) > 0:
637 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
639 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
638 '/../etc/mercurial'))
640 '/../etc/mercurial'))
639 path.extend(rcfiles('/etc/mercurial'))
641 path.extend(rcfiles('/etc/mercurial'))
640 path.append(os.path.expanduser('~/.hgrc'))
642 path.append(os.path.expanduser('~/.hgrc'))
641 path = [os.path.normpath(f) for f in path]
643 path = [os.path.normpath(f) for f in path]
642 return path
644 return path
643
645
644 def parse_patch_output(output_line):
646 def parse_patch_output(output_line):
645 """parses the output produced by patch and returns the file name"""
647 """parses the output produced by patch and returns the file name"""
646 pf = output_line[14:]
648 pf = output_line[14:]
647 if pf.startswith("'") and pf.endswith("'") and " " in pf:
649 if pf.startswith("'") and pf.endswith("'") and " " in pf:
648 pf = pf[1:-1] # Remove the quotes
650 pf = pf[1:-1] # Remove the quotes
649 return pf
651 return pf
650
652
651 def is_exec(f, last):
653 def is_exec(f, last):
652 """check whether a file is executable"""
654 """check whether a file is executable"""
653 return (os.lstat(f).st_mode & 0100 != 0)
655 return (os.lstat(f).st_mode & 0100 != 0)
654
656
655 def set_exec(f, mode):
657 def set_exec(f, mode):
656 s = os.lstat(f).st_mode
658 s = os.lstat(f).st_mode
657 if (s & 0100 != 0) == mode:
659 if (s & 0100 != 0) == mode:
658 return
660 return
659 if mode:
661 if mode:
660 # Turn on +x for every +r bit when making a file executable
662 # Turn on +x for every +r bit when making a file executable
661 # and obey umask.
663 # and obey umask.
662 umask = os.umask(0)
664 umask = os.umask(0)
663 os.umask(umask)
665 os.umask(umask)
664 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
666 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
665 else:
667 else:
666 os.chmod(f, s & 0666)
668 os.chmod(f, s & 0666)
667
669
668 def set_binary(fd):
670 def set_binary(fd):
669 pass
671 pass
670
672
671 def pconvert(path):
673 def pconvert(path):
672 return path
674 return path
673
675
674 def localpath(path):
676 def localpath(path):
675 return path
677 return path
676
678
677 normpath = os.path.normpath
679 normpath = os.path.normpath
678 samestat = os.path.samestat
680 samestat = os.path.samestat
679
681
680 def makelock(info, pathname):
682 def makelock(info, pathname):
681 try:
683 try:
682 os.symlink(info, pathname)
684 os.symlink(info, pathname)
683 except OSError, why:
685 except OSError, why:
684 if why.errno == errno.EEXIST:
686 if why.errno == errno.EEXIST:
685 raise
687 raise
686 else:
688 else:
687 _makelock_file(info, pathname)
689 _makelock_file(info, pathname)
688
690
689 def readlock(pathname):
691 def readlock(pathname):
690 try:
692 try:
691 return os.readlink(pathname)
693 return os.readlink(pathname)
692 except OSError, why:
694 except OSError, why:
693 if why.errno == errno.EINVAL:
695 if why.errno == errno.EINVAL:
694 return _readlock_file(pathname)
696 return _readlock_file(pathname)
695 else:
697 else:
696 raise
698 raise
697
699
698 def shellquote(s):
700 def shellquote(s):
699 return "'%s'" % s.replace("'", "'\\''")
701 return "'%s'" % s.replace("'", "'\\''")
700
702
701 def testpid(pid):
703 def testpid(pid):
702 '''return False if pid dead, True if running or not sure'''
704 '''return False if pid dead, True if running or not sure'''
703 try:
705 try:
704 os.kill(pid, 0)
706 os.kill(pid, 0)
705 return True
707 return True
706 except OSError, inst:
708 except OSError, inst:
707 return inst.errno != errno.ESRCH
709 return inst.errno != errno.ESRCH
708
710
709 def explain_exit(code):
711 def explain_exit(code):
710 """return a 2-tuple (desc, code) describing a process's status"""
712 """return a 2-tuple (desc, code) describing a process's status"""
711 if os.WIFEXITED(code):
713 if os.WIFEXITED(code):
712 val = os.WEXITSTATUS(code)
714 val = os.WEXITSTATUS(code)
713 return _("exited with status %d") % val, val
715 return _("exited with status %d") % val, val
714 elif os.WIFSIGNALED(code):
716 elif os.WIFSIGNALED(code):
715 val = os.WTERMSIG(code)
717 val = os.WTERMSIG(code)
716 return _("killed by signal %d") % val, val
718 return _("killed by signal %d") % val, val
717 elif os.WIFSTOPPED(code):
719 elif os.WIFSTOPPED(code):
718 val = os.WSTOPSIG(code)
720 val = os.WSTOPSIG(code)
719 return _("stopped by signal %d") % val, val
721 return _("stopped by signal %d") % val, val
720 raise ValueError(_("invalid exit code"))
722 raise ValueError(_("invalid exit code"))
721
723
722 def opener(base, audit=True):
724 def opener(base, audit=True):
723 """
725 """
724 return a function that opens files relative to base
726 return a function that opens files relative to base
725
727
726 this function is used to hide the details of COW semantics and
728 this function is used to hide the details of COW semantics and
727 remote file access from higher level code.
729 remote file access from higher level code.
728 """
730 """
729 p = base
731 p = base
730 audit_p = audit
732 audit_p = audit
731
733
732 def mktempcopy(name):
734 def mktempcopy(name):
733 d, fn = os.path.split(name)
735 d, fn = os.path.split(name)
734 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
736 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
735 os.close(fd)
737 os.close(fd)
736 ofp = posixfile(temp, "wb")
738 ofp = posixfile(temp, "wb")
737 try:
739 try:
738 try:
740 try:
739 ifp = posixfile(name, "rb")
741 ifp = posixfile(name, "rb")
740 except IOError, inst:
742 except IOError, inst:
741 if not getattr(inst, 'filename', None):
743 if not getattr(inst, 'filename', None):
742 inst.filename = name
744 inst.filename = name
743 raise
745 raise
744 for chunk in filechunkiter(ifp):
746 for chunk in filechunkiter(ifp):
745 ofp.write(chunk)
747 ofp.write(chunk)
746 ifp.close()
748 ifp.close()
747 ofp.close()
749 ofp.close()
748 except:
750 except:
749 try: os.unlink(temp)
751 try: os.unlink(temp)
750 except: pass
752 except: pass
751 raise
753 raise
752 st = os.lstat(name)
754 st = os.lstat(name)
753 os.chmod(temp, st.st_mode)
755 os.chmod(temp, st.st_mode)
754 return temp
756 return temp
755
757
756 class atomictempfile(posixfile):
758 class atomictempfile(posixfile):
757 """the file will only be copied when rename is called"""
759 """the file will only be copied when rename is called"""
758 def __init__(self, name, mode):
760 def __init__(self, name, mode):
759 self.__name = name
761 self.__name = name
760 self.temp = mktempcopy(name)
762 self.temp = mktempcopy(name)
761 posixfile.__init__(self, self.temp, mode)
763 posixfile.__init__(self, self.temp, mode)
762 def rename(self):
764 def rename(self):
763 if not self.closed:
765 if not self.closed:
764 posixfile.close(self)
766 posixfile.close(self)
765 rename(self.temp, localpath(self.__name))
767 rename(self.temp, localpath(self.__name))
766 def __del__(self):
768 def __del__(self):
767 if not self.closed:
769 if not self.closed:
768 try:
770 try:
769 os.unlink(self.temp)
771 os.unlink(self.temp)
770 except: pass
772 except: pass
771 posixfile.close(self)
773 posixfile.close(self)
772
774
773 class atomicfile(atomictempfile):
775 class atomicfile(atomictempfile):
774 """the file will only be copied on close"""
776 """the file will only be copied on close"""
775 def __init__(self, name, mode):
777 def __init__(self, name, mode):
776 atomictempfile.__init__(self, name, mode)
778 atomictempfile.__init__(self, name, mode)
777 def close(self):
779 def close(self):
778 self.rename()
780 self.rename()
779 def __del__(self):
781 def __del__(self):
780 self.rename()
782 self.rename()
781
783
782 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
784 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
783 if audit_p:
785 if audit_p:
784 audit_path(path)
786 audit_path(path)
785 f = os.path.join(p, path)
787 f = os.path.join(p, path)
786
788
787 if not text:
789 if not text:
788 mode += "b" # for that other OS
790 mode += "b" # for that other OS
789
791
790 if mode[0] != "r":
792 if mode[0] != "r":
791 try:
793 try:
792 nlink = nlinks(f)
794 nlink = nlinks(f)
793 except OSError:
795 except OSError:
794 d = os.path.dirname(f)
796 d = os.path.dirname(f)
795 if not os.path.isdir(d):
797 if not os.path.isdir(d):
796 os.makedirs(d)
798 os.makedirs(d)
797 else:
799 else:
798 if atomic:
800 if atomic:
799 return atomicfile(f, mode)
801 return atomicfile(f, mode)
800 elif atomictemp:
802 elif atomictemp:
801 return atomictempfile(f, mode)
803 return atomictempfile(f, mode)
802 if nlink > 1:
804 if nlink > 1:
803 rename(mktempcopy(f), f)
805 rename(mktempcopy(f), f)
804 return posixfile(f, mode)
806 return posixfile(f, mode)
805
807
806 return o
808 return o
807
809
808 class chunkbuffer(object):
810 class chunkbuffer(object):
809 """Allow arbitrary sized chunks of data to be efficiently read from an
811 """Allow arbitrary sized chunks of data to be efficiently read from an
810 iterator over chunks of arbitrary size."""
812 iterator over chunks of arbitrary size."""
811
813
812 def __init__(self, in_iter, targetsize = 2**16):
814 def __init__(self, in_iter, targetsize = 2**16):
813 """in_iter is the iterator that's iterating over the input chunks.
815 """in_iter is the iterator that's iterating over the input chunks.
814 targetsize is how big a buffer to try to maintain."""
816 targetsize is how big a buffer to try to maintain."""
815 self.in_iter = iter(in_iter)
817 self.in_iter = iter(in_iter)
816 self.buf = ''
818 self.buf = ''
817 self.targetsize = int(targetsize)
819 self.targetsize = int(targetsize)
818 if self.targetsize <= 0:
820 if self.targetsize <= 0:
819 raise ValueError(_("targetsize must be greater than 0, was %d") %
821 raise ValueError(_("targetsize must be greater than 0, was %d") %
820 targetsize)
822 targetsize)
821 self.iterempty = False
823 self.iterempty = False
822
824
823 def fillbuf(self):
825 def fillbuf(self):
824 """Ignore target size; read every chunk from iterator until empty."""
826 """Ignore target size; read every chunk from iterator until empty."""
825 if not self.iterempty:
827 if not self.iterempty:
826 collector = cStringIO.StringIO()
828 collector = cStringIO.StringIO()
827 collector.write(self.buf)
829 collector.write(self.buf)
828 for ch in self.in_iter:
830 for ch in self.in_iter:
829 collector.write(ch)
831 collector.write(ch)
830 self.buf = collector.getvalue()
832 self.buf = collector.getvalue()
831 self.iterempty = True
833 self.iterempty = True
832
834
833 def read(self, l):
835 def read(self, l):
834 """Read L bytes of data from the iterator of chunks of data.
836 """Read L bytes of data from the iterator of chunks of data.
835 Returns less than L bytes if the iterator runs dry."""
837 Returns less than L bytes if the iterator runs dry."""
836 if l > len(self.buf) and not self.iterempty:
838 if l > len(self.buf) and not self.iterempty:
837 # Clamp to a multiple of self.targetsize
839 # Clamp to a multiple of self.targetsize
838 targetsize = self.targetsize * ((l // self.targetsize) + 1)
840 targetsize = self.targetsize * ((l // self.targetsize) + 1)
839 collector = cStringIO.StringIO()
841 collector = cStringIO.StringIO()
840 collector.write(self.buf)
842 collector.write(self.buf)
841 collected = len(self.buf)
843 collected = len(self.buf)
842 for chunk in self.in_iter:
844 for chunk in self.in_iter:
843 collector.write(chunk)
845 collector.write(chunk)
844 collected += len(chunk)
846 collected += len(chunk)
845 if collected >= targetsize:
847 if collected >= targetsize:
846 break
848 break
847 if collected < targetsize:
849 if collected < targetsize:
848 self.iterempty = True
850 self.iterempty = True
849 self.buf = collector.getvalue()
851 self.buf = collector.getvalue()
850 s, self.buf = self.buf[:l], buffer(self.buf, l)
852 s, self.buf = self.buf[:l], buffer(self.buf, l)
851 return s
853 return s
852
854
853 def filechunkiter(f, size=65536, limit=None):
855 def filechunkiter(f, size=65536, limit=None):
854 """Create a generator that produces the data in the file size
856 """Create a generator that produces the data in the file size
855 (default 65536) bytes at a time, up to optional limit (default is
857 (default 65536) bytes at a time, up to optional limit (default is
856 to read all data). Chunks may be less than size bytes if the
858 to read all data). Chunks may be less than size bytes if the
857 chunk is the last chunk in the file, or the file is a socket or
859 chunk is the last chunk in the file, or the file is a socket or
858 some other type of file that sometimes reads less data than is
860 some other type of file that sometimes reads less data than is
859 requested."""
861 requested."""
860 assert size >= 0
862 assert size >= 0
861 assert limit is None or limit >= 0
863 assert limit is None or limit >= 0
862 while True:
864 while True:
863 if limit is None: nbytes = size
865 if limit is None: nbytes = size
864 else: nbytes = min(limit, size)
866 else: nbytes = min(limit, size)
865 s = nbytes and f.read(nbytes)
867 s = nbytes and f.read(nbytes)
866 if not s: break
868 if not s: break
867 if limit: limit -= len(s)
869 if limit: limit -= len(s)
868 yield s
870 yield s
869
871
870 def makedate():
872 def makedate():
871 lt = time.localtime()
873 lt = time.localtime()
872 if lt[8] == 1 and time.daylight:
874 if lt[8] == 1 and time.daylight:
873 tz = time.altzone
875 tz = time.altzone
874 else:
876 else:
875 tz = time.timezone
877 tz = time.timezone
876 return time.mktime(lt), tz
878 return time.mktime(lt), tz
877
879
878 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
880 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
879 """represent a (unixtime, offset) tuple as a localized time.
881 """represent a (unixtime, offset) tuple as a localized time.
880 unixtime is seconds since the epoch, and offset is the time zone's
882 unixtime is seconds since the epoch, and offset is the time zone's
881 number of seconds away from UTC. if timezone is false, do not
883 number of seconds away from UTC. if timezone is false, do not
882 append time zone to string."""
884 append time zone to string."""
883 t, tz = date or makedate()
885 t, tz = date or makedate()
884 s = time.strftime(format, time.gmtime(float(t) - tz))
886 s = time.strftime(format, time.gmtime(float(t) - tz))
885 if timezone:
887 if timezone:
886 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
888 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
887 return s
889 return s
888
890
889 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
891 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
890 """parse a localized time string and return a (unixtime, offset) tuple.
892 """parse a localized time string and return a (unixtime, offset) tuple.
891 if the string cannot be parsed, ValueError is raised."""
893 if the string cannot be parsed, ValueError is raised."""
892 def hastimezone(string):
894 def hastimezone(string):
893 return (string[-4:].isdigit() and
895 return (string[-4:].isdigit() and
894 (string[-5] == '+' or string[-5] == '-') and
896 (string[-5] == '+' or string[-5] == '-') and
895 string[-6].isspace())
897 string[-6].isspace())
896
898
897 # NOTE: unixtime = localunixtime + offset
899 # NOTE: unixtime = localunixtime + offset
898 if hastimezone(string):
900 if hastimezone(string):
899 date, tz = string[:-6], string[-5:]
901 date, tz = string[:-6], string[-5:]
900 tz = int(tz)
902 tz = int(tz)
901 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
903 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
902 else:
904 else:
903 date, offset = string, None
905 date, offset = string, None
904 timetuple = time.strptime(date, format)
906 timetuple = time.strptime(date, format)
905 localunixtime = int(calendar.timegm(timetuple))
907 localunixtime = int(calendar.timegm(timetuple))
906 if offset is None:
908 if offset is None:
907 # local timezone
909 # local timezone
908 unixtime = int(time.mktime(timetuple))
910 unixtime = int(time.mktime(timetuple))
909 offset = unixtime - localunixtime
911 offset = unixtime - localunixtime
910 else:
912 else:
911 unixtime = localunixtime + offset
913 unixtime = localunixtime + offset
912 return unixtime, offset
914 return unixtime, offset
913
915
914 def parsedate(string, formats=None):
916 def parsedate(string, formats=None):
915 """parse a localized time string and return a (unixtime, offset) tuple.
917 """parse a localized time string and return a (unixtime, offset) tuple.
916 The date may be a "unixtime offset" string or in one of the specified
918 The date may be a "unixtime offset" string or in one of the specified
917 formats."""
919 formats."""
918 if not formats:
920 if not formats:
919 formats = defaultdateformats
921 formats = defaultdateformats
920 try:
922 try:
921 when, offset = map(int, string.split(' '))
923 when, offset = map(int, string.split(' '))
922 except ValueError:
924 except ValueError:
923 for format in formats:
925 for format in formats:
924 try:
926 try:
925 when, offset = strdate(string, format)
927 when, offset = strdate(string, format)
926 except ValueError:
928 except ValueError:
927 pass
929 pass
928 else:
930 else:
929 break
931 break
930 else:
932 else:
931 raise ValueError(_('invalid date: %r '
933 raise ValueError(_('invalid date: %r '
932 'see hg(1) manual page for details')
934 'see hg(1) manual page for details')
933 % string)
935 % string)
934 # validate explicit (probably user-specified) date and
936 # validate explicit (probably user-specified) date and
935 # time zone offset. values must fit in signed 32 bits for
937 # time zone offset. values must fit in signed 32 bits for
936 # current 32-bit linux runtimes. timezones go from UTC-12
938 # current 32-bit linux runtimes. timezones go from UTC-12
937 # to UTC+14
939 # to UTC+14
938 if abs(when) > 0x7fffffff:
940 if abs(when) > 0x7fffffff:
939 raise ValueError(_('date exceeds 32 bits: %d') % when)
941 raise ValueError(_('date exceeds 32 bits: %d') % when)
940 if offset < -50400 or offset > 43200:
942 if offset < -50400 or offset > 43200:
941 raise ValueError(_('impossible time zone offset: %d') % offset)
943 raise ValueError(_('impossible time zone offset: %d') % offset)
942 return when, offset
944 return when, offset
943
945
944 def shortuser(user):
946 def shortuser(user):
945 """Return a short representation of a user name or email address."""
947 """Return a short representation of a user name or email address."""
946 f = user.find('@')
948 f = user.find('@')
947 if f >= 0:
949 if f >= 0:
948 user = user[:f]
950 user = user[:f]
949 f = user.find('<')
951 f = user.find('<')
950 if f >= 0:
952 if f >= 0:
951 user = user[f+1:]
953 user = user[f+1:]
952 f = user.find(' ')
954 f = user.find(' ')
953 if f >= 0:
955 if f >= 0:
954 user = user[:f]
956 user = user[:f]
955 f = user.find('.')
957 f = user.find('.')
956 if f >= 0:
958 if f >= 0:
957 user = user[:f]
959 user = user[:f]
958 return user
960 return user
959
961
960 def walkrepos(path):
962 def walkrepos(path):
961 '''yield every hg repository under path, recursively.'''
963 '''yield every hg repository under path, recursively.'''
962 def errhandler(err):
964 def errhandler(err):
963 if err.filename == path:
965 if err.filename == path:
964 raise err
966 raise err
965
967
966 for root, dirs, files in os.walk(path, onerror=errhandler):
968 for root, dirs, files in os.walk(path, onerror=errhandler):
967 for d in dirs:
969 for d in dirs:
968 if d == '.hg':
970 if d == '.hg':
969 yield root
971 yield root
970 dirs[:] = []
972 dirs[:] = []
971 break
973 break
972
974
973 _rcpath = None
975 _rcpath = None
974
976
975 def rcpath():
977 def rcpath():
976 '''return hgrc search path. if env var HGRCPATH is set, use it.
978 '''return hgrc search path. if env var HGRCPATH is set, use it.
977 for each item in path, if directory, use files ending in .rc,
979 for each item in path, if directory, use files ending in .rc,
978 else use item.
980 else use item.
979 make HGRCPATH empty to only look in .hg/hgrc of current repo.
981 make HGRCPATH empty to only look in .hg/hgrc of current repo.
980 if no HGRCPATH, use default os-specific path.'''
982 if no HGRCPATH, use default os-specific path.'''
981 global _rcpath
983 global _rcpath
982 if _rcpath is None:
984 if _rcpath is None:
983 if 'HGRCPATH' in os.environ:
985 if 'HGRCPATH' in os.environ:
984 _rcpath = []
986 _rcpath = []
985 for p in os.environ['HGRCPATH'].split(os.pathsep):
987 for p in os.environ['HGRCPATH'].split(os.pathsep):
986 if not p: continue
988 if not p: continue
987 if os.path.isdir(p):
989 if os.path.isdir(p):
988 for f in os.listdir(p):
990 for f in os.listdir(p):
989 if f.endswith('.rc'):
991 if f.endswith('.rc'):
990 _rcpath.append(os.path.join(p, f))
992 _rcpath.append(os.path.join(p, f))
991 else:
993 else:
992 _rcpath.append(p)
994 _rcpath.append(p)
993 else:
995 else:
994 _rcpath = os_rcpath()
996 _rcpath = os_rcpath()
995 return _rcpath
997 return _rcpath
996
998
997 def bytecount(nbytes):
999 def bytecount(nbytes):
998 '''return byte count formatted as readable string, with units'''
1000 '''return byte count formatted as readable string, with units'''
999
1001
1000 units = (
1002 units = (
1001 (100, 1<<30, _('%.0f GB')),
1003 (100, 1<<30, _('%.0f GB')),
1002 (10, 1<<30, _('%.1f GB')),
1004 (10, 1<<30, _('%.1f GB')),
1003 (1, 1<<30, _('%.2f GB')),
1005 (1, 1<<30, _('%.2f GB')),
1004 (100, 1<<20, _('%.0f MB')),
1006 (100, 1<<20, _('%.0f MB')),
1005 (10, 1<<20, _('%.1f MB')),
1007 (10, 1<<20, _('%.1f MB')),
1006 (1, 1<<20, _('%.2f MB')),
1008 (1, 1<<20, _('%.2f MB')),
1007 (100, 1<<10, _('%.0f KB')),
1009 (100, 1<<10, _('%.0f KB')),
1008 (10, 1<<10, _('%.1f KB')),
1010 (10, 1<<10, _('%.1f KB')),
1009 (1, 1<<10, _('%.2f KB')),
1011 (1, 1<<10, _('%.2f KB')),
1010 (1, 1, _('%.0f bytes')),
1012 (1, 1, _('%.0f bytes')),
1011 )
1013 )
1012
1014
1013 for multiplier, divisor, format in units:
1015 for multiplier, divisor, format in units:
1014 if nbytes >= divisor * multiplier:
1016 if nbytes >= divisor * multiplier:
1015 return format % (nbytes / float(divisor))
1017 return format % (nbytes / float(divisor))
1016 return units[-1][2] % nbytes
1018 return units[-1][2] % nbytes
1017
1019
1018 def drop_scheme(scheme, path):
1020 def drop_scheme(scheme, path):
1019 sc = scheme + ':'
1021 sc = scheme + ':'
1020 if path.startswith(sc):
1022 if path.startswith(sc):
1021 path = path[len(sc):]
1023 path = path[len(sc):]
1022 if path.startswith('//'):
1024 if path.startswith('//'):
1023 path = path[2:]
1025 path = path[2:]
1024 return path
1026 return path
General Comments 0
You need to be logged in to leave comments. Login now