##// END OF EJS Templates
Merge with crew
Brendan Cully -
r4581:4500fbe3 merge default
parent child Browse files
Show More
@@ -1,2300 +1,2302 b''
1 # queue.py - patch queues for mercurial
1 # queue.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details):
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
34 import os, sys, re, errno
34 import os, sys, re, errno
35
35
36 commands.norepo += " qclone qversion"
36 commands.norepo += " qclone qversion"
37
37
38 # Patch names looks like unix-file names.
38 # Patch names looks like unix-file names.
39 # They must be joinable with queue directory and result in the patch path.
39 # They must be joinable with queue directory and result in the patch path.
40 normname = util.normpath
40 normname = util.normpath
41
41
42 class statusentry:
42 class statusentry:
43 def __init__(self, rev, name=None):
43 def __init__(self, rev, name=None):
44 if not name:
44 if not name:
45 fields = rev.split(':', 1)
45 fields = rev.split(':', 1)
46 if len(fields) == 2:
46 if len(fields) == 2:
47 self.rev, self.name = fields
47 self.rev, self.name = fields
48 else:
48 else:
49 self.rev, self.name = None, None
49 self.rev, self.name = None, None
50 else:
50 else:
51 self.rev, self.name = rev, name
51 self.rev, self.name = rev, name
52
52
53 def __str__(self):
53 def __str__(self):
54 return self.rev + ':' + self.name
54 return self.rev + ':' + self.name
55
55
56 class queue:
56 class queue:
57 def __init__(self, ui, path, patchdir=None):
57 def __init__(self, ui, path, patchdir=None):
58 self.basepath = path
58 self.basepath = path
59 self.path = patchdir or os.path.join(path, "patches")
59 self.path = patchdir or os.path.join(path, "patches")
60 self.opener = util.opener(self.path)
60 self.opener = util.opener(self.path)
61 self.ui = ui
61 self.ui = ui
62 self.applied = []
62 self.applied = []
63 self.full_series = []
63 self.full_series = []
64 self.applied_dirty = 0
64 self.applied_dirty = 0
65 self.series_dirty = 0
65 self.series_dirty = 0
66 self.series_path = "series"
66 self.series_path = "series"
67 self.status_path = "status"
67 self.status_path = "status"
68 self.guards_path = "guards"
68 self.guards_path = "guards"
69 self.active_guards = None
69 self.active_guards = None
70 self.guards_dirty = False
70 self.guards_dirty = False
71 self._diffopts = None
71 self._diffopts = None
72
72
73 if os.path.exists(self.join(self.series_path)):
73 if os.path.exists(self.join(self.series_path)):
74 self.full_series = self.opener(self.series_path).read().splitlines()
74 self.full_series = self.opener(self.series_path).read().splitlines()
75 self.parse_series()
75 self.parse_series()
76
76
77 if os.path.exists(self.join(self.status_path)):
77 if os.path.exists(self.join(self.status_path)):
78 lines = self.opener(self.status_path).read().splitlines()
78 lines = self.opener(self.status_path).read().splitlines()
79 self.applied = [statusentry(l) for l in lines]
79 self.applied = [statusentry(l) for l in lines]
80
80
81 def diffopts(self):
81 def diffopts(self):
82 if self._diffopts is None:
82 if self._diffopts is None:
83 self._diffopts = patch.diffopts(self.ui)
83 self._diffopts = patch.diffopts(self.ui)
84 return self._diffopts
84 return self._diffopts
85
85
86 def join(self, *p):
86 def join(self, *p):
87 return os.path.join(self.path, *p)
87 return os.path.join(self.path, *p)
88
88
89 def find_series(self, patch):
89 def find_series(self, patch):
90 pre = re.compile("(\s*)([^#]+)")
90 pre = re.compile("(\s*)([^#]+)")
91 index = 0
91 index = 0
92 for l in self.full_series:
92 for l in self.full_series:
93 m = pre.match(l)
93 m = pre.match(l)
94 if m:
94 if m:
95 s = m.group(2)
95 s = m.group(2)
96 s = s.rstrip()
96 s = s.rstrip()
97 if s == patch:
97 if s == patch:
98 return index
98 return index
99 index += 1
99 index += 1
100 return None
100 return None
101
101
102 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
102 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
103
103
104 def parse_series(self):
104 def parse_series(self):
105 self.series = []
105 self.series = []
106 self.series_guards = []
106 self.series_guards = []
107 for l in self.full_series:
107 for l in self.full_series:
108 h = l.find('#')
108 h = l.find('#')
109 if h == -1:
109 if h == -1:
110 patch = l
110 patch = l
111 comment = ''
111 comment = ''
112 elif h == 0:
112 elif h == 0:
113 continue
113 continue
114 else:
114 else:
115 patch = l[:h]
115 patch = l[:h]
116 comment = l[h:]
116 comment = l[h:]
117 patch = patch.strip()
117 patch = patch.strip()
118 if patch:
118 if patch:
119 if patch in self.series:
119 if patch in self.series:
120 raise util.Abort(_('%s appears more than once in %s') %
120 raise util.Abort(_('%s appears more than once in %s') %
121 (patch, self.join(self.series_path)))
121 (patch, self.join(self.series_path)))
122 self.series.append(patch)
122 self.series.append(patch)
123 self.series_guards.append(self.guard_re.findall(comment))
123 self.series_guards.append(self.guard_re.findall(comment))
124
124
125 def check_guard(self, guard):
125 def check_guard(self, guard):
126 bad_chars = '# \t\r\n\f'
126 bad_chars = '# \t\r\n\f'
127 first = guard[0]
127 first = guard[0]
128 for c in '-+':
128 for c in '-+':
129 if first == c:
129 if first == c:
130 return (_('guard %r starts with invalid character: %r') %
130 return (_('guard %r starts with invalid character: %r') %
131 (guard, c))
131 (guard, c))
132 for c in bad_chars:
132 for c in bad_chars:
133 if c in guard:
133 if c in guard:
134 return _('invalid character in guard %r: %r') % (guard, c)
134 return _('invalid character in guard %r: %r') % (guard, c)
135
135
136 def set_active(self, guards):
136 def set_active(self, guards):
137 for guard in guards:
137 for guard in guards:
138 bad = self.check_guard(guard)
138 bad = self.check_guard(guard)
139 if bad:
139 if bad:
140 raise util.Abort(bad)
140 raise util.Abort(bad)
141 guards = dict.fromkeys(guards).keys()
141 guards = dict.fromkeys(guards).keys()
142 guards.sort()
142 guards.sort()
143 self.ui.debug('active guards: %s\n' % ' '.join(guards))
143 self.ui.debug('active guards: %s\n' % ' '.join(guards))
144 self.active_guards = guards
144 self.active_guards = guards
145 self.guards_dirty = True
145 self.guards_dirty = True
146
146
147 def active(self):
147 def active(self):
148 if self.active_guards is None:
148 if self.active_guards is None:
149 self.active_guards = []
149 self.active_guards = []
150 try:
150 try:
151 guards = self.opener(self.guards_path).read().split()
151 guards = self.opener(self.guards_path).read().split()
152 except IOError, err:
152 except IOError, err:
153 if err.errno != errno.ENOENT: raise
153 if err.errno != errno.ENOENT: raise
154 guards = []
154 guards = []
155 for i, guard in enumerate(guards):
155 for i, guard in enumerate(guards):
156 bad = self.check_guard(guard)
156 bad = self.check_guard(guard)
157 if bad:
157 if bad:
158 self.ui.warn('%s:%d: %s\n' %
158 self.ui.warn('%s:%d: %s\n' %
159 (self.join(self.guards_path), i + 1, bad))
159 (self.join(self.guards_path), i + 1, bad))
160 else:
160 else:
161 self.active_guards.append(guard)
161 self.active_guards.append(guard)
162 return self.active_guards
162 return self.active_guards
163
163
164 def set_guards(self, idx, guards):
164 def set_guards(self, idx, guards):
165 for g in guards:
165 for g in guards:
166 if len(g) < 2:
166 if len(g) < 2:
167 raise util.Abort(_('guard %r too short') % g)
167 raise util.Abort(_('guard %r too short') % g)
168 if g[0] not in '-+':
168 if g[0] not in '-+':
169 raise util.Abort(_('guard %r starts with invalid char') % g)
169 raise util.Abort(_('guard %r starts with invalid char') % g)
170 bad = self.check_guard(g[1:])
170 bad = self.check_guard(g[1:])
171 if bad:
171 if bad:
172 raise util.Abort(bad)
172 raise util.Abort(bad)
173 drop = self.guard_re.sub('', self.full_series[idx])
173 drop = self.guard_re.sub('', self.full_series[idx])
174 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
174 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
175 self.parse_series()
175 self.parse_series()
176 self.series_dirty = True
176 self.series_dirty = True
177
177
178 def pushable(self, idx):
178 def pushable(self, idx):
179 if isinstance(idx, str):
179 if isinstance(idx, str):
180 idx = self.series.index(idx)
180 idx = self.series.index(idx)
181 patchguards = self.series_guards[idx]
181 patchguards = self.series_guards[idx]
182 if not patchguards:
182 if not patchguards:
183 return True, None
183 return True, None
184 default = False
184 default = False
185 guards = self.active()
185 guards = self.active()
186 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
186 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
187 if exactneg:
187 if exactneg:
188 return False, exactneg[0]
188 return False, exactneg[0]
189 pos = [g for g in patchguards if g[0] == '+']
189 pos = [g for g in patchguards if g[0] == '+']
190 exactpos = [g for g in pos if g[1:] in guards]
190 exactpos = [g for g in pos if g[1:] in guards]
191 if pos:
191 if pos:
192 if exactpos:
192 if exactpos:
193 return True, exactpos[0]
193 return True, exactpos[0]
194 return False, pos
194 return False, pos
195 return True, ''
195 return True, ''
196
196
197 def explain_pushable(self, idx, all_patches=False):
197 def explain_pushable(self, idx, all_patches=False):
198 write = all_patches and self.ui.write or self.ui.warn
198 write = all_patches and self.ui.write or self.ui.warn
199 if all_patches or self.ui.verbose:
199 if all_patches or self.ui.verbose:
200 if isinstance(idx, str):
200 if isinstance(idx, str):
201 idx = self.series.index(idx)
201 idx = self.series.index(idx)
202 pushable, why = self.pushable(idx)
202 pushable, why = self.pushable(idx)
203 if all_patches and pushable:
203 if all_patches and pushable:
204 if why is None:
204 if why is None:
205 write(_('allowing %s - no guards in effect\n') %
205 write(_('allowing %s - no guards in effect\n') %
206 self.series[idx])
206 self.series[idx])
207 else:
207 else:
208 if not why:
208 if not why:
209 write(_('allowing %s - no matching negative guards\n') %
209 write(_('allowing %s - no matching negative guards\n') %
210 self.series[idx])
210 self.series[idx])
211 else:
211 else:
212 write(_('allowing %s - guarded by %r\n') %
212 write(_('allowing %s - guarded by %r\n') %
213 (self.series[idx], why))
213 (self.series[idx], why))
214 if not pushable:
214 if not pushable:
215 if why:
215 if why:
216 write(_('skipping %s - guarded by %r\n') %
216 write(_('skipping %s - guarded by %r\n') %
217 (self.series[idx], why))
217 (self.series[idx], why))
218 else:
218 else:
219 write(_('skipping %s - no matching guards\n') %
219 write(_('skipping %s - no matching guards\n') %
220 self.series[idx])
220 self.series[idx])
221
221
222 def save_dirty(self):
222 def save_dirty(self):
223 def write_list(items, path):
223 def write_list(items, path):
224 fp = self.opener(path, 'w')
224 fp = self.opener(path, 'w')
225 for i in items:
225 for i in items:
226 print >> fp, i
226 print >> fp, i
227 fp.close()
227 fp.close()
228 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
228 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
229 if self.series_dirty: write_list(self.full_series, self.series_path)
229 if self.series_dirty: write_list(self.full_series, self.series_path)
230 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
230 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
231
231
232 def readheaders(self, patch):
232 def readheaders(self, patch):
233 def eatdiff(lines):
233 def eatdiff(lines):
234 while lines:
234 while lines:
235 l = lines[-1]
235 l = lines[-1]
236 if (l.startswith("diff -") or
236 if (l.startswith("diff -") or
237 l.startswith("Index:") or
237 l.startswith("Index:") or
238 l.startswith("===========")):
238 l.startswith("===========")):
239 del lines[-1]
239 del lines[-1]
240 else:
240 else:
241 break
241 break
242 def eatempty(lines):
242 def eatempty(lines):
243 while lines:
243 while lines:
244 l = lines[-1]
244 l = lines[-1]
245 if re.match('\s*$', l):
245 if re.match('\s*$', l):
246 del lines[-1]
246 del lines[-1]
247 else:
247 else:
248 break
248 break
249
249
250 pf = self.join(patch)
250 pf = self.join(patch)
251 message = []
251 message = []
252 comments = []
252 comments = []
253 user = None
253 user = None
254 date = None
254 date = None
255 format = None
255 format = None
256 subject = None
256 subject = None
257 diffstart = 0
257 diffstart = 0
258
258
259 for line in file(pf):
259 for line in file(pf):
260 line = line.rstrip()
260 line = line.rstrip()
261 if line.startswith('diff --git'):
261 if line.startswith('diff --git'):
262 diffstart = 2
262 diffstart = 2
263 break
263 break
264 if diffstart:
264 if diffstart:
265 if line.startswith('+++ '):
265 if line.startswith('+++ '):
266 diffstart = 2
266 diffstart = 2
267 break
267 break
268 if line.startswith("--- "):
268 if line.startswith("--- "):
269 diffstart = 1
269 diffstart = 1
270 continue
270 continue
271 elif format == "hgpatch":
271 elif format == "hgpatch":
272 # parse values when importing the result of an hg export
272 # parse values when importing the result of an hg export
273 if line.startswith("# User "):
273 if line.startswith("# User "):
274 user = line[7:]
274 user = line[7:]
275 elif line.startswith("# Date "):
275 elif line.startswith("# Date "):
276 date = line[7:]
276 date = line[7:]
277 elif not line.startswith("# ") and line:
277 elif not line.startswith("# ") and line:
278 message.append(line)
278 message.append(line)
279 format = None
279 format = None
280 elif line == '# HG changeset patch':
280 elif line == '# HG changeset patch':
281 format = "hgpatch"
281 format = "hgpatch"
282 elif (format != "tagdone" and (line.startswith("Subject: ") or
282 elif (format != "tagdone" and (line.startswith("Subject: ") or
283 line.startswith("subject: "))):
283 line.startswith("subject: "))):
284 subject = line[9:]
284 subject = line[9:]
285 format = "tag"
285 format = "tag"
286 elif (format != "tagdone" and (line.startswith("From: ") or
286 elif (format != "tagdone" and (line.startswith("From: ") or
287 line.startswith("from: "))):
287 line.startswith("from: "))):
288 user = line[6:]
288 user = line[6:]
289 format = "tag"
289 format = "tag"
290 elif format == "tag" and line == "":
290 elif format == "tag" and line == "":
291 # when looking for tags (subject: from: etc) they
291 # when looking for tags (subject: from: etc) they
292 # end once you find a blank line in the source
292 # end once you find a blank line in the source
293 format = "tagdone"
293 format = "tagdone"
294 elif message or line:
294 elif message or line:
295 message.append(line)
295 message.append(line)
296 comments.append(line)
296 comments.append(line)
297
297
298 eatdiff(message)
298 eatdiff(message)
299 eatdiff(comments)
299 eatdiff(comments)
300 eatempty(message)
300 eatempty(message)
301 eatempty(comments)
301 eatempty(comments)
302
302
303 # make sure message isn't empty
303 # make sure message isn't empty
304 if format and format.startswith("tag") and subject:
304 if format and format.startswith("tag") and subject:
305 message.insert(0, "")
305 message.insert(0, "")
306 message.insert(0, subject)
306 message.insert(0, subject)
307 return (message, comments, user, date, diffstart > 1)
307 return (message, comments, user, date, diffstart > 1)
308
308
309 def removeundo(self, repo):
309 def removeundo(self, repo):
310 undo = repo.sjoin('undo')
310 undo = repo.sjoin('undo')
311 if not os.path.exists(undo):
311 if not os.path.exists(undo):
312 return
312 return
313 try:
313 try:
314 os.unlink(undo)
314 os.unlink(undo)
315 except OSError, inst:
315 except OSError, inst:
316 self.ui.warn('error removing undo: %s\n' % str(inst))
316 self.ui.warn('error removing undo: %s\n' % str(inst))
317
317
318 def printdiff(self, repo, node1, node2=None, files=None,
318 def printdiff(self, repo, node1, node2=None, files=None,
319 fp=None, changes=None, opts={}):
319 fp=None, changes=None, opts={}):
320 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
320 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
321
321
322 patch.diff(repo, node1, node2, fns, match=matchfn,
322 patch.diff(repo, node1, node2, fns, match=matchfn,
323 fp=fp, changes=changes, opts=self.diffopts())
323 fp=fp, changes=changes, opts=self.diffopts())
324
324
325 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
325 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
326 # first try just applying the patch
326 # first try just applying the patch
327 (err, n) = self.apply(repo, [ patch ], update_status=False,
327 (err, n) = self.apply(repo, [ patch ], update_status=False,
328 strict=True, merge=rev, wlock=wlock)
328 strict=True, merge=rev, wlock=wlock)
329
329
330 if err == 0:
330 if err == 0:
331 return (err, n)
331 return (err, n)
332
332
333 if n is None:
333 if n is None:
334 raise util.Abort(_("apply failed for patch %s") % patch)
334 raise util.Abort(_("apply failed for patch %s") % patch)
335
335
336 self.ui.warn("patch didn't work out, merging %s\n" % patch)
336 self.ui.warn("patch didn't work out, merging %s\n" % patch)
337
337
338 # apply failed, strip away that rev and merge.
338 # apply failed, strip away that rev and merge.
339 hg.clean(repo, head, wlock=wlock)
339 hg.clean(repo, head, wlock=wlock)
340 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
340 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
341
341
342 ctx = repo.changectx(rev)
342 ctx = repo.changectx(rev)
343 ret = hg.merge(repo, rev, wlock=wlock)
343 ret = hg.merge(repo, rev, wlock=wlock)
344 if ret:
344 if ret:
345 raise util.Abort(_("update returned %d") % ret)
345 raise util.Abort(_("update returned %d") % ret)
346 n = repo.commit(None, ctx.description(), ctx.user(),
346 n = repo.commit(None, ctx.description(), ctx.user(),
347 force=1, wlock=wlock)
347 force=1, wlock=wlock)
348 if n == None:
348 if n == None:
349 raise util.Abort(_("repo commit failed"))
349 raise util.Abort(_("repo commit failed"))
350 try:
350 try:
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 except:
352 except:
353 raise util.Abort(_("unable to read %s") % patch)
353 raise util.Abort(_("unable to read %s") % patch)
354
354
355 patchf = self.opener(patch, "w")
355 patchf = self.opener(patch, "w")
356 if comments:
356 if comments:
357 comments = "\n".join(comments) + '\n\n'
357 comments = "\n".join(comments) + '\n\n'
358 patchf.write(comments)
358 patchf.write(comments)
359 self.printdiff(repo, head, n, fp=patchf)
359 self.printdiff(repo, head, n, fp=patchf)
360 patchf.close()
360 patchf.close()
361 self.removeundo(repo)
361 self.removeundo(repo)
362 return (0, n)
362 return (0, n)
363
363
364 def qparents(self, repo, rev=None):
364 def qparents(self, repo, rev=None):
365 if rev is None:
365 if rev is None:
366 (p1, p2) = repo.dirstate.parents()
366 (p1, p2) = repo.dirstate.parents()
367 if p2 == revlog.nullid:
367 if p2 == revlog.nullid:
368 return p1
368 return p1
369 if len(self.applied) == 0:
369 if len(self.applied) == 0:
370 return None
370 return None
371 return revlog.bin(self.applied[-1].rev)
371 return revlog.bin(self.applied[-1].rev)
372 pp = repo.changelog.parents(rev)
372 pp = repo.changelog.parents(rev)
373 if pp[1] != revlog.nullid:
373 if pp[1] != revlog.nullid:
374 arevs = [ x.rev for x in self.applied ]
374 arevs = [ x.rev for x in self.applied ]
375 p0 = revlog.hex(pp[0])
375 p0 = revlog.hex(pp[0])
376 p1 = revlog.hex(pp[1])
376 p1 = revlog.hex(pp[1])
377 if p0 in arevs:
377 if p0 in arevs:
378 return pp[0]
378 return pp[0]
379 if p1 in arevs:
379 if p1 in arevs:
380 return pp[1]
380 return pp[1]
381 return pp[0]
381 return pp[0]
382
382
383 def mergepatch(self, repo, mergeq, series, wlock):
383 def mergepatch(self, repo, mergeq, series, wlock):
384 if len(self.applied) == 0:
384 if len(self.applied) == 0:
385 # each of the patches merged in will have two parents. This
385 # each of the patches merged in will have two parents. This
386 # can confuse the qrefresh, qdiff, and strip code because it
386 # can confuse the qrefresh, qdiff, and strip code because it
387 # needs to know which parent is actually in the patch queue.
387 # needs to know which parent is actually in the patch queue.
388 # so, we insert a merge marker with only one parent. This way
388 # so, we insert a merge marker with only one parent. This way
389 # the first patch in the queue is never a merge patch
389 # the first patch in the queue is never a merge patch
390 #
390 #
391 pname = ".hg.patches.merge.marker"
391 pname = ".hg.patches.merge.marker"
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
393 wlock=wlock)
393 wlock=wlock)
394 self.removeundo(repo)
394 self.removeundo(repo)
395 self.applied.append(statusentry(revlog.hex(n), pname))
395 self.applied.append(statusentry(revlog.hex(n), pname))
396 self.applied_dirty = 1
396 self.applied_dirty = 1
397
397
398 head = self.qparents(repo)
398 head = self.qparents(repo)
399
399
400 for patch in series:
400 for patch in series:
401 patch = mergeq.lookup(patch, strict=True)
401 patch = mergeq.lookup(patch, strict=True)
402 if not patch:
402 if not patch:
403 self.ui.warn("patch %s does not exist\n" % patch)
403 self.ui.warn("patch %s does not exist\n" % patch)
404 return (1, None)
404 return (1, None)
405 pushable, reason = self.pushable(patch)
405 pushable, reason = self.pushable(patch)
406 if not pushable:
406 if not pushable:
407 self.explain_pushable(patch, all_patches=True)
407 self.explain_pushable(patch, all_patches=True)
408 continue
408 continue
409 info = mergeq.isapplied(patch)
409 info = mergeq.isapplied(patch)
410 if not info:
410 if not info:
411 self.ui.warn("patch %s is not applied\n" % patch)
411 self.ui.warn("patch %s is not applied\n" % patch)
412 return (1, None)
412 return (1, None)
413 rev = revlog.bin(info[1])
413 rev = revlog.bin(info[1])
414 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
414 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
415 if head:
415 if head:
416 self.applied.append(statusentry(revlog.hex(head), patch))
416 self.applied.append(statusentry(revlog.hex(head), patch))
417 self.applied_dirty = 1
417 self.applied_dirty = 1
418 if err:
418 if err:
419 return (err, head)
419 return (err, head)
420 self.save_dirty()
420 self.save_dirty()
421 return (0, head)
421 return (0, head)
422
422
423 def patch(self, repo, patchfile):
423 def patch(self, repo, patchfile):
424 '''Apply patchfile to the working directory.
424 '''Apply patchfile to the working directory.
425 patchfile: file name of patch'''
425 patchfile: file name of patch'''
426 files = {}
426 files = {}
427 try:
427 try:
428 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
428 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
429 files=files)
429 files=files)
430 except Exception, inst:
430 except Exception, inst:
431 self.ui.note(str(inst) + '\n')
431 self.ui.note(str(inst) + '\n')
432 if not self.ui.verbose:
432 if not self.ui.verbose:
433 self.ui.warn("patch failed, unable to continue (try -v)\n")
433 self.ui.warn("patch failed, unable to continue (try -v)\n")
434 return (False, files, False)
434 return (False, files, False)
435
435
436 return (True, files, fuzz)
436 return (True, files, fuzz)
437
437
438 def apply(self, repo, series, list=False, update_status=True,
438 def apply(self, repo, series, list=False, update_status=True,
439 strict=False, patchdir=None, merge=None, wlock=None,
439 strict=False, patchdir=None, merge=None, wlock=None,
440 all_files={}):
440 all_files={}):
441 if not wlock:
442 wlock = repo.wlock()
443 lock = repo.lock()
441 tr = repo.transaction()
444 tr = repo.transaction()
442 try:
445 try:
443 ret = self._apply(tr, repo, series, list, update_status,
446 ret = self._apply(tr, repo, series, list, update_status,
444 strict, patchdir, merge, wlock,
447 strict, patchdir, merge, wlock,
445 all_files=all_files)
448 lock=lock, all_files=all_files)
446 tr.close()
449 tr.close()
447 self.save_dirty()
450 self.save_dirty()
448 return ret
451 return ret
449 except:
452 except:
450 try:
453 try:
451 tr.abort()
454 tr.abort()
452 finally:
455 finally:
453 repo.reload()
456 repo.reload()
454 repo.wreload()
457 repo.wreload()
455 raise
458 raise
456
459
457 def _apply(self, tr, repo, series, list=False, update_status=True,
460 def _apply(self, tr, repo, series, list=False, update_status=True,
458 strict=False, patchdir=None, merge=None, wlock=None,
461 strict=False, patchdir=None, merge=None, wlock=None,
459 all_files={}):
462 lock=None, all_files={}):
460 # TODO unify with commands.py
463 # TODO unify with commands.py
461 if not patchdir:
464 if not patchdir:
462 patchdir = self.path
465 patchdir = self.path
463 err = 0
466 err = 0
464 if not wlock:
465 wlock = repo.wlock()
466 lock = repo.lock()
467 n = None
467 n = None
468 for patchname in series:
468 for patchname in series:
469 pushable, reason = self.pushable(patchname)
469 pushable, reason = self.pushable(patchname)
470 if not pushable:
470 if not pushable:
471 self.explain_pushable(patchname, all_patches=True)
471 self.explain_pushable(patchname, all_patches=True)
472 continue
472 continue
473 self.ui.warn("applying %s\n" % patchname)
473 self.ui.warn("applying %s\n" % patchname)
474 pf = os.path.join(patchdir, patchname)
474 pf = os.path.join(patchdir, patchname)
475
475
476 try:
476 try:
477 message, comments, user, date, patchfound = self.readheaders(patchname)
477 message, comments, user, date, patchfound = self.readheaders(patchname)
478 except:
478 except:
479 self.ui.warn("Unable to read %s\n" % patchname)
479 self.ui.warn("Unable to read %s\n" % patchname)
480 err = 1
480 err = 1
481 break
481 break
482
482
483 if not message:
483 if not message:
484 message = "imported patch %s\n" % patchname
484 message = "imported patch %s\n" % patchname
485 else:
485 else:
486 if list:
486 if list:
487 message.append("\nimported patch %s" % patchname)
487 message.append("\nimported patch %s" % patchname)
488 message = '\n'.join(message)
488 message = '\n'.join(message)
489
489
490 (patcherr, files, fuzz) = self.patch(repo, pf)
490 (patcherr, files, fuzz) = self.patch(repo, pf)
491 all_files.update(files)
491 all_files.update(files)
492 patcherr = not patcherr
492 patcherr = not patcherr
493
493
494 if merge and files:
494 if merge and files:
495 # Mark as removed/merged and update dirstate parent info
495 # Mark as removed/merged and update dirstate parent info
496 removed = []
496 removed = []
497 merged = []
497 merged = []
498 for f in files:
498 for f in files:
499 if os.path.exists(repo.dirstate.wjoin(f)):
499 if os.path.exists(repo.dirstate.wjoin(f)):
500 merged.append(f)
500 merged.append(f)
501 else:
501 else:
502 removed.append(f)
502 removed.append(f)
503 repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r')
503 repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r')
504 repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm')
504 repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm')
505 p1, p2 = repo.dirstate.parents()
505 p1, p2 = repo.dirstate.parents()
506 repo.dirstate.setparents(p1, merge)
506 repo.dirstate.setparents(p1, merge)
507 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
507 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
508 n = repo.commit(files, message, user, date, force=1, lock=lock,
508 n = repo.commit(files, message, user, date, force=1, lock=lock,
509 wlock=wlock)
509 wlock=wlock)
510
510
511 if n == None:
511 if n == None:
512 raise util.Abort(_("repo commit failed"))
512 raise util.Abort(_("repo commit failed"))
513
513
514 if update_status:
514 if update_status:
515 self.applied.append(statusentry(revlog.hex(n), patchname))
515 self.applied.append(statusentry(revlog.hex(n), patchname))
516
516
517 if patcherr:
517 if patcherr:
518 if not patchfound:
518 if not patchfound:
519 self.ui.warn("patch %s is empty\n" % patchname)
519 self.ui.warn("patch %s is empty\n" % patchname)
520 err = 0
520 err = 0
521 else:
521 else:
522 self.ui.warn("patch failed, rejects left in working dir\n")
522 self.ui.warn("patch failed, rejects left in working dir\n")
523 err = 1
523 err = 1
524 break
524 break
525
525
526 if fuzz and strict:
526 if fuzz and strict:
527 self.ui.warn("fuzz found when applying patch, stopping\n")
527 self.ui.warn("fuzz found when applying patch, stopping\n")
528 err = 1
528 err = 1
529 break
529 break
530 self.removeundo(repo)
530 self.removeundo(repo)
531 return (err, n)
531 return (err, n)
532
532
533 def delete(self, repo, patches, opts):
533 def delete(self, repo, patches, opts):
534 realpatches = []
534 realpatches = []
535 for patch in patches:
535 for patch in patches:
536 patch = self.lookup(patch, strict=True)
536 patch = self.lookup(patch, strict=True)
537 info = self.isapplied(patch)
537 info = self.isapplied(patch)
538 if info:
538 if info:
539 raise util.Abort(_("cannot delete applied patch %s") % patch)
539 raise util.Abort(_("cannot delete applied patch %s") % patch)
540 if patch not in self.series:
540 if patch not in self.series:
541 raise util.Abort(_("patch %s not in series file") % patch)
541 raise util.Abort(_("patch %s not in series file") % patch)
542 realpatches.append(patch)
542 realpatches.append(patch)
543
543
544 appliedbase = 0
544 appliedbase = 0
545 if opts.get('rev'):
545 if opts.get('rev'):
546 if not self.applied:
546 if not self.applied:
547 raise util.Abort(_('no patches applied'))
547 raise util.Abort(_('no patches applied'))
548 revs = cmdutil.revrange(repo, opts['rev'])
548 revs = cmdutil.revrange(repo, opts['rev'])
549 if len(revs) > 1 and revs[0] > revs[1]:
549 if len(revs) > 1 and revs[0] > revs[1]:
550 revs.reverse()
550 revs.reverse()
551 for rev in revs:
551 for rev in revs:
552 if appliedbase >= len(self.applied):
552 if appliedbase >= len(self.applied):
553 raise util.Abort(_("revision %d is not managed") % rev)
553 raise util.Abort(_("revision %d is not managed") % rev)
554
554
555 base = revlog.bin(self.applied[appliedbase].rev)
555 base = revlog.bin(self.applied[appliedbase].rev)
556 node = repo.changelog.node(rev)
556 node = repo.changelog.node(rev)
557 if node != base:
557 if node != base:
558 raise util.Abort(_("cannot delete revision %d above "
558 raise util.Abort(_("cannot delete revision %d above "
559 "applied patches") % rev)
559 "applied patches") % rev)
560 realpatches.append(self.applied[appliedbase].name)
560 realpatches.append(self.applied[appliedbase].name)
561 appliedbase += 1
561 appliedbase += 1
562
562
563 if not opts.get('keep'):
563 if not opts.get('keep'):
564 r = self.qrepo()
564 r = self.qrepo()
565 if r:
565 if r:
566 r.remove(realpatches, True)
566 r.remove(realpatches, True)
567 else:
567 else:
568 for p in realpatches:
568 for p in realpatches:
569 os.unlink(self.join(p))
569 os.unlink(self.join(p))
570
570
571 if appliedbase:
571 if appliedbase:
572 del self.applied[:appliedbase]
572 del self.applied[:appliedbase]
573 self.applied_dirty = 1
573 self.applied_dirty = 1
574 indices = [self.find_series(p) for p in realpatches]
574 indices = [self.find_series(p) for p in realpatches]
575 indices.sort()
575 indices.sort()
576 for i in indices[-1::-1]:
576 for i in indices[-1::-1]:
577 del self.full_series[i]
577 del self.full_series[i]
578 self.parse_series()
578 self.parse_series()
579 self.series_dirty = 1
579 self.series_dirty = 1
580
580
581 def check_toppatch(self, repo):
581 def check_toppatch(self, repo):
582 if len(self.applied) > 0:
582 if len(self.applied) > 0:
583 top = revlog.bin(self.applied[-1].rev)
583 top = revlog.bin(self.applied[-1].rev)
584 pp = repo.dirstate.parents()
584 pp = repo.dirstate.parents()
585 if top not in pp:
585 if top not in pp:
586 raise util.Abort(_("queue top not at same revision as working directory"))
586 raise util.Abort(_("queue top not at same revision as working directory"))
587 return top
587 return top
588 return None
588 return None
589 def check_localchanges(self, repo, force=False, refresh=True):
589 def check_localchanges(self, repo, force=False, refresh=True):
590 m, a, r, d = repo.status()[:4]
590 m, a, r, d = repo.status()[:4]
591 if m or a or r or d:
591 if m or a or r or d:
592 if not force:
592 if not force:
593 if refresh:
593 if refresh:
594 raise util.Abort(_("local changes found, refresh first"))
594 raise util.Abort(_("local changes found, refresh first"))
595 else:
595 else:
596 raise util.Abort(_("local changes found"))
596 raise util.Abort(_("local changes found"))
597 return m, a, r, d
597 return m, a, r, d
598 def new(self, repo, patch, msg=None, force=None):
598 def new(self, repo, patch, msg=None, force=None):
599 if os.path.exists(self.join(patch)):
599 if os.path.exists(self.join(patch)):
600 raise util.Abort(_('patch "%s" already exists') % patch)
600 raise util.Abort(_('patch "%s" already exists') % patch)
601 m, a, r, d = self.check_localchanges(repo, force)
601 m, a, r, d = self.check_localchanges(repo, force)
602 commitfiles = m + a + r
602 commitfiles = m + a + r
603 self.check_toppatch(repo)
603 self.check_toppatch(repo)
604 wlock = repo.wlock()
604 wlock = repo.wlock()
605 insert = self.full_series_end()
605 insert = self.full_series_end()
606 if msg:
606 if msg:
607 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
607 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
608 wlock=wlock)
608 wlock=wlock)
609 else:
609 else:
610 n = repo.commit(commitfiles,
610 n = repo.commit(commitfiles,
611 "New patch: %s" % patch, force=True, wlock=wlock)
611 "New patch: %s" % patch, force=True, wlock=wlock)
612 if n == None:
612 if n == None:
613 raise util.Abort(_("repo commit failed"))
613 raise util.Abort(_("repo commit failed"))
614 self.full_series[insert:insert] = [patch]
614 self.full_series[insert:insert] = [patch]
615 self.applied.append(statusentry(revlog.hex(n), patch))
615 self.applied.append(statusentry(revlog.hex(n), patch))
616 self.parse_series()
616 self.parse_series()
617 self.series_dirty = 1
617 self.series_dirty = 1
618 self.applied_dirty = 1
618 self.applied_dirty = 1
619 p = self.opener(patch, "w")
619 p = self.opener(patch, "w")
620 if msg:
620 if msg:
621 msg = msg + "\n"
621 msg = msg + "\n"
622 p.write(msg)
622 p.write(msg)
623 p.close()
623 p.close()
624 wlock = None
624 wlock = None
625 r = self.qrepo()
625 r = self.qrepo()
626 if r: r.add([patch])
626 if r: r.add([patch])
627 if commitfiles:
627 if commitfiles:
628 self.refresh(repo, short=True)
628 self.refresh(repo, short=True)
629 self.removeundo(repo)
629 self.removeundo(repo)
630
630
631 def strip(self, repo, rev, update=True, backup="all", wlock=None):
631 def strip(self, repo, rev, update=True, backup="all", wlock=None):
632 def limitheads(chlog, stop):
632 def limitheads(chlog, stop):
633 """return the list of all nodes that have no children"""
633 """return the list of all nodes that have no children"""
634 p = {}
634 p = {}
635 h = []
635 h = []
636 stoprev = 0
636 stoprev = 0
637 if stop in chlog.nodemap:
637 if stop in chlog.nodemap:
638 stoprev = chlog.rev(stop)
638 stoprev = chlog.rev(stop)
639
639
640 for r in xrange(chlog.count() - 1, -1, -1):
640 for r in xrange(chlog.count() - 1, -1, -1):
641 n = chlog.node(r)
641 n = chlog.node(r)
642 if n not in p:
642 if n not in p:
643 h.append(n)
643 h.append(n)
644 if n == stop:
644 if n == stop:
645 break
645 break
646 if r < stoprev:
646 if r < stoprev:
647 break
647 break
648 for pn in chlog.parents(n):
648 for pn in chlog.parents(n):
649 p[pn] = 1
649 p[pn] = 1
650 return h
650 return h
651
651
652 def bundle(cg):
652 def bundle(cg):
653 backupdir = repo.join("strip-backup")
653 backupdir = repo.join("strip-backup")
654 if not os.path.isdir(backupdir):
654 if not os.path.isdir(backupdir):
655 os.mkdir(backupdir)
655 os.mkdir(backupdir)
656 name = os.path.join(backupdir, "%s" % revlog.short(rev))
656 name = os.path.join(backupdir, "%s" % revlog.short(rev))
657 name = savename(name)
657 name = savename(name)
658 self.ui.warn("saving bundle to %s\n" % name)
658 self.ui.warn("saving bundle to %s\n" % name)
659 return changegroup.writebundle(cg, name, "HG10BZ")
659 return changegroup.writebundle(cg, name, "HG10BZ")
660
660
661 def stripall(revnum):
661 def stripall(revnum):
662 mm = repo.changectx(rev).manifest()
662 mm = repo.changectx(rev).manifest()
663 seen = {}
663 seen = {}
664
664
665 for x in xrange(revnum, repo.changelog.count()):
665 for x in xrange(revnum, repo.changelog.count()):
666 for f in repo.changectx(x).files():
666 for f in repo.changectx(x).files():
667 if f in seen:
667 if f in seen:
668 continue
668 continue
669 seen[f] = 1
669 seen[f] = 1
670 if f in mm:
670 if f in mm:
671 filerev = mm[f]
671 filerev = mm[f]
672 else:
672 else:
673 filerev = 0
673 filerev = 0
674 seen[f] = filerev
674 seen[f] = filerev
675 # we go in two steps here so the strip loop happens in a
675 # we go in two steps here so the strip loop happens in a
676 # sensible order. When stripping many files, this helps keep
676 # sensible order. When stripping many files, this helps keep
677 # our disk access patterns under control.
677 # our disk access patterns under control.
678 seen_list = seen.keys()
678 seen_list = seen.keys()
679 seen_list.sort()
679 seen_list.sort()
680 for f in seen_list:
680 for f in seen_list:
681 ff = repo.file(f)
681 ff = repo.file(f)
682 filerev = seen[f]
682 filerev = seen[f]
683 if filerev != 0:
683 if filerev != 0:
684 if filerev in ff.nodemap:
684 if filerev in ff.nodemap:
685 filerev = ff.rev(filerev)
685 filerev = ff.rev(filerev)
686 else:
686 else:
687 filerev = 0
687 filerev = 0
688 ff.strip(filerev, revnum)
688 ff.strip(filerev, revnum)
689
689
690 if not wlock:
690 if not wlock:
691 wlock = repo.wlock()
691 wlock = repo.wlock()
692 lock = repo.lock()
692 lock = repo.lock()
693 chlog = repo.changelog
693 chlog = repo.changelog
694 # TODO delete the undo files, and handle undo of merge sets
694 # TODO delete the undo files, and handle undo of merge sets
695 pp = chlog.parents(rev)
695 pp = chlog.parents(rev)
696 revnum = chlog.rev(rev)
696 revnum = chlog.rev(rev)
697
697
698 if update:
698 if update:
699 self.check_localchanges(repo, refresh=False)
699 self.check_localchanges(repo, refresh=False)
700 urev = self.qparents(repo, rev)
700 urev = self.qparents(repo, rev)
701 hg.clean(repo, urev, wlock=wlock)
701 hg.clean(repo, urev, wlock=wlock)
702 repo.dirstate.write()
702 repo.dirstate.write()
703
703
704 # save is a list of all the branches we are truncating away
704 # save is a list of all the branches we are truncating away
705 # that we actually want to keep. changegroup will be used
705 # that we actually want to keep. changegroup will be used
706 # to preserve them and add them back after the truncate
706 # to preserve them and add them back after the truncate
707 saveheads = []
707 saveheads = []
708 savebases = {}
708 savebases = {}
709
709
710 heads = limitheads(chlog, rev)
710 heads = limitheads(chlog, rev)
711 seen = {}
711 seen = {}
712
712
713 # search through all the heads, finding those where the revision
713 # search through all the heads, finding those where the revision
714 # we want to strip away is an ancestor. Also look for merges
714 # we want to strip away is an ancestor. Also look for merges
715 # that might be turned into new heads by the strip.
715 # that might be turned into new heads by the strip.
716 while heads:
716 while heads:
717 h = heads.pop()
717 h = heads.pop()
718 n = h
718 n = h
719 while True:
719 while True:
720 seen[n] = 1
720 seen[n] = 1
721 pp = chlog.parents(n)
721 pp = chlog.parents(n)
722 if pp[1] != revlog.nullid:
722 if pp[1] != revlog.nullid:
723 for p in pp:
723 for p in pp:
724 if chlog.rev(p) > revnum and p not in seen:
724 if chlog.rev(p) > revnum and p not in seen:
725 heads.append(p)
725 heads.append(p)
726 if pp[0] == revlog.nullid:
726 if pp[0] == revlog.nullid:
727 break
727 break
728 if chlog.rev(pp[0]) < revnum:
728 if chlog.rev(pp[0]) < revnum:
729 break
729 break
730 n = pp[0]
730 n = pp[0]
731 if n == rev:
731 if n == rev:
732 break
732 break
733 r = chlog.reachable(h, rev)
733 r = chlog.reachable(h, rev)
734 if rev not in r:
734 if rev not in r:
735 saveheads.append(h)
735 saveheads.append(h)
736 for x in r:
736 for x in r:
737 if chlog.rev(x) > revnum:
737 if chlog.rev(x) > revnum:
738 savebases[x] = 1
738 savebases[x] = 1
739
739
740 # create a changegroup for all the branches we need to keep
740 # create a changegroup for all the branches we need to keep
741 if backup == "all":
741 if backup == "all":
742 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
742 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
743 bundle(backupch)
743 bundle(backupch)
744 if saveheads:
744 if saveheads:
745 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
745 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
746 chgrpfile = bundle(backupch)
746 chgrpfile = bundle(backupch)
747
747
748 stripall(revnum)
748 stripall(revnum)
749
749
750 change = chlog.read(rev)
750 change = chlog.read(rev)
751 chlog.strip(revnum, revnum)
751 chlog.strip(revnum, revnum)
752 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
752 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
753 self.removeundo(repo)
753 self.removeundo(repo)
754 if saveheads:
754 if saveheads:
755 self.ui.status("adding branch\n")
755 self.ui.status("adding branch\n")
756 commands.unbundle(self.ui, repo, "file:%s" % chgrpfile,
756 commands.unbundle(self.ui, repo, "file:%s" % chgrpfile,
757 update=False)
757 update=False)
758 if backup != "strip":
758 if backup != "strip":
759 os.unlink(chgrpfile)
759 os.unlink(chgrpfile)
760
760
761 def isapplied(self, patch):
761 def isapplied(self, patch):
762 """returns (index, rev, patch)"""
762 """returns (index, rev, patch)"""
763 for i in xrange(len(self.applied)):
763 for i in xrange(len(self.applied)):
764 a = self.applied[i]
764 a = self.applied[i]
765 if a.name == patch:
765 if a.name == patch:
766 return (i, a.rev, a.name)
766 return (i, a.rev, a.name)
767 return None
767 return None
768
768
769 # if the exact patch name does not exist, we try a few
769 # if the exact patch name does not exist, we try a few
770 # variations. If strict is passed, we try only #1
770 # variations. If strict is passed, we try only #1
771 #
771 #
772 # 1) a number to indicate an offset in the series file
772 # 1) a number to indicate an offset in the series file
773 # 2) a unique substring of the patch name was given
773 # 2) a unique substring of the patch name was given
774 # 3) patchname[-+]num to indicate an offset in the series file
774 # 3) patchname[-+]num to indicate an offset in the series file
775 def lookup(self, patch, strict=False):
775 def lookup(self, patch, strict=False):
776 patch = patch and str(patch)
776 patch = patch and str(patch)
777
777
778 def partial_name(s):
778 def partial_name(s):
779 if s in self.series:
779 if s in self.series:
780 return s
780 return s
781 matches = [x for x in self.series if s in x]
781 matches = [x for x in self.series if s in x]
782 if len(matches) > 1:
782 if len(matches) > 1:
783 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
783 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
784 for m in matches:
784 for m in matches:
785 self.ui.warn(' %s\n' % m)
785 self.ui.warn(' %s\n' % m)
786 return None
786 return None
787 if matches:
787 if matches:
788 return matches[0]
788 return matches[0]
789 if len(self.series) > 0 and len(self.applied) > 0:
789 if len(self.series) > 0 and len(self.applied) > 0:
790 if s == 'qtip':
790 if s == 'qtip':
791 return self.series[self.series_end(True)-1]
791 return self.series[self.series_end(True)-1]
792 if s == 'qbase':
792 if s == 'qbase':
793 return self.series[0]
793 return self.series[0]
794 return None
794 return None
795 if patch == None:
795 if patch == None:
796 return None
796 return None
797
797
798 # we don't want to return a partial match until we make
798 # we don't want to return a partial match until we make
799 # sure the file name passed in does not exist (checked below)
799 # sure the file name passed in does not exist (checked below)
800 res = partial_name(patch)
800 res = partial_name(patch)
801 if res and res == patch:
801 if res and res == patch:
802 return res
802 return res
803
803
804 if not os.path.isfile(self.join(patch)):
804 if not os.path.isfile(self.join(patch)):
805 try:
805 try:
806 sno = int(patch)
806 sno = int(patch)
807 except(ValueError, OverflowError):
807 except(ValueError, OverflowError):
808 pass
808 pass
809 else:
809 else:
810 if sno < len(self.series):
810 if sno < len(self.series):
811 return self.series[sno]
811 return self.series[sno]
812 if not strict:
812 if not strict:
813 # return any partial match made above
813 # return any partial match made above
814 if res:
814 if res:
815 return res
815 return res
816 minus = patch.rfind('-')
816 minus = patch.rfind('-')
817 if minus >= 0:
817 if minus >= 0:
818 res = partial_name(patch[:minus])
818 res = partial_name(patch[:minus])
819 if res:
819 if res:
820 i = self.series.index(res)
820 i = self.series.index(res)
821 try:
821 try:
822 off = int(patch[minus+1:] or 1)
822 off = int(patch[minus+1:] or 1)
823 except(ValueError, OverflowError):
823 except(ValueError, OverflowError):
824 pass
824 pass
825 else:
825 else:
826 if i - off >= 0:
826 if i - off >= 0:
827 return self.series[i - off]
827 return self.series[i - off]
828 plus = patch.rfind('+')
828 plus = patch.rfind('+')
829 if plus >= 0:
829 if plus >= 0:
830 res = partial_name(patch[:plus])
830 res = partial_name(patch[:plus])
831 if res:
831 if res:
832 i = self.series.index(res)
832 i = self.series.index(res)
833 try:
833 try:
834 off = int(patch[plus+1:] or 1)
834 off = int(patch[plus+1:] or 1)
835 except(ValueError, OverflowError):
835 except(ValueError, OverflowError):
836 pass
836 pass
837 else:
837 else:
838 if i + off < len(self.series):
838 if i + off < len(self.series):
839 return self.series[i + off]
839 return self.series[i + off]
840 raise util.Abort(_("patch %s not in series") % patch)
840 raise util.Abort(_("patch %s not in series") % patch)
841
841
842 def push(self, repo, patch=None, force=False, list=False,
842 def push(self, repo, patch=None, force=False, list=False,
843 mergeq=None, wlock=None):
843 mergeq=None, wlock=None):
844 if not wlock:
844 if not wlock:
845 wlock = repo.wlock()
845 wlock = repo.wlock()
846 patch = self.lookup(patch)
846 patch = self.lookup(patch)
847 # Suppose our series file is: A B C and the current 'top' patch is B.
847 # Suppose our series file is: A B C and the current 'top' patch is B.
848 # qpush C should be performed (moving forward)
848 # qpush C should be performed (moving forward)
849 # qpush B is a NOP (no change)
849 # qpush B is a NOP (no change)
850 # qpush A is an error (can't go backwards with qpush)
850 # qpush A is an error (can't go backwards with qpush)
851 if patch:
851 if patch:
852 info = self.isapplied(patch)
852 info = self.isapplied(patch)
853 if info:
853 if info:
854 if info[0] < len(self.applied) - 1:
854 if info[0] < len(self.applied) - 1:
855 raise util.Abort(_("cannot push to a previous patch: %s") %
855 raise util.Abort(_("cannot push to a previous patch: %s") %
856 patch)
856 patch)
857 if info[0] < len(self.series) - 1:
857 if info[0] < len(self.series) - 1:
858 self.ui.warn(_('qpush: %s is already at the top\n') % patch)
858 self.ui.warn(_('qpush: %s is already at the top\n') % patch)
859 else:
859 else:
860 self.ui.warn(_('all patches are currently applied\n'))
860 self.ui.warn(_('all patches are currently applied\n'))
861 return
861 return
862
862
863 # Following the above example, starting at 'top' of B:
863 # Following the above example, starting at 'top' of B:
864 # qpush should be performed (pushes C), but a subsequent qpush without
864 # qpush should be performed (pushes C), but a subsequent qpush without
865 # an argument is an error (nothing to apply). This allows a loop
865 # an argument is an error (nothing to apply). This allows a loop
866 # of "...while hg qpush..." to work as it detects an error when done
866 # of "...while hg qpush..." to work as it detects an error when done
867 if self.series_end() == len(self.series):
867 if self.series_end() == len(self.series):
868 self.ui.warn(_('patch series already fully applied\n'))
868 self.ui.warn(_('patch series already fully applied\n'))
869 return 1
869 return 1
870 if not force:
870 if not force:
871 self.check_localchanges(repo)
871 self.check_localchanges(repo)
872
872
873 self.applied_dirty = 1;
873 self.applied_dirty = 1;
874 start = self.series_end()
874 start = self.series_end()
875 if start > 0:
875 if start > 0:
876 self.check_toppatch(repo)
876 self.check_toppatch(repo)
877 if not patch:
877 if not patch:
878 patch = self.series[start]
878 patch = self.series[start]
879 end = start + 1
879 end = start + 1
880 else:
880 else:
881 end = self.series.index(patch, start) + 1
881 end = self.series.index(patch, start) + 1
882 s = self.series[start:end]
882 s = self.series[start:end]
883 all_files = {}
883 all_files = {}
884 try:
884 try:
885 if mergeq:
885 if mergeq:
886 ret = self.mergepatch(repo, mergeq, s, wlock)
886 ret = self.mergepatch(repo, mergeq, s, wlock)
887 else:
887 else:
888 ret = self.apply(repo, s, list, wlock=wlock,
888 ret = self.apply(repo, s, list, wlock=wlock,
889 all_files=all_files)
889 all_files=all_files)
890 except:
890 except:
891 self.ui.warn(_('cleaning up working directory...'))
891 self.ui.warn(_('cleaning up working directory...'))
892 node = repo.dirstate.parents()[0]
892 node = repo.dirstate.parents()[0]
893 hg.revert(repo, node, None, wlock)
893 hg.revert(repo, node, None, wlock)
894 unknown = repo.status(wlock=wlock)[4]
894 unknown = repo.status(wlock=wlock)[4]
895 # only remove unknown files that we know we touched or
895 # only remove unknown files that we know we touched or
896 # created while patching
896 # created while patching
897 for f in unknown:
897 for f in unknown:
898 if f in all_files:
898 if f in all_files:
899 util.unlink(repo.wjoin(f))
899 util.unlink(repo.wjoin(f))
900 self.ui.warn(_('done\n'))
900 self.ui.warn(_('done\n'))
901 raise
901 raise
902 top = self.applied[-1].name
902 top = self.applied[-1].name
903 if ret[0]:
903 if ret[0]:
904 self.ui.write("Errors during apply, please fix and refresh %s\n" %
904 self.ui.write("Errors during apply, please fix and refresh %s\n" %
905 top)
905 top)
906 else:
906 else:
907 self.ui.write("Now at: %s\n" % top)
907 self.ui.write("Now at: %s\n" % top)
908 return ret[0]
908 return ret[0]
909
909
910 def pop(self, repo, patch=None, force=False, update=True, all=False,
910 def pop(self, repo, patch=None, force=False, update=True, all=False,
911 wlock=None):
911 wlock=None):
912 def getfile(f, rev):
912 def getfile(f, rev):
913 t = repo.file(f).read(rev)
913 t = repo.file(f).read(rev)
914 repo.wfile(f, "w").write(t)
914 repo.wfile(f, "w").write(t)
915
915
916 if not wlock:
916 if not wlock:
917 wlock = repo.wlock()
917 wlock = repo.wlock()
918 if patch:
918 if patch:
919 # index, rev, patch
919 # index, rev, patch
920 info = self.isapplied(patch)
920 info = self.isapplied(patch)
921 if not info:
921 if not info:
922 patch = self.lookup(patch)
922 patch = self.lookup(patch)
923 info = self.isapplied(patch)
923 info = self.isapplied(patch)
924 if not info:
924 if not info:
925 raise util.Abort(_("patch %s is not applied") % patch)
925 raise util.Abort(_("patch %s is not applied") % patch)
926
926
927 if len(self.applied) == 0:
927 if len(self.applied) == 0:
928 # Allow qpop -a to work repeatedly,
928 # Allow qpop -a to work repeatedly,
929 # but not qpop without an argument
929 # but not qpop without an argument
930 self.ui.warn(_("no patches applied\n"))
930 self.ui.warn(_("no patches applied\n"))
931 return not all
931 return not all
932
932
933 if not update:
933 if not update:
934 parents = repo.dirstate.parents()
934 parents = repo.dirstate.parents()
935 rr = [ revlog.bin(x.rev) for x in self.applied ]
935 rr = [ revlog.bin(x.rev) for x in self.applied ]
936 for p in parents:
936 for p in parents:
937 if p in rr:
937 if p in rr:
938 self.ui.warn("qpop: forcing dirstate update\n")
938 self.ui.warn("qpop: forcing dirstate update\n")
939 update = True
939 update = True
940
940
941 if not force and update:
941 if not force and update:
942 self.check_localchanges(repo)
942 self.check_localchanges(repo)
943
943
944 self.applied_dirty = 1;
944 self.applied_dirty = 1;
945 end = len(self.applied)
945 end = len(self.applied)
946 if not patch:
946 if not patch:
947 if all:
947 if all:
948 popi = 0
948 popi = 0
949 else:
949 else:
950 popi = len(self.applied) - 1
950 popi = len(self.applied) - 1
951 else:
951 else:
952 popi = info[0] + 1
952 popi = info[0] + 1
953 if popi >= end:
953 if popi >= end:
954 self.ui.warn("qpop: %s is already at the top\n" % patch)
954 self.ui.warn("qpop: %s is already at the top\n" % patch)
955 return
955 return
956 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
956 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
957
957
958 start = info[0]
958 start = info[0]
959 rev = revlog.bin(info[1])
959 rev = revlog.bin(info[1])
960
960
961 # we know there are no local changes, so we can make a simplified
961 # we know there are no local changes, so we can make a simplified
962 # form of hg.update.
962 # form of hg.update.
963 if update:
963 if update:
964 top = self.check_toppatch(repo)
964 top = self.check_toppatch(repo)
965 qp = self.qparents(repo, rev)
965 qp = self.qparents(repo, rev)
966 changes = repo.changelog.read(qp)
966 changes = repo.changelog.read(qp)
967 mmap = repo.manifest.read(changes[0])
967 mmap = repo.manifest.read(changes[0])
968 m, a, r, d, u = repo.status(qp, top)[:5]
968 m, a, r, d, u = repo.status(qp, top)[:5]
969 if d:
969 if d:
970 raise util.Abort("deletions found between repo revs")
970 raise util.Abort("deletions found between repo revs")
971 for f in m:
971 for f in m:
972 getfile(f, mmap[f])
972 getfile(f, mmap[f])
973 for f in r:
973 for f in r:
974 getfile(f, mmap[f])
974 getfile(f, mmap[f])
975 util.set_exec(repo.wjoin(f), mmap.execf(f))
975 util.set_exec(repo.wjoin(f), mmap.execf(f))
976 repo.dirstate.update(m + r, 'n')
976 repo.dirstate.update(m + r, 'n')
977 for f in a:
977 for f in a:
978 try:
978 try:
979 os.unlink(repo.wjoin(f))
979 os.unlink(repo.wjoin(f))
980 except OSError, e:
980 except OSError, e:
981 if e.errno != errno.ENOENT:
981 if e.errno != errno.ENOENT:
982 raise
982 raise
983 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
983 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
984 except: pass
984 except: pass
985 if a:
985 if a:
986 repo.dirstate.forget(a)
986 repo.dirstate.forget(a)
987 repo.dirstate.setparents(qp, revlog.nullid)
987 repo.dirstate.setparents(qp, revlog.nullid)
988 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
988 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
989 del self.applied[start:end]
989 del self.applied[start:end]
990 if len(self.applied):
990 if len(self.applied):
991 self.ui.write("Now at: %s\n" % self.applied[-1].name)
991 self.ui.write("Now at: %s\n" % self.applied[-1].name)
992 else:
992 else:
993 self.ui.write("Patch queue now empty\n")
993 self.ui.write("Patch queue now empty\n")
994
994
995 def diff(self, repo, pats, opts):
995 def diff(self, repo, pats, opts):
996 top = self.check_toppatch(repo)
996 top = self.check_toppatch(repo)
997 if not top:
997 if not top:
998 self.ui.write("No patches applied\n")
998 self.ui.write("No patches applied\n")
999 return
999 return
1000 qp = self.qparents(repo, top)
1000 qp = self.qparents(repo, top)
1001 if opts.get('git'):
1001 if opts.get('git'):
1002 self.diffopts().git = True
1002 self.diffopts().git = True
1003 self.printdiff(repo, qp, files=pats, opts=opts)
1003 self.printdiff(repo, qp, files=pats, opts=opts)
1004
1004
1005 def refresh(self, repo, pats=None, **opts):
1005 def refresh(self, repo, pats=None, **opts):
1006 if len(self.applied) == 0:
1006 if len(self.applied) == 0:
1007 self.ui.write("No patches applied\n")
1007 self.ui.write("No patches applied\n")
1008 return 1
1008 return 1
1009 wlock = repo.wlock()
1009 wlock = repo.wlock()
1010 self.check_toppatch(repo)
1010 self.check_toppatch(repo)
1011 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1011 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1012 top = revlog.bin(top)
1012 top = revlog.bin(top)
1013 cparents = repo.changelog.parents(top)
1013 cparents = repo.changelog.parents(top)
1014 patchparent = self.qparents(repo, top)
1014 patchparent = self.qparents(repo, top)
1015 message, comments, user, date, patchfound = self.readheaders(patchfn)
1015 message, comments, user, date, patchfound = self.readheaders(patchfn)
1016
1016
1017 patchf = self.opener(patchfn, "w")
1017 patchf = self.opener(patchfn, "w")
1018 msg = opts.get('msg', '').rstrip()
1018 msg = opts.get('msg', '').rstrip()
1019 if msg:
1019 if msg:
1020 if comments:
1020 if comments:
1021 # Remove existing message.
1021 # Remove existing message.
1022 ci = 0
1022 ci = 0
1023 subj = None
1023 subj = None
1024 for mi in xrange(len(message)):
1024 for mi in xrange(len(message)):
1025 if comments[ci].lower().startswith('subject: '):
1025 if comments[ci].lower().startswith('subject: '):
1026 subj = comments[ci][9:]
1026 subj = comments[ci][9:]
1027 while message[mi] != comments[ci] and message[mi] != subj:
1027 while message[mi] != comments[ci] and message[mi] != subj:
1028 ci += 1
1028 ci += 1
1029 del comments[ci]
1029 del comments[ci]
1030 comments.append(msg)
1030 comments.append(msg)
1031 if comments:
1031 if comments:
1032 comments = "\n".join(comments) + '\n\n'
1032 comments = "\n".join(comments) + '\n\n'
1033 patchf.write(comments)
1033 patchf.write(comments)
1034
1034
1035 if opts.get('git'):
1035 if opts.get('git'):
1036 self.diffopts().git = True
1036 self.diffopts().git = True
1037 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1037 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1038 tip = repo.changelog.tip()
1038 tip = repo.changelog.tip()
1039 if top == tip:
1039 if top == tip:
1040 # if the top of our patch queue is also the tip, there is an
1040 # if the top of our patch queue is also the tip, there is an
1041 # optimization here. We update the dirstate in place and strip
1041 # optimization here. We update the dirstate in place and strip
1042 # off the tip commit. Then just commit the current directory
1042 # off the tip commit. Then just commit the current directory
1043 # tree. We can also send repo.commit the list of files
1043 # tree. We can also send repo.commit the list of files
1044 # changed to speed up the diff
1044 # changed to speed up the diff
1045 #
1045 #
1046 # in short mode, we only diff the files included in the
1046 # in short mode, we only diff the files included in the
1047 # patch already
1047 # patch already
1048 #
1048 #
1049 # this should really read:
1049 # this should really read:
1050 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1050 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1051 # but we do it backwards to take advantage of manifest/chlog
1051 # but we do it backwards to take advantage of manifest/chlog
1052 # caching against the next repo.status call
1052 # caching against the next repo.status call
1053 #
1053 #
1054 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1054 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1055 changes = repo.changelog.read(tip)
1055 changes = repo.changelog.read(tip)
1056 man = repo.manifest.read(changes[0])
1056 man = repo.manifest.read(changes[0])
1057 aaa = aa[:]
1057 aaa = aa[:]
1058 if opts.get('short'):
1058 if opts.get('short'):
1059 filelist = mm + aa + dd
1059 filelist = mm + aa + dd
1060 match = dict.fromkeys(filelist).__contains__
1060 else:
1061 else:
1061 filelist = None
1062 filelist = None
1062 m, a, r, d, u = repo.status(files=filelist)[:5]
1063 match = util.always
1064 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1063
1065
1064 # we might end up with files that were added between tip and
1066 # we might end up with files that were added between tip and
1065 # the dirstate parent, but then changed in the local dirstate.
1067 # the dirstate parent, but then changed in the local dirstate.
1066 # in this case, we want them to only show up in the added section
1068 # in this case, we want them to only show up in the added section
1067 for x in m:
1069 for x in m:
1068 if x not in aa:
1070 if x not in aa:
1069 mm.append(x)
1071 mm.append(x)
1070 # we might end up with files added by the local dirstate that
1072 # we might end up with files added by the local dirstate that
1071 # were deleted by the patch. In this case, they should only
1073 # were deleted by the patch. In this case, they should only
1072 # show up in the changed section.
1074 # show up in the changed section.
1073 for x in a:
1075 for x in a:
1074 if x in dd:
1076 if x in dd:
1075 del dd[dd.index(x)]
1077 del dd[dd.index(x)]
1076 mm.append(x)
1078 mm.append(x)
1077 else:
1079 else:
1078 aa.append(x)
1080 aa.append(x)
1079 # make sure any files deleted in the local dirstate
1081 # make sure any files deleted in the local dirstate
1080 # are not in the add or change column of the patch
1082 # are not in the add or change column of the patch
1081 forget = []
1083 forget = []
1082 for x in d + r:
1084 for x in d + r:
1083 if x in aa:
1085 if x in aa:
1084 del aa[aa.index(x)]
1086 del aa[aa.index(x)]
1085 forget.append(x)
1087 forget.append(x)
1086 continue
1088 continue
1087 elif x in mm:
1089 elif x in mm:
1088 del mm[mm.index(x)]
1090 del mm[mm.index(x)]
1089 dd.append(x)
1091 dd.append(x)
1090
1092
1091 m = util.unique(mm)
1093 m = util.unique(mm)
1092 r = util.unique(dd)
1094 r = util.unique(dd)
1093 a = util.unique(aa)
1095 a = util.unique(aa)
1094 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1096 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1095 filelist = util.unique(c[0] + c[1] + c[2])
1097 filelist = util.unique(c[0] + c[1] + c[2])
1096 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1098 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1097 fp=patchf, changes=c, opts=self.diffopts())
1099 fp=patchf, changes=c, opts=self.diffopts())
1098 patchf.close()
1100 patchf.close()
1099
1101
1100 repo.dirstate.setparents(*cparents)
1102 repo.dirstate.setparents(*cparents)
1101 copies = {}
1103 copies = {}
1102 for dst in a:
1104 for dst in a:
1103 src = repo.dirstate.copied(dst)
1105 src = repo.dirstate.copied(dst)
1104 if src is None:
1106 if src is None:
1105 continue
1107 continue
1106 copies.setdefault(src, []).append(dst)
1108 copies.setdefault(src, []).append(dst)
1107 repo.dirstate.update(a, 'a')
1109 repo.dirstate.update(a, 'a')
1108 # remember the copies between patchparent and tip
1110 # remember the copies between patchparent and tip
1109 # this may be slow, so don't do it if we're not tracking copies
1111 # this may be slow, so don't do it if we're not tracking copies
1110 if self.diffopts().git:
1112 if self.diffopts().git:
1111 for dst in aaa:
1113 for dst in aaa:
1112 f = repo.file(dst)
1114 f = repo.file(dst)
1113 src = f.renamed(man[dst])
1115 src = f.renamed(man[dst])
1114 if src:
1116 if src:
1115 copies[src[0]] = copies.get(dst, [])
1117 copies[src[0]] = copies.get(dst, [])
1116 if dst in a:
1118 if dst in a:
1117 copies[src[0]].append(dst)
1119 copies[src[0]].append(dst)
1118 # we can't copy a file created by the patch itself
1120 # we can't copy a file created by the patch itself
1119 if dst in copies:
1121 if dst in copies:
1120 del copies[dst]
1122 del copies[dst]
1121 for src, dsts in copies.iteritems():
1123 for src, dsts in copies.iteritems():
1122 for dst in dsts:
1124 for dst in dsts:
1123 repo.dirstate.copy(src, dst)
1125 repo.dirstate.copy(src, dst)
1124 repo.dirstate.update(r, 'r')
1126 repo.dirstate.update(r, 'r')
1125 # if the patch excludes a modified file, mark that file with mtime=0
1127 # if the patch excludes a modified file, mark that file with mtime=0
1126 # so status can see it.
1128 # so status can see it.
1127 mm = []
1129 mm = []
1128 for i in xrange(len(m)-1, -1, -1):
1130 for i in xrange(len(m)-1, -1, -1):
1129 if not matchfn(m[i]):
1131 if not matchfn(m[i]):
1130 mm.append(m[i])
1132 mm.append(m[i])
1131 del m[i]
1133 del m[i]
1132 repo.dirstate.update(m, 'n')
1134 repo.dirstate.update(m, 'n')
1133 repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
1135 repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
1134 repo.dirstate.forget(forget)
1136 repo.dirstate.forget(forget)
1135
1137
1136 if not msg:
1138 if not msg:
1137 if not message:
1139 if not message:
1138 message = "patch queue: %s\n" % patchfn
1140 message = "patch queue: %s\n" % patchfn
1139 else:
1141 else:
1140 message = "\n".join(message)
1142 message = "\n".join(message)
1141 else:
1143 else:
1142 message = msg
1144 message = msg
1143
1145
1144 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1146 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1145 n = repo.commit(filelist, message, changes[1], match=matchfn,
1147 n = repo.commit(filelist, message, changes[1], match=matchfn,
1146 force=1, wlock=wlock)
1148 force=1, wlock=wlock)
1147 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1149 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1148 self.applied_dirty = 1
1150 self.applied_dirty = 1
1149 self.removeundo(repo)
1151 self.removeundo(repo)
1150 else:
1152 else:
1151 self.printdiff(repo, patchparent, fp=patchf)
1153 self.printdiff(repo, patchparent, fp=patchf)
1152 patchf.close()
1154 patchf.close()
1153 added = repo.status()[1]
1155 added = repo.status()[1]
1154 for a in added:
1156 for a in added:
1155 f = repo.wjoin(a)
1157 f = repo.wjoin(a)
1156 try:
1158 try:
1157 os.unlink(f)
1159 os.unlink(f)
1158 except OSError, e:
1160 except OSError, e:
1159 if e.errno != errno.ENOENT:
1161 if e.errno != errno.ENOENT:
1160 raise
1162 raise
1161 try: os.removedirs(os.path.dirname(f))
1163 try: os.removedirs(os.path.dirname(f))
1162 except: pass
1164 except: pass
1163 # forget the file copies in the dirstate
1165 # forget the file copies in the dirstate
1164 # push should readd the files later on
1166 # push should readd the files later on
1165 repo.dirstate.forget(added)
1167 repo.dirstate.forget(added)
1166 self.pop(repo, force=True, wlock=wlock)
1168 self.pop(repo, force=True, wlock=wlock)
1167 self.push(repo, force=True, wlock=wlock)
1169 self.push(repo, force=True, wlock=wlock)
1168
1170
1169 def init(self, repo, create=False):
1171 def init(self, repo, create=False):
1170 if not create and os.path.isdir(self.path):
1172 if not create and os.path.isdir(self.path):
1171 raise util.Abort(_("patch queue directory already exists"))
1173 raise util.Abort(_("patch queue directory already exists"))
1172 try:
1174 try:
1173 os.mkdir(self.path)
1175 os.mkdir(self.path)
1174 except OSError, inst:
1176 except OSError, inst:
1175 if inst.errno != errno.EEXIST or not create:
1177 if inst.errno != errno.EEXIST or not create:
1176 raise
1178 raise
1177 if create:
1179 if create:
1178 return self.qrepo(create=True)
1180 return self.qrepo(create=True)
1179
1181
1180 def unapplied(self, repo, patch=None):
1182 def unapplied(self, repo, patch=None):
1181 if patch and patch not in self.series:
1183 if patch and patch not in self.series:
1182 raise util.Abort(_("patch %s is not in series file") % patch)
1184 raise util.Abort(_("patch %s is not in series file") % patch)
1183 if not patch:
1185 if not patch:
1184 start = self.series_end()
1186 start = self.series_end()
1185 else:
1187 else:
1186 start = self.series.index(patch) + 1
1188 start = self.series.index(patch) + 1
1187 unapplied = []
1189 unapplied = []
1188 for i in xrange(start, len(self.series)):
1190 for i in xrange(start, len(self.series)):
1189 pushable, reason = self.pushable(i)
1191 pushable, reason = self.pushable(i)
1190 if pushable:
1192 if pushable:
1191 unapplied.append((i, self.series[i]))
1193 unapplied.append((i, self.series[i]))
1192 self.explain_pushable(i)
1194 self.explain_pushable(i)
1193 return unapplied
1195 return unapplied
1194
1196
1195 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1197 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1196 summary=False):
1198 summary=False):
1197 def displayname(patchname):
1199 def displayname(patchname):
1198 if summary:
1200 if summary:
1199 msg = self.readheaders(patchname)[0]
1201 msg = self.readheaders(patchname)[0]
1200 msg = msg and ': ' + msg[0] or ': '
1202 msg = msg and ': ' + msg[0] or ': '
1201 else:
1203 else:
1202 msg = ''
1204 msg = ''
1203 return '%s%s' % (patchname, msg)
1205 return '%s%s' % (patchname, msg)
1204
1206
1205 applied = dict.fromkeys([p.name for p in self.applied])
1207 applied = dict.fromkeys([p.name for p in self.applied])
1206 if length is None:
1208 if length is None:
1207 length = len(self.series) - start
1209 length = len(self.series) - start
1208 if not missing:
1210 if not missing:
1209 for i in xrange(start, start+length):
1211 for i in xrange(start, start+length):
1210 patch = self.series[i]
1212 patch = self.series[i]
1211 if patch in applied:
1213 if patch in applied:
1212 stat = 'A'
1214 stat = 'A'
1213 elif self.pushable(i)[0]:
1215 elif self.pushable(i)[0]:
1214 stat = 'U'
1216 stat = 'U'
1215 else:
1217 else:
1216 stat = 'G'
1218 stat = 'G'
1217 pfx = ''
1219 pfx = ''
1218 if self.ui.verbose:
1220 if self.ui.verbose:
1219 pfx = '%d %s ' % (i, stat)
1221 pfx = '%d %s ' % (i, stat)
1220 elif status and status != stat:
1222 elif status and status != stat:
1221 continue
1223 continue
1222 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1224 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1223 else:
1225 else:
1224 msng_list = []
1226 msng_list = []
1225 for root, dirs, files in os.walk(self.path):
1227 for root, dirs, files in os.walk(self.path):
1226 d = root[len(self.path) + 1:]
1228 d = root[len(self.path) + 1:]
1227 for f in files:
1229 for f in files:
1228 fl = os.path.join(d, f)
1230 fl = os.path.join(d, f)
1229 if (fl not in self.series and
1231 if (fl not in self.series and
1230 fl not in (self.status_path, self.series_path,
1232 fl not in (self.status_path, self.series_path,
1231 self.guards_path)
1233 self.guards_path)
1232 and not fl.startswith('.')):
1234 and not fl.startswith('.')):
1233 msng_list.append(fl)
1235 msng_list.append(fl)
1234 msng_list.sort()
1236 msng_list.sort()
1235 for x in msng_list:
1237 for x in msng_list:
1236 pfx = self.ui.verbose and ('D ') or ''
1238 pfx = self.ui.verbose and ('D ') or ''
1237 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1239 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1238
1240
1239 def issaveline(self, l):
1241 def issaveline(self, l):
1240 if l.name == '.hg.patches.save.line':
1242 if l.name == '.hg.patches.save.line':
1241 return True
1243 return True
1242
1244
1243 def qrepo(self, create=False):
1245 def qrepo(self, create=False):
1244 if create or os.path.isdir(self.join(".hg")):
1246 if create or os.path.isdir(self.join(".hg")):
1245 return hg.repository(self.ui, path=self.path, create=create)
1247 return hg.repository(self.ui, path=self.path, create=create)
1246
1248
1247 def restore(self, repo, rev, delete=None, qupdate=None):
1249 def restore(self, repo, rev, delete=None, qupdate=None):
1248 c = repo.changelog.read(rev)
1250 c = repo.changelog.read(rev)
1249 desc = c[4].strip()
1251 desc = c[4].strip()
1250 lines = desc.splitlines()
1252 lines = desc.splitlines()
1251 i = 0
1253 i = 0
1252 datastart = None
1254 datastart = None
1253 series = []
1255 series = []
1254 applied = []
1256 applied = []
1255 qpp = None
1257 qpp = None
1256 for i in xrange(0, len(lines)):
1258 for i in xrange(0, len(lines)):
1257 if lines[i] == 'Patch Data:':
1259 if lines[i] == 'Patch Data:':
1258 datastart = i + 1
1260 datastart = i + 1
1259 elif lines[i].startswith('Dirstate:'):
1261 elif lines[i].startswith('Dirstate:'):
1260 l = lines[i].rstrip()
1262 l = lines[i].rstrip()
1261 l = l[10:].split(' ')
1263 l = l[10:].split(' ')
1262 qpp = [ hg.bin(x) for x in l ]
1264 qpp = [ hg.bin(x) for x in l ]
1263 elif datastart != None:
1265 elif datastart != None:
1264 l = lines[i].rstrip()
1266 l = lines[i].rstrip()
1265 se = statusentry(l)
1267 se = statusentry(l)
1266 file_ = se.name
1268 file_ = se.name
1267 if se.rev:
1269 if se.rev:
1268 applied.append(se)
1270 applied.append(se)
1269 else:
1271 else:
1270 series.append(file_)
1272 series.append(file_)
1271 if datastart == None:
1273 if datastart == None:
1272 self.ui.warn("No saved patch data found\n")
1274 self.ui.warn("No saved patch data found\n")
1273 return 1
1275 return 1
1274 self.ui.warn("restoring status: %s\n" % lines[0])
1276 self.ui.warn("restoring status: %s\n" % lines[0])
1275 self.full_series = series
1277 self.full_series = series
1276 self.applied = applied
1278 self.applied = applied
1277 self.parse_series()
1279 self.parse_series()
1278 self.series_dirty = 1
1280 self.series_dirty = 1
1279 self.applied_dirty = 1
1281 self.applied_dirty = 1
1280 heads = repo.changelog.heads()
1282 heads = repo.changelog.heads()
1281 if delete:
1283 if delete:
1282 if rev not in heads:
1284 if rev not in heads:
1283 self.ui.warn("save entry has children, leaving it alone\n")
1285 self.ui.warn("save entry has children, leaving it alone\n")
1284 else:
1286 else:
1285 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1287 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1286 pp = repo.dirstate.parents()
1288 pp = repo.dirstate.parents()
1287 if rev in pp:
1289 if rev in pp:
1288 update = True
1290 update = True
1289 else:
1291 else:
1290 update = False
1292 update = False
1291 self.strip(repo, rev, update=update, backup='strip')
1293 self.strip(repo, rev, update=update, backup='strip')
1292 if qpp:
1294 if qpp:
1293 self.ui.warn("saved queue repository parents: %s %s\n" %
1295 self.ui.warn("saved queue repository parents: %s %s\n" %
1294 (hg.short(qpp[0]), hg.short(qpp[1])))
1296 (hg.short(qpp[0]), hg.short(qpp[1])))
1295 if qupdate:
1297 if qupdate:
1296 print "queue directory updating"
1298 print "queue directory updating"
1297 r = self.qrepo()
1299 r = self.qrepo()
1298 if not r:
1300 if not r:
1299 self.ui.warn("Unable to load queue repository\n")
1301 self.ui.warn("Unable to load queue repository\n")
1300 return 1
1302 return 1
1301 hg.clean(r, qpp[0])
1303 hg.clean(r, qpp[0])
1302
1304
1303 def save(self, repo, msg=None):
1305 def save(self, repo, msg=None):
1304 if len(self.applied) == 0:
1306 if len(self.applied) == 0:
1305 self.ui.warn("save: no patches applied, exiting\n")
1307 self.ui.warn("save: no patches applied, exiting\n")
1306 return 1
1308 return 1
1307 if self.issaveline(self.applied[-1]):
1309 if self.issaveline(self.applied[-1]):
1308 self.ui.warn("status is already saved\n")
1310 self.ui.warn("status is already saved\n")
1309 return 1
1311 return 1
1310
1312
1311 ar = [ ':' + x for x in self.full_series ]
1313 ar = [ ':' + x for x in self.full_series ]
1312 if not msg:
1314 if not msg:
1313 msg = "hg patches saved state"
1315 msg = "hg patches saved state"
1314 else:
1316 else:
1315 msg = "hg patches: " + msg.rstrip('\r\n')
1317 msg = "hg patches: " + msg.rstrip('\r\n')
1316 r = self.qrepo()
1318 r = self.qrepo()
1317 if r:
1319 if r:
1318 pp = r.dirstate.parents()
1320 pp = r.dirstate.parents()
1319 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1321 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1320 msg += "\n\nPatch Data:\n"
1322 msg += "\n\nPatch Data:\n"
1321 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1323 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1322 "\n".join(ar) + '\n' or "")
1324 "\n".join(ar) + '\n' or "")
1323 n = repo.commit(None, text, user=None, force=1)
1325 n = repo.commit(None, text, user=None, force=1)
1324 if not n:
1326 if not n:
1325 self.ui.warn("repo commit failed\n")
1327 self.ui.warn("repo commit failed\n")
1326 return 1
1328 return 1
1327 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1329 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1328 self.applied_dirty = 1
1330 self.applied_dirty = 1
1329 self.removeundo(repo)
1331 self.removeundo(repo)
1330
1332
1331 def full_series_end(self):
1333 def full_series_end(self):
1332 if len(self.applied) > 0:
1334 if len(self.applied) > 0:
1333 p = self.applied[-1].name
1335 p = self.applied[-1].name
1334 end = self.find_series(p)
1336 end = self.find_series(p)
1335 if end == None:
1337 if end == None:
1336 return len(self.full_series)
1338 return len(self.full_series)
1337 return end + 1
1339 return end + 1
1338 return 0
1340 return 0
1339
1341
1340 def series_end(self, all_patches=False):
1342 def series_end(self, all_patches=False):
1341 """If all_patches is False, return the index of the next pushable patch
1343 """If all_patches is False, return the index of the next pushable patch
1342 in the series, or the series length. If all_patches is True, return the
1344 in the series, or the series length. If all_patches is True, return the
1343 index of the first patch past the last applied one.
1345 index of the first patch past the last applied one.
1344 """
1346 """
1345 end = 0
1347 end = 0
1346 def next(start):
1348 def next(start):
1347 if all_patches:
1349 if all_patches:
1348 return start
1350 return start
1349 i = start
1351 i = start
1350 while i < len(self.series):
1352 while i < len(self.series):
1351 p, reason = self.pushable(i)
1353 p, reason = self.pushable(i)
1352 if p:
1354 if p:
1353 break
1355 break
1354 self.explain_pushable(i)
1356 self.explain_pushable(i)
1355 i += 1
1357 i += 1
1356 return i
1358 return i
1357 if len(self.applied) > 0:
1359 if len(self.applied) > 0:
1358 p = self.applied[-1].name
1360 p = self.applied[-1].name
1359 try:
1361 try:
1360 end = self.series.index(p)
1362 end = self.series.index(p)
1361 except ValueError:
1363 except ValueError:
1362 return 0
1364 return 0
1363 return next(end + 1)
1365 return next(end + 1)
1364 return next(end)
1366 return next(end)
1365
1367
1366 def appliedname(self, index):
1368 def appliedname(self, index):
1367 pname = self.applied[index].name
1369 pname = self.applied[index].name
1368 if not self.ui.verbose:
1370 if not self.ui.verbose:
1369 p = pname
1371 p = pname
1370 else:
1372 else:
1371 p = str(self.series.index(pname)) + " " + pname
1373 p = str(self.series.index(pname)) + " " + pname
1372 return p
1374 return p
1373
1375
1374 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1376 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1375 force=None, git=False):
1377 force=None, git=False):
1376 def checkseries(patchname):
1378 def checkseries(patchname):
1377 if patchname in self.series:
1379 if patchname in self.series:
1378 raise util.Abort(_('patch %s is already in the series file')
1380 raise util.Abort(_('patch %s is already in the series file')
1379 % patchname)
1381 % patchname)
1380 def checkfile(patchname):
1382 def checkfile(patchname):
1381 if not force and os.path.exists(self.join(patchname)):
1383 if not force and os.path.exists(self.join(patchname)):
1382 raise util.Abort(_('patch "%s" already exists')
1384 raise util.Abort(_('patch "%s" already exists')
1383 % patchname)
1385 % patchname)
1384
1386
1385 if rev:
1387 if rev:
1386 if files:
1388 if files:
1387 raise util.Abort(_('option "-r" not valid when importing '
1389 raise util.Abort(_('option "-r" not valid when importing '
1388 'files'))
1390 'files'))
1389 rev = cmdutil.revrange(repo, rev)
1391 rev = cmdutil.revrange(repo, rev)
1390 rev.sort(lambda x, y: cmp(y, x))
1392 rev.sort(lambda x, y: cmp(y, x))
1391 if (len(files) > 1 or len(rev) > 1) and patchname:
1393 if (len(files) > 1 or len(rev) > 1) and patchname:
1392 raise util.Abort(_('option "-n" not valid when importing multiple '
1394 raise util.Abort(_('option "-n" not valid when importing multiple '
1393 'patches'))
1395 'patches'))
1394 i = 0
1396 i = 0
1395 added = []
1397 added = []
1396 if rev:
1398 if rev:
1397 # If mq patches are applied, we can only import revisions
1399 # If mq patches are applied, we can only import revisions
1398 # that form a linear path to qbase.
1400 # that form a linear path to qbase.
1399 # Otherwise, they should form a linear path to a head.
1401 # Otherwise, they should form a linear path to a head.
1400 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1402 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1401 if len(heads) > 1:
1403 if len(heads) > 1:
1402 raise util.Abort(_('revision %d is the root of more than one '
1404 raise util.Abort(_('revision %d is the root of more than one '
1403 'branch') % rev[-1])
1405 'branch') % rev[-1])
1404 if self.applied:
1406 if self.applied:
1405 base = revlog.hex(repo.changelog.node(rev[0]))
1407 base = revlog.hex(repo.changelog.node(rev[0]))
1406 if base in [n.rev for n in self.applied]:
1408 if base in [n.rev for n in self.applied]:
1407 raise util.Abort(_('revision %d is already managed')
1409 raise util.Abort(_('revision %d is already managed')
1408 % rev[0])
1410 % rev[0])
1409 if heads != [revlog.bin(self.applied[-1].rev)]:
1411 if heads != [revlog.bin(self.applied[-1].rev)]:
1410 raise util.Abort(_('revision %d is not the parent of '
1412 raise util.Abort(_('revision %d is not the parent of '
1411 'the queue') % rev[0])
1413 'the queue') % rev[0])
1412 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1414 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1413 lastparent = repo.changelog.parentrevs(base)[0]
1415 lastparent = repo.changelog.parentrevs(base)[0]
1414 else:
1416 else:
1415 if heads != [repo.changelog.node(rev[0])]:
1417 if heads != [repo.changelog.node(rev[0])]:
1416 raise util.Abort(_('revision %d has unmanaged children')
1418 raise util.Abort(_('revision %d has unmanaged children')
1417 % rev[0])
1419 % rev[0])
1418 lastparent = None
1420 lastparent = None
1419
1421
1420 if git:
1422 if git:
1421 self.diffopts().git = True
1423 self.diffopts().git = True
1422
1424
1423 for r in rev:
1425 for r in rev:
1424 p1, p2 = repo.changelog.parentrevs(r)
1426 p1, p2 = repo.changelog.parentrevs(r)
1425 n = repo.changelog.node(r)
1427 n = repo.changelog.node(r)
1426 if p2 != revlog.nullrev:
1428 if p2 != revlog.nullrev:
1427 raise util.Abort(_('cannot import merge revision %d') % r)
1429 raise util.Abort(_('cannot import merge revision %d') % r)
1428 if lastparent and lastparent != r:
1430 if lastparent and lastparent != r:
1429 raise util.Abort(_('revision %d is not the parent of %d')
1431 raise util.Abort(_('revision %d is not the parent of %d')
1430 % (r, lastparent))
1432 % (r, lastparent))
1431 lastparent = p1
1433 lastparent = p1
1432
1434
1433 if not patchname:
1435 if not patchname:
1434 patchname = normname('%d.diff' % r)
1436 patchname = normname('%d.diff' % r)
1435 checkseries(patchname)
1437 checkseries(patchname)
1436 checkfile(patchname)
1438 checkfile(patchname)
1437 self.full_series.insert(0, patchname)
1439 self.full_series.insert(0, patchname)
1438
1440
1439 patchf = self.opener(patchname, "w")
1441 patchf = self.opener(patchname, "w")
1440 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1442 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1441 patchf.close()
1443 patchf.close()
1442
1444
1443 se = statusentry(revlog.hex(n), patchname)
1445 se = statusentry(revlog.hex(n), patchname)
1444 self.applied.insert(0, se)
1446 self.applied.insert(0, se)
1445
1447
1446 added.append(patchname)
1448 added.append(patchname)
1447 patchname = None
1449 patchname = None
1448 self.parse_series()
1450 self.parse_series()
1449 self.applied_dirty = 1
1451 self.applied_dirty = 1
1450
1452
1451 for filename in files:
1453 for filename in files:
1452 if existing:
1454 if existing:
1453 if filename == '-':
1455 if filename == '-':
1454 raise util.Abort(_('-e is incompatible with import from -'))
1456 raise util.Abort(_('-e is incompatible with import from -'))
1455 if not patchname:
1457 if not patchname:
1456 patchname = normname(filename)
1458 patchname = normname(filename)
1457 if not os.path.isfile(self.join(patchname)):
1459 if not os.path.isfile(self.join(patchname)):
1458 raise util.Abort(_("patch %s does not exist") % patchname)
1460 raise util.Abort(_("patch %s does not exist") % patchname)
1459 else:
1461 else:
1460 try:
1462 try:
1461 if filename == '-':
1463 if filename == '-':
1462 if not patchname:
1464 if not patchname:
1463 raise util.Abort(_('need --name to import a patch from -'))
1465 raise util.Abort(_('need --name to import a patch from -'))
1464 text = sys.stdin.read()
1466 text = sys.stdin.read()
1465 else:
1467 else:
1466 text = file(filename).read()
1468 text = file(filename).read()
1467 except IOError:
1469 except IOError:
1468 raise util.Abort(_("unable to read %s") % patchname)
1470 raise util.Abort(_("unable to read %s") % patchname)
1469 if not patchname:
1471 if not patchname:
1470 patchname = normname(os.path.basename(filename))
1472 patchname = normname(os.path.basename(filename))
1471 checkfile(patchname)
1473 checkfile(patchname)
1472 patchf = self.opener(patchname, "w")
1474 patchf = self.opener(patchname, "w")
1473 patchf.write(text)
1475 patchf.write(text)
1474 checkseries(patchname)
1476 checkseries(patchname)
1475 index = self.full_series_end() + i
1477 index = self.full_series_end() + i
1476 self.full_series[index:index] = [patchname]
1478 self.full_series[index:index] = [patchname]
1477 self.parse_series()
1479 self.parse_series()
1478 self.ui.warn("adding %s to series file\n" % patchname)
1480 self.ui.warn("adding %s to series file\n" % patchname)
1479 i += 1
1481 i += 1
1480 added.append(patchname)
1482 added.append(patchname)
1481 patchname = None
1483 patchname = None
1482 self.series_dirty = 1
1484 self.series_dirty = 1
1483 qrepo = self.qrepo()
1485 qrepo = self.qrepo()
1484 if qrepo:
1486 if qrepo:
1485 qrepo.add(added)
1487 qrepo.add(added)
1486
1488
1487 def delete(ui, repo, *patches, **opts):
1489 def delete(ui, repo, *patches, **opts):
1488 """remove patches from queue
1490 """remove patches from queue
1489
1491
1490 With --rev, mq will stop managing the named revisions. The
1492 With --rev, mq will stop managing the named revisions. The
1491 patches must be applied and at the base of the stack. This option
1493 patches must be applied and at the base of the stack. This option
1492 is useful when the patches have been applied upstream.
1494 is useful when the patches have been applied upstream.
1493
1495
1494 Otherwise, the patches must not be applied.
1496 Otherwise, the patches must not be applied.
1495
1497
1496 With --keep, the patch files are preserved in the patch directory."""
1498 With --keep, the patch files are preserved in the patch directory."""
1497 q = repo.mq
1499 q = repo.mq
1498 q.delete(repo, patches, opts)
1500 q.delete(repo, patches, opts)
1499 q.save_dirty()
1501 q.save_dirty()
1500 return 0
1502 return 0
1501
1503
1502 def applied(ui, repo, patch=None, **opts):
1504 def applied(ui, repo, patch=None, **opts):
1503 """print the patches already applied"""
1505 """print the patches already applied"""
1504 q = repo.mq
1506 q = repo.mq
1505 if patch:
1507 if patch:
1506 if patch not in q.series:
1508 if patch not in q.series:
1507 raise util.Abort(_("patch %s is not in series file") % patch)
1509 raise util.Abort(_("patch %s is not in series file") % patch)
1508 end = q.series.index(patch) + 1
1510 end = q.series.index(patch) + 1
1509 else:
1511 else:
1510 end = q.series_end(True)
1512 end = q.series_end(True)
1511 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1513 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1512
1514
1513 def unapplied(ui, repo, patch=None, **opts):
1515 def unapplied(ui, repo, patch=None, **opts):
1514 """print the patches not yet applied"""
1516 """print the patches not yet applied"""
1515 q = repo.mq
1517 q = repo.mq
1516 if patch:
1518 if patch:
1517 if patch not in q.series:
1519 if patch not in q.series:
1518 raise util.Abort(_("patch %s is not in series file") % patch)
1520 raise util.Abort(_("patch %s is not in series file") % patch)
1519 start = q.series.index(patch) + 1
1521 start = q.series.index(patch) + 1
1520 else:
1522 else:
1521 start = q.series_end(True)
1523 start = q.series_end(True)
1522 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1524 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1523
1525
1524 def qimport(ui, repo, *filename, **opts):
1526 def qimport(ui, repo, *filename, **opts):
1525 """import a patch
1527 """import a patch
1526
1528
1527 The patch will have the same name as its source file unless you
1529 The patch will have the same name as its source file unless you
1528 give it a new one with --name.
1530 give it a new one with --name.
1529
1531
1530 You can register an existing patch inside the patch directory
1532 You can register an existing patch inside the patch directory
1531 with the --existing flag.
1533 with the --existing flag.
1532
1534
1533 With --force, an existing patch of the same name will be overwritten.
1535 With --force, an existing patch of the same name will be overwritten.
1534
1536
1535 An existing changeset may be placed under mq control with --rev
1537 An existing changeset may be placed under mq control with --rev
1536 (e.g. qimport --rev tip -n patch will place tip under mq control).
1538 (e.g. qimport --rev tip -n patch will place tip under mq control).
1537 With --git, patches imported with --rev will use the git diff
1539 With --git, patches imported with --rev will use the git diff
1538 format.
1540 format.
1539 """
1541 """
1540 q = repo.mq
1542 q = repo.mq
1541 q.qimport(repo, filename, patchname=opts['name'],
1543 q.qimport(repo, filename, patchname=opts['name'],
1542 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1544 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1543 git=opts['git'])
1545 git=opts['git'])
1544 q.save_dirty()
1546 q.save_dirty()
1545 return 0
1547 return 0
1546
1548
1547 def init(ui, repo, **opts):
1549 def init(ui, repo, **opts):
1548 """init a new queue repository
1550 """init a new queue repository
1549
1551
1550 The queue repository is unversioned by default. If -c is
1552 The queue repository is unversioned by default. If -c is
1551 specified, qinit will create a separate nested repository
1553 specified, qinit will create a separate nested repository
1552 for patches. Use qcommit to commit changes to this queue
1554 for patches. Use qcommit to commit changes to this queue
1553 repository."""
1555 repository."""
1554 q = repo.mq
1556 q = repo.mq
1555 r = q.init(repo, create=opts['create_repo'])
1557 r = q.init(repo, create=opts['create_repo'])
1556 q.save_dirty()
1558 q.save_dirty()
1557 if r:
1559 if r:
1558 if not os.path.exists(r.wjoin('.hgignore')):
1560 if not os.path.exists(r.wjoin('.hgignore')):
1559 fp = r.wopener('.hgignore', 'w')
1561 fp = r.wopener('.hgignore', 'w')
1560 fp.write('syntax: glob\n')
1562 fp.write('syntax: glob\n')
1561 fp.write('status\n')
1563 fp.write('status\n')
1562 fp.write('guards\n')
1564 fp.write('guards\n')
1563 fp.close()
1565 fp.close()
1564 if not os.path.exists(r.wjoin('series')):
1566 if not os.path.exists(r.wjoin('series')):
1565 r.wopener('series', 'w').close()
1567 r.wopener('series', 'w').close()
1566 r.add(['.hgignore', 'series'])
1568 r.add(['.hgignore', 'series'])
1567 commands.add(ui, r)
1569 commands.add(ui, r)
1568 return 0
1570 return 0
1569
1571
1570 def clone(ui, source, dest=None, **opts):
1572 def clone(ui, source, dest=None, **opts):
1571 '''clone main and patch repository at same time
1573 '''clone main and patch repository at same time
1572
1574
1573 If source is local, destination will have no patches applied. If
1575 If source is local, destination will have no patches applied. If
1574 source is remote, this command can not check if patches are
1576 source is remote, this command can not check if patches are
1575 applied in source, so cannot guarantee that patches are not
1577 applied in source, so cannot guarantee that patches are not
1576 applied in destination. If you clone remote repository, be sure
1578 applied in destination. If you clone remote repository, be sure
1577 before that it has no patches applied.
1579 before that it has no patches applied.
1578
1580
1579 Source patch repository is looked for in <src>/.hg/patches by
1581 Source patch repository is looked for in <src>/.hg/patches by
1580 default. Use -p <url> to change.
1582 default. Use -p <url> to change.
1581 '''
1583 '''
1582 cmdutil.setremoteconfig(ui, opts)
1584 cmdutil.setremoteconfig(ui, opts)
1583 if dest is None:
1585 if dest is None:
1584 dest = hg.defaultdest(source)
1586 dest = hg.defaultdest(source)
1585 sr = hg.repository(ui, ui.expandpath(source))
1587 sr = hg.repository(ui, ui.expandpath(source))
1586 qbase, destrev = None, None
1588 qbase, destrev = None, None
1587 if sr.local():
1589 if sr.local():
1588 if sr.mq.applied:
1590 if sr.mq.applied:
1589 qbase = revlog.bin(sr.mq.applied[0].rev)
1591 qbase = revlog.bin(sr.mq.applied[0].rev)
1590 if not hg.islocal(dest):
1592 if not hg.islocal(dest):
1591 heads = dict.fromkeys(sr.heads())
1593 heads = dict.fromkeys(sr.heads())
1592 for h in sr.heads(qbase):
1594 for h in sr.heads(qbase):
1593 del heads[h]
1595 del heads[h]
1594 destrev = heads.keys()
1596 destrev = heads.keys()
1595 destrev.append(sr.changelog.parents(qbase)[0])
1597 destrev.append(sr.changelog.parents(qbase)[0])
1596 ui.note(_('cloning main repo\n'))
1598 ui.note(_('cloning main repo\n'))
1597 sr, dr = hg.clone(ui, sr.url(), dest,
1599 sr, dr = hg.clone(ui, sr.url(), dest,
1598 pull=opts['pull'],
1600 pull=opts['pull'],
1599 rev=destrev,
1601 rev=destrev,
1600 update=False,
1602 update=False,
1601 stream=opts['uncompressed'])
1603 stream=opts['uncompressed'])
1602 ui.note(_('cloning patch repo\n'))
1604 ui.note(_('cloning patch repo\n'))
1603 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1605 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1604 dr.url() + '/.hg/patches',
1606 dr.url() + '/.hg/patches',
1605 pull=opts['pull'],
1607 pull=opts['pull'],
1606 update=not opts['noupdate'],
1608 update=not opts['noupdate'],
1607 stream=opts['uncompressed'])
1609 stream=opts['uncompressed'])
1608 if dr.local():
1610 if dr.local():
1609 if qbase:
1611 if qbase:
1610 ui.note(_('stripping applied patches from destination repo\n'))
1612 ui.note(_('stripping applied patches from destination repo\n'))
1611 dr.mq.strip(dr, qbase, update=False, backup=None)
1613 dr.mq.strip(dr, qbase, update=False, backup=None)
1612 if not opts['noupdate']:
1614 if not opts['noupdate']:
1613 ui.note(_('updating destination repo\n'))
1615 ui.note(_('updating destination repo\n'))
1614 hg.update(dr, dr.changelog.tip())
1616 hg.update(dr, dr.changelog.tip())
1615
1617
1616 def commit(ui, repo, *pats, **opts):
1618 def commit(ui, repo, *pats, **opts):
1617 """commit changes in the queue repository"""
1619 """commit changes in the queue repository"""
1618 q = repo.mq
1620 q = repo.mq
1619 r = q.qrepo()
1621 r = q.qrepo()
1620 if not r: raise util.Abort('no queue repository')
1622 if not r: raise util.Abort('no queue repository')
1621 commands.commit(r.ui, r, *pats, **opts)
1623 commands.commit(r.ui, r, *pats, **opts)
1622
1624
1623 def series(ui, repo, **opts):
1625 def series(ui, repo, **opts):
1624 """print the entire series file"""
1626 """print the entire series file"""
1625 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1627 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1626 return 0
1628 return 0
1627
1629
1628 def top(ui, repo, **opts):
1630 def top(ui, repo, **opts):
1629 """print the name of the current patch"""
1631 """print the name of the current patch"""
1630 q = repo.mq
1632 q = repo.mq
1631 t = q.applied and q.series_end(True) or 0
1633 t = q.applied and q.series_end(True) or 0
1632 if t:
1634 if t:
1633 return q.qseries(repo, start=t-1, length=1, status='A',
1635 return q.qseries(repo, start=t-1, length=1, status='A',
1634 summary=opts.get('summary'))
1636 summary=opts.get('summary'))
1635 else:
1637 else:
1636 ui.write("No patches applied\n")
1638 ui.write("No patches applied\n")
1637 return 1
1639 return 1
1638
1640
1639 def next(ui, repo, **opts):
1641 def next(ui, repo, **opts):
1640 """print the name of the next patch"""
1642 """print the name of the next patch"""
1641 q = repo.mq
1643 q = repo.mq
1642 end = q.series_end()
1644 end = q.series_end()
1643 if end == len(q.series):
1645 if end == len(q.series):
1644 ui.write("All patches applied\n")
1646 ui.write("All patches applied\n")
1645 return 1
1647 return 1
1646 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1648 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1647
1649
1648 def prev(ui, repo, **opts):
1650 def prev(ui, repo, **opts):
1649 """print the name of the previous patch"""
1651 """print the name of the previous patch"""
1650 q = repo.mq
1652 q = repo.mq
1651 l = len(q.applied)
1653 l = len(q.applied)
1652 if l == 1:
1654 if l == 1:
1653 ui.write("Only one patch applied\n")
1655 ui.write("Only one patch applied\n")
1654 return 1
1656 return 1
1655 if not l:
1657 if not l:
1656 ui.write("No patches applied\n")
1658 ui.write("No patches applied\n")
1657 return 1
1659 return 1
1658 return q.qseries(repo, start=l-2, length=1, status='A',
1660 return q.qseries(repo, start=l-2, length=1, status='A',
1659 summary=opts.get('summary'))
1661 summary=opts.get('summary'))
1660
1662
1661 def new(ui, repo, patch, **opts):
1663 def new(ui, repo, patch, **opts):
1662 """create a new patch
1664 """create a new patch
1663
1665
1664 qnew creates a new patch on top of the currently-applied patch
1666 qnew creates a new patch on top of the currently-applied patch
1665 (if any). It will refuse to run if there are any outstanding
1667 (if any). It will refuse to run if there are any outstanding
1666 changes unless -f is specified, in which case the patch will
1668 changes unless -f is specified, in which case the patch will
1667 be initialised with them.
1669 be initialised with them.
1668
1670
1669 -e, -m or -l set the patch header as well as the commit message.
1671 -e, -m or -l set the patch header as well as the commit message.
1670 If none is specified, the patch header is empty and the
1672 If none is specified, the patch header is empty and the
1671 commit message is 'New patch: PATCH'"""
1673 commit message is 'New patch: PATCH'"""
1672 q = repo.mq
1674 q = repo.mq
1673 message = cmdutil.logmessage(opts)
1675 message = cmdutil.logmessage(opts)
1674 if opts['edit']:
1676 if opts['edit']:
1675 message = ui.edit(message, ui.username())
1677 message = ui.edit(message, ui.username())
1676 q.new(repo, patch, msg=message, force=opts['force'])
1678 q.new(repo, patch, msg=message, force=opts['force'])
1677 q.save_dirty()
1679 q.save_dirty()
1678 return 0
1680 return 0
1679
1681
1680 def refresh(ui, repo, *pats, **opts):
1682 def refresh(ui, repo, *pats, **opts):
1681 """update the current patch
1683 """update the current patch
1682
1684
1683 If any file patterns are provided, the refreshed patch will contain only
1685 If any file patterns are provided, the refreshed patch will contain only
1684 the modifications that match those patterns; the remaining modifications
1686 the modifications that match those patterns; the remaining modifications
1685 will remain in the working directory.
1687 will remain in the working directory.
1686
1688
1687 hg add/remove/copy/rename work as usual, though you might want to use
1689 hg add/remove/copy/rename work as usual, though you might want to use
1688 git-style patches (--git or [diff] git=1) to track copies and renames.
1690 git-style patches (--git or [diff] git=1) to track copies and renames.
1689 """
1691 """
1690 q = repo.mq
1692 q = repo.mq
1691 message = cmdutil.logmessage(opts)
1693 message = cmdutil.logmessage(opts)
1692 if opts['edit']:
1694 if opts['edit']:
1693 if message:
1695 if message:
1694 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1696 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1695 patch = q.applied[-1].name
1697 patch = q.applied[-1].name
1696 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1698 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1697 message = ui.edit('\n'.join(message), user or ui.username())
1699 message = ui.edit('\n'.join(message), user or ui.username())
1698 ret = q.refresh(repo, pats, msg=message, **opts)
1700 ret = q.refresh(repo, pats, msg=message, **opts)
1699 q.save_dirty()
1701 q.save_dirty()
1700 return ret
1702 return ret
1701
1703
1702 def diff(ui, repo, *pats, **opts):
1704 def diff(ui, repo, *pats, **opts):
1703 """diff of the current patch"""
1705 """diff of the current patch"""
1704 repo.mq.diff(repo, pats, opts)
1706 repo.mq.diff(repo, pats, opts)
1705 return 0
1707 return 0
1706
1708
1707 def fold(ui, repo, *files, **opts):
1709 def fold(ui, repo, *files, **opts):
1708 """fold the named patches into the current patch
1710 """fold the named patches into the current patch
1709
1711
1710 Patches must not yet be applied. Each patch will be successively
1712 Patches must not yet be applied. Each patch will be successively
1711 applied to the current patch in the order given. If all the
1713 applied to the current patch in the order given. If all the
1712 patches apply successfully, the current patch will be refreshed
1714 patches apply successfully, the current patch will be refreshed
1713 with the new cumulative patch, and the folded patches will
1715 with the new cumulative patch, and the folded patches will
1714 be deleted. With -k/--keep, the folded patch files will not
1716 be deleted. With -k/--keep, the folded patch files will not
1715 be removed afterwards.
1717 be removed afterwards.
1716
1718
1717 The header for each folded patch will be concatenated with
1719 The header for each folded patch will be concatenated with
1718 the current patch header, separated by a line of '* * *'."""
1720 the current patch header, separated by a line of '* * *'."""
1719
1721
1720 q = repo.mq
1722 q = repo.mq
1721
1723
1722 if not files:
1724 if not files:
1723 raise util.Abort(_('qfold requires at least one patch name'))
1725 raise util.Abort(_('qfold requires at least one patch name'))
1724 if not q.check_toppatch(repo):
1726 if not q.check_toppatch(repo):
1725 raise util.Abort(_('No patches applied'))
1727 raise util.Abort(_('No patches applied'))
1726
1728
1727 message = cmdutil.logmessage(opts)
1729 message = cmdutil.logmessage(opts)
1728 if opts['edit']:
1730 if opts['edit']:
1729 if message:
1731 if message:
1730 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1732 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1731
1733
1732 parent = q.lookup('qtip')
1734 parent = q.lookup('qtip')
1733 patches = []
1735 patches = []
1734 messages = []
1736 messages = []
1735 for f in files:
1737 for f in files:
1736 p = q.lookup(f)
1738 p = q.lookup(f)
1737 if p in patches or p == parent:
1739 if p in patches or p == parent:
1738 ui.warn(_('Skipping already folded patch %s') % p)
1740 ui.warn(_('Skipping already folded patch %s') % p)
1739 if q.isapplied(p):
1741 if q.isapplied(p):
1740 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1742 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1741 patches.append(p)
1743 patches.append(p)
1742
1744
1743 for p in patches:
1745 for p in patches:
1744 if not message:
1746 if not message:
1745 messages.append(q.readheaders(p)[0])
1747 messages.append(q.readheaders(p)[0])
1746 pf = q.join(p)
1748 pf = q.join(p)
1747 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1749 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1748 if not patchsuccess:
1750 if not patchsuccess:
1749 raise util.Abort(_('Error folding patch %s') % p)
1751 raise util.Abort(_('Error folding patch %s') % p)
1750 patch.updatedir(ui, repo, files)
1752 patch.updatedir(ui, repo, files)
1751
1753
1752 if not message:
1754 if not message:
1753 message, comments, user = q.readheaders(parent)[0:3]
1755 message, comments, user = q.readheaders(parent)[0:3]
1754 for msg in messages:
1756 for msg in messages:
1755 message.append('* * *')
1757 message.append('* * *')
1756 message.extend(msg)
1758 message.extend(msg)
1757 message = '\n'.join(message)
1759 message = '\n'.join(message)
1758
1760
1759 if opts['edit']:
1761 if opts['edit']:
1760 message = ui.edit(message, user or ui.username())
1762 message = ui.edit(message, user or ui.username())
1761
1763
1762 q.refresh(repo, msg=message)
1764 q.refresh(repo, msg=message)
1763 q.delete(repo, patches, opts)
1765 q.delete(repo, patches, opts)
1764 q.save_dirty()
1766 q.save_dirty()
1765
1767
1766 def goto(ui, repo, patch, **opts):
1768 def goto(ui, repo, patch, **opts):
1767 '''push or pop patches until named patch is at top of stack'''
1769 '''push or pop patches until named patch is at top of stack'''
1768 q = repo.mq
1770 q = repo.mq
1769 patch = q.lookup(patch)
1771 patch = q.lookup(patch)
1770 if q.isapplied(patch):
1772 if q.isapplied(patch):
1771 ret = q.pop(repo, patch, force=opts['force'])
1773 ret = q.pop(repo, patch, force=opts['force'])
1772 else:
1774 else:
1773 ret = q.push(repo, patch, force=opts['force'])
1775 ret = q.push(repo, patch, force=opts['force'])
1774 q.save_dirty()
1776 q.save_dirty()
1775 return ret
1777 return ret
1776
1778
1777 def guard(ui, repo, *args, **opts):
1779 def guard(ui, repo, *args, **opts):
1778 '''set or print guards for a patch
1780 '''set or print guards for a patch
1779
1781
1780 Guards control whether a patch can be pushed. A patch with no
1782 Guards control whether a patch can be pushed. A patch with no
1781 guards is always pushed. A patch with a positive guard ("+foo") is
1783 guards is always pushed. A patch with a positive guard ("+foo") is
1782 pushed only if the qselect command has activated it. A patch with
1784 pushed only if the qselect command has activated it. A patch with
1783 a negative guard ("-foo") is never pushed if the qselect command
1785 a negative guard ("-foo") is never pushed if the qselect command
1784 has activated it.
1786 has activated it.
1785
1787
1786 With no arguments, print the currently active guards.
1788 With no arguments, print the currently active guards.
1787 With arguments, set guards for the named patch.
1789 With arguments, set guards for the named patch.
1788
1790
1789 To set a negative guard "-foo" on topmost patch ("--" is needed so
1791 To set a negative guard "-foo" on topmost patch ("--" is needed so
1790 hg will not interpret "-foo" as an option):
1792 hg will not interpret "-foo" as an option):
1791 hg qguard -- -foo
1793 hg qguard -- -foo
1792
1794
1793 To set guards on another patch:
1795 To set guards on another patch:
1794 hg qguard other.patch +2.6.17 -stable
1796 hg qguard other.patch +2.6.17 -stable
1795 '''
1797 '''
1796 def status(idx):
1798 def status(idx):
1797 guards = q.series_guards[idx] or ['unguarded']
1799 guards = q.series_guards[idx] or ['unguarded']
1798 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1800 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1799 q = repo.mq
1801 q = repo.mq
1800 patch = None
1802 patch = None
1801 args = list(args)
1803 args = list(args)
1802 if opts['list']:
1804 if opts['list']:
1803 if args or opts['none']:
1805 if args or opts['none']:
1804 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1806 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1805 for i in xrange(len(q.series)):
1807 for i in xrange(len(q.series)):
1806 status(i)
1808 status(i)
1807 return
1809 return
1808 if not args or args[0][0:1] in '-+':
1810 if not args or args[0][0:1] in '-+':
1809 if not q.applied:
1811 if not q.applied:
1810 raise util.Abort(_('no patches applied'))
1812 raise util.Abort(_('no patches applied'))
1811 patch = q.applied[-1].name
1813 patch = q.applied[-1].name
1812 if patch is None and args[0][0:1] not in '-+':
1814 if patch is None and args[0][0:1] not in '-+':
1813 patch = args.pop(0)
1815 patch = args.pop(0)
1814 if patch is None:
1816 if patch is None:
1815 raise util.Abort(_('no patch to work with'))
1817 raise util.Abort(_('no patch to work with'))
1816 if args or opts['none']:
1818 if args or opts['none']:
1817 idx = q.find_series(patch)
1819 idx = q.find_series(patch)
1818 if idx is None:
1820 if idx is None:
1819 raise util.Abort(_('no patch named %s') % patch)
1821 raise util.Abort(_('no patch named %s') % patch)
1820 q.set_guards(idx, args)
1822 q.set_guards(idx, args)
1821 q.save_dirty()
1823 q.save_dirty()
1822 else:
1824 else:
1823 status(q.series.index(q.lookup(patch)))
1825 status(q.series.index(q.lookup(patch)))
1824
1826
1825 def header(ui, repo, patch=None):
1827 def header(ui, repo, patch=None):
1826 """Print the header of the topmost or specified patch"""
1828 """Print the header of the topmost or specified patch"""
1827 q = repo.mq
1829 q = repo.mq
1828
1830
1829 if patch:
1831 if patch:
1830 patch = q.lookup(patch)
1832 patch = q.lookup(patch)
1831 else:
1833 else:
1832 if not q.applied:
1834 if not q.applied:
1833 ui.write('No patches applied\n')
1835 ui.write('No patches applied\n')
1834 return 1
1836 return 1
1835 patch = q.lookup('qtip')
1837 patch = q.lookup('qtip')
1836 message = repo.mq.readheaders(patch)[0]
1838 message = repo.mq.readheaders(patch)[0]
1837
1839
1838 ui.write('\n'.join(message) + '\n')
1840 ui.write('\n'.join(message) + '\n')
1839
1841
1840 def lastsavename(path):
1842 def lastsavename(path):
1841 (directory, base) = os.path.split(path)
1843 (directory, base) = os.path.split(path)
1842 names = os.listdir(directory)
1844 names = os.listdir(directory)
1843 namere = re.compile("%s.([0-9]+)" % base)
1845 namere = re.compile("%s.([0-9]+)" % base)
1844 maxindex = None
1846 maxindex = None
1845 maxname = None
1847 maxname = None
1846 for f in names:
1848 for f in names:
1847 m = namere.match(f)
1849 m = namere.match(f)
1848 if m:
1850 if m:
1849 index = int(m.group(1))
1851 index = int(m.group(1))
1850 if maxindex == None or index > maxindex:
1852 if maxindex == None or index > maxindex:
1851 maxindex = index
1853 maxindex = index
1852 maxname = f
1854 maxname = f
1853 if maxname:
1855 if maxname:
1854 return (os.path.join(directory, maxname), maxindex)
1856 return (os.path.join(directory, maxname), maxindex)
1855 return (None, None)
1857 return (None, None)
1856
1858
1857 def savename(path):
1859 def savename(path):
1858 (last, index) = lastsavename(path)
1860 (last, index) = lastsavename(path)
1859 if last is None:
1861 if last is None:
1860 index = 0
1862 index = 0
1861 newpath = path + ".%d" % (index + 1)
1863 newpath = path + ".%d" % (index + 1)
1862 return newpath
1864 return newpath
1863
1865
1864 def push(ui, repo, patch=None, **opts):
1866 def push(ui, repo, patch=None, **opts):
1865 """push the next patch onto the stack"""
1867 """push the next patch onto the stack"""
1866 q = repo.mq
1868 q = repo.mq
1867 mergeq = None
1869 mergeq = None
1868
1870
1869 if opts['all']:
1871 if opts['all']:
1870 if not q.series:
1872 if not q.series:
1871 ui.warn(_('no patches in series\n'))
1873 ui.warn(_('no patches in series\n'))
1872 return 0
1874 return 0
1873 patch = q.series[-1]
1875 patch = q.series[-1]
1874 if opts['merge']:
1876 if opts['merge']:
1875 if opts['name']:
1877 if opts['name']:
1876 newpath = opts['name']
1878 newpath = opts['name']
1877 else:
1879 else:
1878 newpath, i = lastsavename(q.path)
1880 newpath, i = lastsavename(q.path)
1879 if not newpath:
1881 if not newpath:
1880 ui.warn("no saved queues found, please use -n\n")
1882 ui.warn("no saved queues found, please use -n\n")
1881 return 1
1883 return 1
1882 mergeq = queue(ui, repo.join(""), newpath)
1884 mergeq = queue(ui, repo.join(""), newpath)
1883 ui.warn("merging with queue at: %s\n" % mergeq.path)
1885 ui.warn("merging with queue at: %s\n" % mergeq.path)
1884 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1886 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1885 mergeq=mergeq)
1887 mergeq=mergeq)
1886 return ret
1888 return ret
1887
1889
1888 def pop(ui, repo, patch=None, **opts):
1890 def pop(ui, repo, patch=None, **opts):
1889 """pop the current patch off the stack"""
1891 """pop the current patch off the stack"""
1890 localupdate = True
1892 localupdate = True
1891 if opts['name']:
1893 if opts['name']:
1892 q = queue(ui, repo.join(""), repo.join(opts['name']))
1894 q = queue(ui, repo.join(""), repo.join(opts['name']))
1893 ui.warn('using patch queue: %s\n' % q.path)
1895 ui.warn('using patch queue: %s\n' % q.path)
1894 localupdate = False
1896 localupdate = False
1895 else:
1897 else:
1896 q = repo.mq
1898 q = repo.mq
1897 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1899 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1898 all=opts['all'])
1900 all=opts['all'])
1899 q.save_dirty()
1901 q.save_dirty()
1900 return ret
1902 return ret
1901
1903
1902 def rename(ui, repo, patch, name=None, **opts):
1904 def rename(ui, repo, patch, name=None, **opts):
1903 """rename a patch
1905 """rename a patch
1904
1906
1905 With one argument, renames the current patch to PATCH1.
1907 With one argument, renames the current patch to PATCH1.
1906 With two arguments, renames PATCH1 to PATCH2."""
1908 With two arguments, renames PATCH1 to PATCH2."""
1907
1909
1908 q = repo.mq
1910 q = repo.mq
1909
1911
1910 if not name:
1912 if not name:
1911 name = patch
1913 name = patch
1912 patch = None
1914 patch = None
1913
1915
1914 if patch:
1916 if patch:
1915 patch = q.lookup(patch)
1917 patch = q.lookup(patch)
1916 else:
1918 else:
1917 if not q.applied:
1919 if not q.applied:
1918 ui.write(_('No patches applied\n'))
1920 ui.write(_('No patches applied\n'))
1919 return
1921 return
1920 patch = q.lookup('qtip')
1922 patch = q.lookup('qtip')
1921 absdest = q.join(name)
1923 absdest = q.join(name)
1922 if os.path.isdir(absdest):
1924 if os.path.isdir(absdest):
1923 name = normname(os.path.join(name, os.path.basename(patch)))
1925 name = normname(os.path.join(name, os.path.basename(patch)))
1924 absdest = q.join(name)
1926 absdest = q.join(name)
1925 if os.path.exists(absdest):
1927 if os.path.exists(absdest):
1926 raise util.Abort(_('%s already exists') % absdest)
1928 raise util.Abort(_('%s already exists') % absdest)
1927
1929
1928 if name in q.series:
1930 if name in q.series:
1929 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1931 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1930
1932
1931 if ui.verbose:
1933 if ui.verbose:
1932 ui.write('Renaming %s to %s\n' % (patch, name))
1934 ui.write('Renaming %s to %s\n' % (patch, name))
1933 i = q.find_series(patch)
1935 i = q.find_series(patch)
1934 guards = q.guard_re.findall(q.full_series[i])
1936 guards = q.guard_re.findall(q.full_series[i])
1935 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1937 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1936 q.parse_series()
1938 q.parse_series()
1937 q.series_dirty = 1
1939 q.series_dirty = 1
1938
1940
1939 info = q.isapplied(patch)
1941 info = q.isapplied(patch)
1940 if info:
1942 if info:
1941 q.applied[info[0]] = statusentry(info[1], name)
1943 q.applied[info[0]] = statusentry(info[1], name)
1942 q.applied_dirty = 1
1944 q.applied_dirty = 1
1943
1945
1944 util.rename(q.join(patch), absdest)
1946 util.rename(q.join(patch), absdest)
1945 r = q.qrepo()
1947 r = q.qrepo()
1946 if r:
1948 if r:
1947 wlock = r.wlock()
1949 wlock = r.wlock()
1948 if r.dirstate.state(name) == 'r':
1950 if r.dirstate.state(name) == 'r':
1949 r.undelete([name], wlock)
1951 r.undelete([name], wlock)
1950 r.copy(patch, name, wlock)
1952 r.copy(patch, name, wlock)
1951 r.remove([patch], False, wlock)
1953 r.remove([patch], False, wlock)
1952
1954
1953 q.save_dirty()
1955 q.save_dirty()
1954
1956
1955 def restore(ui, repo, rev, **opts):
1957 def restore(ui, repo, rev, **opts):
1956 """restore the queue state saved by a rev"""
1958 """restore the queue state saved by a rev"""
1957 rev = repo.lookup(rev)
1959 rev = repo.lookup(rev)
1958 q = repo.mq
1960 q = repo.mq
1959 q.restore(repo, rev, delete=opts['delete'],
1961 q.restore(repo, rev, delete=opts['delete'],
1960 qupdate=opts['update'])
1962 qupdate=opts['update'])
1961 q.save_dirty()
1963 q.save_dirty()
1962 return 0
1964 return 0
1963
1965
1964 def save(ui, repo, **opts):
1966 def save(ui, repo, **opts):
1965 """save current queue state"""
1967 """save current queue state"""
1966 q = repo.mq
1968 q = repo.mq
1967 message = cmdutil.logmessage(opts)
1969 message = cmdutil.logmessage(opts)
1968 ret = q.save(repo, msg=message)
1970 ret = q.save(repo, msg=message)
1969 if ret:
1971 if ret:
1970 return ret
1972 return ret
1971 q.save_dirty()
1973 q.save_dirty()
1972 if opts['copy']:
1974 if opts['copy']:
1973 path = q.path
1975 path = q.path
1974 if opts['name']:
1976 if opts['name']:
1975 newpath = os.path.join(q.basepath, opts['name'])
1977 newpath = os.path.join(q.basepath, opts['name'])
1976 if os.path.exists(newpath):
1978 if os.path.exists(newpath):
1977 if not os.path.isdir(newpath):
1979 if not os.path.isdir(newpath):
1978 raise util.Abort(_('destination %s exists and is not '
1980 raise util.Abort(_('destination %s exists and is not '
1979 'a directory') % newpath)
1981 'a directory') % newpath)
1980 if not opts['force']:
1982 if not opts['force']:
1981 raise util.Abort(_('destination %s exists, '
1983 raise util.Abort(_('destination %s exists, '
1982 'use -f to force') % newpath)
1984 'use -f to force') % newpath)
1983 else:
1985 else:
1984 newpath = savename(path)
1986 newpath = savename(path)
1985 ui.warn("copy %s to %s\n" % (path, newpath))
1987 ui.warn("copy %s to %s\n" % (path, newpath))
1986 util.copyfiles(path, newpath)
1988 util.copyfiles(path, newpath)
1987 if opts['empty']:
1989 if opts['empty']:
1988 try:
1990 try:
1989 os.unlink(q.join(q.status_path))
1991 os.unlink(q.join(q.status_path))
1990 except:
1992 except:
1991 pass
1993 pass
1992 return 0
1994 return 0
1993
1995
1994 def strip(ui, repo, rev, **opts):
1996 def strip(ui, repo, rev, **opts):
1995 """strip a revision and all later revs on the same branch"""
1997 """strip a revision and all later revs on the same branch"""
1996 rev = repo.lookup(rev)
1998 rev = repo.lookup(rev)
1997 backup = 'all'
1999 backup = 'all'
1998 if opts['backup']:
2000 if opts['backup']:
1999 backup = 'strip'
2001 backup = 'strip'
2000 elif opts['nobackup']:
2002 elif opts['nobackup']:
2001 backup = 'none'
2003 backup = 'none'
2002 update = repo.dirstate.parents()[0] != revlog.nullid
2004 update = repo.dirstate.parents()[0] != revlog.nullid
2003 repo.mq.strip(repo, rev, backup=backup, update=update)
2005 repo.mq.strip(repo, rev, backup=backup, update=update)
2004 return 0
2006 return 0
2005
2007
2006 def select(ui, repo, *args, **opts):
2008 def select(ui, repo, *args, **opts):
2007 '''set or print guarded patches to push
2009 '''set or print guarded patches to push
2008
2010
2009 Use the qguard command to set or print guards on patch, then use
2011 Use the qguard command to set or print guards on patch, then use
2010 qselect to tell mq which guards to use. A patch will be pushed if it
2012 qselect to tell mq which guards to use. A patch will be pushed if it
2011 has no guards or any positive guards match the currently selected guard,
2013 has no guards or any positive guards match the currently selected guard,
2012 but will not be pushed if any negative guards match the current guard.
2014 but will not be pushed if any negative guards match the current guard.
2013 For example:
2015 For example:
2014
2016
2015 qguard foo.patch -stable (negative guard)
2017 qguard foo.patch -stable (negative guard)
2016 qguard bar.patch +stable (positive guard)
2018 qguard bar.patch +stable (positive guard)
2017 qselect stable
2019 qselect stable
2018
2020
2019 This activates the "stable" guard. mq will skip foo.patch (because
2021 This activates the "stable" guard. mq will skip foo.patch (because
2020 it has a negative match) but push bar.patch (because it
2022 it has a negative match) but push bar.patch (because it
2021 has a positive match).
2023 has a positive match).
2022
2024
2023 With no arguments, prints the currently active guards.
2025 With no arguments, prints the currently active guards.
2024 With one argument, sets the active guard.
2026 With one argument, sets the active guard.
2025
2027
2026 Use -n/--none to deactivate guards (no other arguments needed).
2028 Use -n/--none to deactivate guards (no other arguments needed).
2027 When no guards are active, patches with positive guards are skipped
2029 When no guards are active, patches with positive guards are skipped
2028 and patches with negative guards are pushed.
2030 and patches with negative guards are pushed.
2029
2031
2030 qselect can change the guards on applied patches. It does not pop
2032 qselect can change the guards on applied patches. It does not pop
2031 guarded patches by default. Use --pop to pop back to the last applied
2033 guarded patches by default. Use --pop to pop back to the last applied
2032 patch that is not guarded. Use --reapply (which implies --pop) to push
2034 patch that is not guarded. Use --reapply (which implies --pop) to push
2033 back to the current patch afterwards, but skip guarded patches.
2035 back to the current patch afterwards, but skip guarded patches.
2034
2036
2035 Use -s/--series to print a list of all guards in the series file (no
2037 Use -s/--series to print a list of all guards in the series file (no
2036 other arguments needed). Use -v for more information.'''
2038 other arguments needed). Use -v for more information.'''
2037
2039
2038 q = repo.mq
2040 q = repo.mq
2039 guards = q.active()
2041 guards = q.active()
2040 if args or opts['none']:
2042 if args or opts['none']:
2041 old_unapplied = q.unapplied(repo)
2043 old_unapplied = q.unapplied(repo)
2042 old_guarded = [i for i in xrange(len(q.applied)) if
2044 old_guarded = [i for i in xrange(len(q.applied)) if
2043 not q.pushable(i)[0]]
2045 not q.pushable(i)[0]]
2044 q.set_active(args)
2046 q.set_active(args)
2045 q.save_dirty()
2047 q.save_dirty()
2046 if not args:
2048 if not args:
2047 ui.status(_('guards deactivated\n'))
2049 ui.status(_('guards deactivated\n'))
2048 if not opts['pop'] and not opts['reapply']:
2050 if not opts['pop'] and not opts['reapply']:
2049 unapplied = q.unapplied(repo)
2051 unapplied = q.unapplied(repo)
2050 guarded = [i for i in xrange(len(q.applied))
2052 guarded = [i for i in xrange(len(q.applied))
2051 if not q.pushable(i)[0]]
2053 if not q.pushable(i)[0]]
2052 if len(unapplied) != len(old_unapplied):
2054 if len(unapplied) != len(old_unapplied):
2053 ui.status(_('number of unguarded, unapplied patches has '
2055 ui.status(_('number of unguarded, unapplied patches has '
2054 'changed from %d to %d\n') %
2056 'changed from %d to %d\n') %
2055 (len(old_unapplied), len(unapplied)))
2057 (len(old_unapplied), len(unapplied)))
2056 if len(guarded) != len(old_guarded):
2058 if len(guarded) != len(old_guarded):
2057 ui.status(_('number of guarded, applied patches has changed '
2059 ui.status(_('number of guarded, applied patches has changed '
2058 'from %d to %d\n') %
2060 'from %d to %d\n') %
2059 (len(old_guarded), len(guarded)))
2061 (len(old_guarded), len(guarded)))
2060 elif opts['series']:
2062 elif opts['series']:
2061 guards = {}
2063 guards = {}
2062 noguards = 0
2064 noguards = 0
2063 for gs in q.series_guards:
2065 for gs in q.series_guards:
2064 if not gs:
2066 if not gs:
2065 noguards += 1
2067 noguards += 1
2066 for g in gs:
2068 for g in gs:
2067 guards.setdefault(g, 0)
2069 guards.setdefault(g, 0)
2068 guards[g] += 1
2070 guards[g] += 1
2069 if ui.verbose:
2071 if ui.verbose:
2070 guards['NONE'] = noguards
2072 guards['NONE'] = noguards
2071 guards = guards.items()
2073 guards = guards.items()
2072 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2074 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2073 if guards:
2075 if guards:
2074 ui.note(_('guards in series file:\n'))
2076 ui.note(_('guards in series file:\n'))
2075 for guard, count in guards:
2077 for guard, count in guards:
2076 ui.note('%2d ' % count)
2078 ui.note('%2d ' % count)
2077 ui.write(guard, '\n')
2079 ui.write(guard, '\n')
2078 else:
2080 else:
2079 ui.note(_('no guards in series file\n'))
2081 ui.note(_('no guards in series file\n'))
2080 else:
2082 else:
2081 if guards:
2083 if guards:
2082 ui.note(_('active guards:\n'))
2084 ui.note(_('active guards:\n'))
2083 for g in guards:
2085 for g in guards:
2084 ui.write(g, '\n')
2086 ui.write(g, '\n')
2085 else:
2087 else:
2086 ui.write(_('no active guards\n'))
2088 ui.write(_('no active guards\n'))
2087 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2089 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2088 popped = False
2090 popped = False
2089 if opts['pop'] or opts['reapply']:
2091 if opts['pop'] or opts['reapply']:
2090 for i in xrange(len(q.applied)):
2092 for i in xrange(len(q.applied)):
2091 pushable, reason = q.pushable(i)
2093 pushable, reason = q.pushable(i)
2092 if not pushable:
2094 if not pushable:
2093 ui.status(_('popping guarded patches\n'))
2095 ui.status(_('popping guarded patches\n'))
2094 popped = True
2096 popped = True
2095 if i == 0:
2097 if i == 0:
2096 q.pop(repo, all=True)
2098 q.pop(repo, all=True)
2097 else:
2099 else:
2098 q.pop(repo, i-1)
2100 q.pop(repo, i-1)
2099 break
2101 break
2100 if popped:
2102 if popped:
2101 try:
2103 try:
2102 if reapply:
2104 if reapply:
2103 ui.status(_('reapplying unguarded patches\n'))
2105 ui.status(_('reapplying unguarded patches\n'))
2104 q.push(repo, reapply)
2106 q.push(repo, reapply)
2105 finally:
2107 finally:
2106 q.save_dirty()
2108 q.save_dirty()
2107
2109
2108 def reposetup(ui, repo):
2110 def reposetup(ui, repo):
2109 class mqrepo(repo.__class__):
2111 class mqrepo(repo.__class__):
2110 def abort_if_wdir_patched(self, errmsg, force=False):
2112 def abort_if_wdir_patched(self, errmsg, force=False):
2111 if self.mq.applied and not force:
2113 if self.mq.applied and not force:
2112 parent = revlog.hex(self.dirstate.parents()[0])
2114 parent = revlog.hex(self.dirstate.parents()[0])
2113 if parent in [s.rev for s in self.mq.applied]:
2115 if parent in [s.rev for s in self.mq.applied]:
2114 raise util.Abort(errmsg)
2116 raise util.Abort(errmsg)
2115
2117
2116 def commit(self, *args, **opts):
2118 def commit(self, *args, **opts):
2117 if len(args) >= 6:
2119 if len(args) >= 6:
2118 force = args[5]
2120 force = args[5]
2119 else:
2121 else:
2120 force = opts.get('force')
2122 force = opts.get('force')
2121 self.abort_if_wdir_patched(
2123 self.abort_if_wdir_patched(
2122 _('cannot commit over an applied mq patch'),
2124 _('cannot commit over an applied mq patch'),
2123 force)
2125 force)
2124
2126
2125 return super(mqrepo, self).commit(*args, **opts)
2127 return super(mqrepo, self).commit(*args, **opts)
2126
2128
2127 def push(self, remote, force=False, revs=None):
2129 def push(self, remote, force=False, revs=None):
2128 if self.mq.applied and not force and not revs:
2130 if self.mq.applied and not force and not revs:
2129 raise util.Abort(_('source has mq patches applied'))
2131 raise util.Abort(_('source has mq patches applied'))
2130 return super(mqrepo, self).push(remote, force, revs)
2132 return super(mqrepo, self).push(remote, force, revs)
2131
2133
2132 def tags(self):
2134 def tags(self):
2133 if self.tagscache:
2135 if self.tagscache:
2134 return self.tagscache
2136 return self.tagscache
2135
2137
2136 tagscache = super(mqrepo, self).tags()
2138 tagscache = super(mqrepo, self).tags()
2137
2139
2138 q = self.mq
2140 q = self.mq
2139 if not q.applied:
2141 if not q.applied:
2140 return tagscache
2142 return tagscache
2141
2143
2142 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2144 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2143 mqtags.append((mqtags[-1][0], 'qtip'))
2145 mqtags.append((mqtags[-1][0], 'qtip'))
2144 mqtags.append((mqtags[0][0], 'qbase'))
2146 mqtags.append((mqtags[0][0], 'qbase'))
2145 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2147 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2146 for patch in mqtags:
2148 for patch in mqtags:
2147 if patch[1] in tagscache:
2149 if patch[1] in tagscache:
2148 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2150 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2149 else:
2151 else:
2150 tagscache[patch[1]] = patch[0]
2152 tagscache[patch[1]] = patch[0]
2151
2153
2152 return tagscache
2154 return tagscache
2153
2155
2154 def _branchtags(self):
2156 def _branchtags(self):
2155 q = self.mq
2157 q = self.mq
2156 if not q.applied:
2158 if not q.applied:
2157 return super(mqrepo, self)._branchtags()
2159 return super(mqrepo, self)._branchtags()
2158
2160
2159 self.branchcache = {} # avoid recursion in changectx
2161 self.branchcache = {} # avoid recursion in changectx
2160 cl = self.changelog
2162 cl = self.changelog
2161 partial, last, lrev = self._readbranchcache()
2163 partial, last, lrev = self._readbranchcache()
2162
2164
2163 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2165 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2164 start = lrev + 1
2166 start = lrev + 1
2165 if start < qbase:
2167 if start < qbase:
2166 # update the cache (excluding the patches) and save it
2168 # update the cache (excluding the patches) and save it
2167 self._updatebranchcache(partial, lrev+1, qbase)
2169 self._updatebranchcache(partial, lrev+1, qbase)
2168 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2170 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2169 start = qbase
2171 start = qbase
2170 # if start = qbase, the cache is as updated as it should be.
2172 # if start = qbase, the cache is as updated as it should be.
2171 # if start > qbase, the cache includes (part of) the patches.
2173 # if start > qbase, the cache includes (part of) the patches.
2172 # we might as well use it, but we won't save it.
2174 # we might as well use it, but we won't save it.
2173
2175
2174 # update the cache up to the tip
2176 # update the cache up to the tip
2175 self._updatebranchcache(partial, start, cl.count())
2177 self._updatebranchcache(partial, start, cl.count())
2176
2178
2177 return partial
2179 return partial
2178
2180
2179 if repo.local():
2181 if repo.local():
2180 repo.__class__ = mqrepo
2182 repo.__class__ = mqrepo
2181 repo.mq = queue(ui, repo.join(""))
2183 repo.mq = queue(ui, repo.join(""))
2182
2184
2183 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2185 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2184
2186
2185 cmdtable = {
2187 cmdtable = {
2186 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2188 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2187 "qclone": (clone,
2189 "qclone": (clone,
2188 [('', 'pull', None, _('use pull protocol to copy metadata')),
2190 [('', 'pull', None, _('use pull protocol to copy metadata')),
2189 ('U', 'noupdate', None, _('do not update the new working directories')),
2191 ('U', 'noupdate', None, _('do not update the new working directories')),
2190 ('', 'uncompressed', None,
2192 ('', 'uncompressed', None,
2191 _('use uncompressed transfer (fast over LAN)')),
2193 _('use uncompressed transfer (fast over LAN)')),
2192 ('e', 'ssh', '', _('specify ssh command to use')),
2194 ('e', 'ssh', '', _('specify ssh command to use')),
2193 ('p', 'patches', '', _('location of source patch repo')),
2195 ('p', 'patches', '', _('location of source patch repo')),
2194 ('', 'remotecmd', '',
2196 ('', 'remotecmd', '',
2195 _('specify hg command to run on the remote side'))],
2197 _('specify hg command to run on the remote side'))],
2196 'hg qclone [OPTION]... SOURCE [DEST]'),
2198 'hg qclone [OPTION]... SOURCE [DEST]'),
2197 "qcommit|qci":
2199 "qcommit|qci":
2198 (commit,
2200 (commit,
2199 commands.table["^commit|ci"][1],
2201 commands.table["^commit|ci"][1],
2200 'hg qcommit [OPTION]... [FILE]...'),
2202 'hg qcommit [OPTION]... [FILE]...'),
2201 "^qdiff": (diff,
2203 "^qdiff": (diff,
2202 [('g', 'git', None, _('use git extended diff format')),
2204 [('g', 'git', None, _('use git extended diff format')),
2203 ('I', 'include', [], _('include names matching the given patterns')),
2205 ('I', 'include', [], _('include names matching the given patterns')),
2204 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2206 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2205 'hg qdiff [-I] [-X] [FILE]...'),
2207 'hg qdiff [-I] [-X] [FILE]...'),
2206 "qdelete|qremove|qrm":
2208 "qdelete|qremove|qrm":
2207 (delete,
2209 (delete,
2208 [('k', 'keep', None, _('keep patch file')),
2210 [('k', 'keep', None, _('keep patch file')),
2209 ('r', 'rev', [], _('stop managing a revision'))],
2211 ('r', 'rev', [], _('stop managing a revision'))],
2210 'hg qdelete [-k] [-r REV]... PATCH...'),
2212 'hg qdelete [-k] [-r REV]... PATCH...'),
2211 'qfold':
2213 'qfold':
2212 (fold,
2214 (fold,
2213 [('e', 'edit', None, _('edit patch header')),
2215 [('e', 'edit', None, _('edit patch header')),
2214 ('k', 'keep', None, _('keep folded patch files'))
2216 ('k', 'keep', None, _('keep folded patch files'))
2215 ] + commands.commitopts,
2217 ] + commands.commitopts,
2216 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2218 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2217 'qgoto': (goto, [('f', 'force', None, _('overwrite any local changes'))],
2219 'qgoto': (goto, [('f', 'force', None, _('overwrite any local changes'))],
2218 'hg qgoto [OPT]... PATCH'),
2220 'hg qgoto [OPT]... PATCH'),
2219 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2221 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2220 ('n', 'none', None, _('drop all guards'))],
2222 ('n', 'none', None, _('drop all guards'))],
2221 'hg qguard [PATCH] [+GUARD]... [-GUARD]...'),
2223 'hg qguard [PATCH] [+GUARD]... [-GUARD]...'),
2222 'qheader': (header, [],
2224 'qheader': (header, [],
2223 _('hg qheader [PATCH]')),
2225 _('hg qheader [PATCH]')),
2224 "^qimport":
2226 "^qimport":
2225 (qimport,
2227 (qimport,
2226 [('e', 'existing', None, 'import file in patch dir'),
2228 [('e', 'existing', None, 'import file in patch dir'),
2227 ('n', 'name', '', 'patch file name'),
2229 ('n', 'name', '', 'patch file name'),
2228 ('f', 'force', None, 'overwrite existing files'),
2230 ('f', 'force', None, 'overwrite existing files'),
2229 ('r', 'rev', [], 'place existing revisions under mq control'),
2231 ('r', 'rev', [], 'place existing revisions under mq control'),
2230 ('g', 'git', None, _('use git extended diff format'))],
2232 ('g', 'git', None, _('use git extended diff format'))],
2231 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2233 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2232 "^qinit":
2234 "^qinit":
2233 (init,
2235 (init,
2234 [('c', 'create-repo', None, 'create queue repository')],
2236 [('c', 'create-repo', None, 'create queue repository')],
2235 'hg qinit [-c]'),
2237 'hg qinit [-c]'),
2236 "qnew":
2238 "qnew":
2237 (new,
2239 (new,
2238 [('e', 'edit', None, _('edit commit message')),
2240 [('e', 'edit', None, _('edit commit message')),
2239 ('f', 'force', None, _('import uncommitted changes into patch'))
2241 ('f', 'force', None, _('import uncommitted changes into patch'))
2240 ] + commands.commitopts,
2242 ] + commands.commitopts,
2241 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2243 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2242 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2244 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2243 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2245 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2244 "^qpop":
2246 "^qpop":
2245 (pop,
2247 (pop,
2246 [('a', 'all', None, 'pop all patches'),
2248 [('a', 'all', None, 'pop all patches'),
2247 ('n', 'name', '', 'queue name to pop'),
2249 ('n', 'name', '', 'queue name to pop'),
2248 ('f', 'force', None, 'forget any local changes')],
2250 ('f', 'force', None, 'forget any local changes')],
2249 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2251 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2250 "^qpush":
2252 "^qpush":
2251 (push,
2253 (push,
2252 [('f', 'force', None, 'apply if the patch has rejects'),
2254 [('f', 'force', None, 'apply if the patch has rejects'),
2253 ('l', 'list', None, 'list patch name in commit text'),
2255 ('l', 'list', None, 'list patch name in commit text'),
2254 ('a', 'all', None, 'apply all patches'),
2256 ('a', 'all', None, 'apply all patches'),
2255 ('m', 'merge', None, 'merge from another queue'),
2257 ('m', 'merge', None, 'merge from another queue'),
2256 ('n', 'name', '', 'merge queue name')],
2258 ('n', 'name', '', 'merge queue name')],
2257 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2259 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2258 "^qrefresh":
2260 "^qrefresh":
2259 (refresh,
2261 (refresh,
2260 [('e', 'edit', None, _('edit commit message')),
2262 [('e', 'edit', None, _('edit commit message')),
2261 ('g', 'git', None, _('use git extended diff format')),
2263 ('g', 'git', None, _('use git extended diff format')),
2262 ('s', 'short', None, 'refresh only files already in the patch'),
2264 ('s', 'short', None, 'refresh only files already in the patch'),
2263 ('I', 'include', [], _('include names matching the given patterns')),
2265 ('I', 'include', [], _('include names matching the given patterns')),
2264 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2266 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2265 ] + commands.commitopts,
2267 ] + commands.commitopts,
2266 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2268 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2267 'qrename|qmv':
2269 'qrename|qmv':
2268 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2270 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2269 "qrestore":
2271 "qrestore":
2270 (restore,
2272 (restore,
2271 [('d', 'delete', None, 'delete save entry'),
2273 [('d', 'delete', None, 'delete save entry'),
2272 ('u', 'update', None, 'update queue working dir')],
2274 ('u', 'update', None, 'update queue working dir')],
2273 'hg qrestore [-d] [-u] REV'),
2275 'hg qrestore [-d] [-u] REV'),
2274 "qsave":
2276 "qsave":
2275 (save,
2277 (save,
2276 [('c', 'copy', None, 'copy patch directory'),
2278 [('c', 'copy', None, 'copy patch directory'),
2277 ('n', 'name', '', 'copy directory name'),
2279 ('n', 'name', '', 'copy directory name'),
2278 ('e', 'empty', None, 'clear queue status file'),
2280 ('e', 'empty', None, 'clear queue status file'),
2279 ('f', 'force', None, 'force copy')] + commands.commitopts,
2281 ('f', 'force', None, 'force copy')] + commands.commitopts,
2280 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2282 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2281 "qselect": (select,
2283 "qselect": (select,
2282 [('n', 'none', None, _('disable all guards')),
2284 [('n', 'none', None, _('disable all guards')),
2283 ('s', 'series', None, _('list all guards in series file')),
2285 ('s', 'series', None, _('list all guards in series file')),
2284 ('', 'pop', None,
2286 ('', 'pop', None,
2285 _('pop to before first guarded applied patch')),
2287 _('pop to before first guarded applied patch')),
2286 ('', 'reapply', None, _('pop, then reapply patches'))],
2288 ('', 'reapply', None, _('pop, then reapply patches'))],
2287 'hg qselect [OPTION]... [GUARD]...'),
2289 'hg qselect [OPTION]... [GUARD]...'),
2288 "qseries":
2290 "qseries":
2289 (series,
2291 (series,
2290 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2292 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2291 'hg qseries [-ms]'),
2293 'hg qseries [-ms]'),
2292 "^strip":
2294 "^strip":
2293 (strip,
2295 (strip,
2294 [('f', 'force', None, 'force multi-head removal'),
2296 [('f', 'force', None, 'force multi-head removal'),
2295 ('b', 'backup', None, 'bundle unrelated changesets'),
2297 ('b', 'backup', None, 'bundle unrelated changesets'),
2296 ('n', 'nobackup', None, 'no backups')],
2298 ('n', 'nobackup', None, 'no backups')],
2297 'hg strip [-f] [-b] [-n] REV'),
2299 'hg strip [-f] [-b] [-n] REV'),
2298 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2300 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2299 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2301 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2300 }
2302 }
@@ -1,1204 +1,1202 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex
10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex
11 import mdiff, bdiff, util, templater, patch, commands, hg, lock
11 import mdiff, bdiff, util, templater, patch, commands, hg, lock
12 import fancyopts, revlog, version, extensions
12 import fancyopts, revlog, version, extensions
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 class UnknownCommand(Exception):
16 class UnknownCommand(Exception):
17 """Exception raised if command is not in the command table."""
17 """Exception raised if command is not in the command table."""
18 class AmbiguousCommand(Exception):
18 class AmbiguousCommand(Exception):
19 """Exception raised if command shortcut matches more than one command."""
19 """Exception raised if command shortcut matches more than one command."""
20 class ParseError(Exception):
20 class ParseError(Exception):
21 """Exception raised on errors in parsing the command line."""
21 """Exception raised on errors in parsing the command line."""
22
22
23 def runcatch(ui, args):
23 def runcatch(ui, args):
24 def catchterm(*args):
24 def catchterm(*args):
25 raise util.SignalInterrupt
25 raise util.SignalInterrupt
26
26
27 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
27 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
28 num = getattr(signal, name, None)
28 num = getattr(signal, name, None)
29 if num: signal.signal(num, catchterm)
29 if num: signal.signal(num, catchterm)
30
30
31 try:
31 try:
32 try:
32 try:
33 # enter the debugger before command execution
33 # enter the debugger before command execution
34 if '--debugger' in args:
34 if '--debugger' in args:
35 pdb.set_trace()
35 pdb.set_trace()
36 try:
36 try:
37 return dispatch(ui, args)
37 return dispatch(ui, args)
38 finally:
38 finally:
39 ui.flush()
39 ui.flush()
40 except:
40 except:
41 # enter the debugger when we hit an exception
41 # enter the debugger when we hit an exception
42 if '--debugger' in args:
42 if '--debugger' in args:
43 pdb.post_mortem(sys.exc_info()[2])
43 pdb.post_mortem(sys.exc_info()[2])
44 ui.print_exc()
44 ui.print_exc()
45 raise
45 raise
46
46
47 except ParseError, inst:
47 except ParseError, inst:
48 if inst.args[0]:
48 if inst.args[0]:
49 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
49 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
50 commands.help_(ui, inst.args[0])
50 commands.help_(ui, inst.args[0])
51 else:
51 else:
52 ui.warn(_("hg: %s\n") % inst.args[1])
52 ui.warn(_("hg: %s\n") % inst.args[1])
53 commands.help_(ui, 'shortlist')
53 commands.help_(ui, 'shortlist')
54 except AmbiguousCommand, inst:
54 except AmbiguousCommand, inst:
55 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
55 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
56 (inst.args[0], " ".join(inst.args[1])))
56 (inst.args[0], " ".join(inst.args[1])))
57 except UnknownCommand, inst:
57 except UnknownCommand, inst:
58 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
58 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
59 commands.help_(ui, 'shortlist')
59 commands.help_(ui, 'shortlist')
60 except hg.RepoError, inst:
60 except hg.RepoError, inst:
61 ui.warn(_("abort: %s!\n") % inst)
61 ui.warn(_("abort: %s!\n") % inst)
62 except lock.LockHeld, inst:
62 except lock.LockHeld, inst:
63 if inst.errno == errno.ETIMEDOUT:
63 if inst.errno == errno.ETIMEDOUT:
64 reason = _('timed out waiting for lock held by %s') % inst.locker
64 reason = _('timed out waiting for lock held by %s') % inst.locker
65 else:
65 else:
66 reason = _('lock held by %s') % inst.locker
66 reason = _('lock held by %s') % inst.locker
67 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
67 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
68 except lock.LockUnavailable, inst:
68 except lock.LockUnavailable, inst:
69 ui.warn(_("abort: could not lock %s: %s\n") %
69 ui.warn(_("abort: could not lock %s: %s\n") %
70 (inst.desc or inst.filename, inst.strerror))
70 (inst.desc or inst.filename, inst.strerror))
71 except revlog.RevlogError, inst:
71 except revlog.RevlogError, inst:
72 ui.warn(_("abort: %s!\n") % inst)
72 ui.warn(_("abort: %s!\n") % inst)
73 except util.SignalInterrupt:
73 except util.SignalInterrupt:
74 ui.warn(_("killed!\n"))
74 ui.warn(_("killed!\n"))
75 except KeyboardInterrupt:
75 except KeyboardInterrupt:
76 try:
76 try:
77 ui.warn(_("interrupted!\n"))
77 ui.warn(_("interrupted!\n"))
78 except IOError, inst:
78 except IOError, inst:
79 if inst.errno == errno.EPIPE:
79 if inst.errno == errno.EPIPE:
80 if ui.debugflag:
80 if ui.debugflag:
81 ui.warn(_("\nbroken pipe\n"))
81 ui.warn(_("\nbroken pipe\n"))
82 else:
82 else:
83 raise
83 raise
84 except socket.error, inst:
84 except socket.error, inst:
85 ui.warn(_("abort: %s\n") % inst[1])
85 ui.warn(_("abort: %s\n") % inst[1])
86 except IOError, inst:
86 except IOError, inst:
87 if hasattr(inst, "code"):
87 if hasattr(inst, "code"):
88 ui.warn(_("abort: %s\n") % inst)
88 ui.warn(_("abort: %s\n") % inst)
89 elif hasattr(inst, "reason"):
89 elif hasattr(inst, "reason"):
90 try: # usually it is in the form (errno, strerror)
90 try: # usually it is in the form (errno, strerror)
91 reason = inst.reason.args[1]
91 reason = inst.reason.args[1]
92 except: # it might be anything, for example a string
92 except: # it might be anything, for example a string
93 reason = inst.reason
93 reason = inst.reason
94 ui.warn(_("abort: error: %s\n") % reason)
94 ui.warn(_("abort: error: %s\n") % reason)
95 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
95 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
96 if ui.debugflag:
96 if ui.debugflag:
97 ui.warn(_("broken pipe\n"))
97 ui.warn(_("broken pipe\n"))
98 elif getattr(inst, "strerror", None):
98 elif getattr(inst, "strerror", None):
99 if getattr(inst, "filename", None):
99 if getattr(inst, "filename", None):
100 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
100 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
101 else:
101 else:
102 ui.warn(_("abort: %s\n") % inst.strerror)
102 ui.warn(_("abort: %s\n") % inst.strerror)
103 else:
103 else:
104 raise
104 raise
105 except OSError, inst:
105 except OSError, inst:
106 if getattr(inst, "filename", None):
106 if getattr(inst, "filename", None):
107 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
107 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
108 else:
108 else:
109 ui.warn(_("abort: %s\n") % inst.strerror)
109 ui.warn(_("abort: %s\n") % inst.strerror)
110 except util.UnexpectedOutput, inst:
110 except util.UnexpectedOutput, inst:
111 ui.warn(_("abort: %s") % inst[0])
111 ui.warn(_("abort: %s") % inst[0])
112 if not isinstance(inst[1], basestring):
112 if not isinstance(inst[1], basestring):
113 ui.warn(" %r\n" % (inst[1],))
113 ui.warn(" %r\n" % (inst[1],))
114 elif not inst[1]:
114 elif not inst[1]:
115 ui.warn(_(" empty string\n"))
115 ui.warn(_(" empty string\n"))
116 else:
116 else:
117 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
117 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
118 except util.Abort, inst:
118 except util.Abort, inst:
119 ui.warn(_("abort: %s\n") % inst)
119 ui.warn(_("abort: %s\n") % inst)
120 except TypeError, inst:
120 except TypeError, inst:
121 # was this an argument error?
121 # was this an argument error?
122 tb = traceback.extract_tb(sys.exc_info()[2])
122 tb = traceback.extract_tb(sys.exc_info()[2])
123 if len(tb) > 2: # no
123 if len(tb) > 2: # no
124 raise
124 raise
125 ui.debug(inst, "\n")
125 ui.debug(inst, "\n")
126 ui.warn(_("%s: invalid arguments\n") % cmd)
126 ui.warn(_("%s: invalid arguments\n") % cmd)
127 commands.help_(ui, cmd)
127 commands.help_(ui, cmd)
128 except SystemExit, inst:
128 except SystemExit, inst:
129 # Commands shouldn't sys.exit directly, but give a return code.
129 # Commands shouldn't sys.exit directly, but give a return code.
130 # Just in case catch this and and pass exit code to caller.
130 # Just in case catch this and and pass exit code to caller.
131 return inst.code
131 return inst.code
132 except:
132 except:
133 ui.warn(_("** unknown exception encountered, details follow\n"))
133 ui.warn(_("** unknown exception encountered, details follow\n"))
134 ui.warn(_("** report bug details to "
134 ui.warn(_("** report bug details to "
135 "http://www.selenic.com/mercurial/bts\n"))
135 "http://www.selenic.com/mercurial/bts\n"))
136 ui.warn(_("** or mercurial@selenic.com\n"))
136 ui.warn(_("** or mercurial@selenic.com\n"))
137 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
137 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
138 % version.get_version())
138 % version.get_version())
139 raise
139 raise
140
140
141 return -1
141 return -1
142
142
143 def findpossible(ui, cmd):
143 def findpossible(ui, cmd):
144 """
144 """
145 Return cmd -> (aliases, command table entry)
145 Return cmd -> (aliases, command table entry)
146 for each matching command.
146 for each matching command.
147 Return debug commands (or their aliases) only if no normal command matches.
147 Return debug commands (or their aliases) only if no normal command matches.
148 """
148 """
149 choice = {}
149 choice = {}
150 debugchoice = {}
150 debugchoice = {}
151 for e in commands.table.keys():
151 for e in commands.table.keys():
152 aliases = e.lstrip("^").split("|")
152 aliases = e.lstrip("^").split("|")
153 found = None
153 found = None
154 if cmd in aliases:
154 if cmd in aliases:
155 found = cmd
155 found = cmd
156 elif not ui.config("ui", "strict"):
156 elif not ui.config("ui", "strict"):
157 for a in aliases:
157 for a in aliases:
158 if a.startswith(cmd):
158 if a.startswith(cmd):
159 found = a
159 found = a
160 break
160 break
161 if found is not None:
161 if found is not None:
162 if aliases[0].startswith("debug") or found.startswith("debug"):
162 if aliases[0].startswith("debug") or found.startswith("debug"):
163 debugchoice[found] = (aliases, commands.table[e])
163 debugchoice[found] = (aliases, commands.table[e])
164 else:
164 else:
165 choice[found] = (aliases, commands.table[e])
165 choice[found] = (aliases, commands.table[e])
166
166
167 if not choice and debugchoice:
167 if not choice and debugchoice:
168 choice = debugchoice
168 choice = debugchoice
169
169
170 return choice
170 return choice
171
171
172 def findcmd(ui, cmd):
172 def findcmd(ui, cmd):
173 """Return (aliases, command table entry) for command string."""
173 """Return (aliases, command table entry) for command string."""
174 choice = findpossible(ui, cmd)
174 choice = findpossible(ui, cmd)
175
175
176 if choice.has_key(cmd):
176 if choice.has_key(cmd):
177 return choice[cmd]
177 return choice[cmd]
178
178
179 if len(choice) > 1:
179 if len(choice) > 1:
180 clist = choice.keys()
180 clist = choice.keys()
181 clist.sort()
181 clist.sort()
182 raise AmbiguousCommand(cmd, clist)
182 raise AmbiguousCommand(cmd, clist)
183
183
184 if choice:
184 if choice:
185 return choice.values()[0]
185 return choice.values()[0]
186
186
187 raise UnknownCommand(cmd)
187 raise UnknownCommand(cmd)
188
188
189 def findrepo():
189 def findrepo():
190 p = os.getcwd()
190 p = os.getcwd()
191 while not os.path.isdir(os.path.join(p, ".hg")):
191 while not os.path.isdir(os.path.join(p, ".hg")):
192 oldp, p = p, os.path.dirname(p)
192 oldp, p = p, os.path.dirname(p)
193 if p == oldp:
193 if p == oldp:
194 return None
194 return None
195
195
196 return p
196 return p
197
197
198 def parse(ui, args):
198 def parse(ui, args):
199 options = {}
199 options = {}
200 cmdoptions = {}
200 cmdoptions = {}
201
201
202 try:
202 try:
203 args = fancyopts.fancyopts(args, commands.globalopts, options)
203 args = fancyopts.fancyopts(args, commands.globalopts, options)
204 except fancyopts.getopt.GetoptError, inst:
204 except fancyopts.getopt.GetoptError, inst:
205 raise ParseError(None, inst)
205 raise ParseError(None, inst)
206
206
207 if args:
207 if args:
208 cmd, args = args[0], args[1:]
208 cmd, args = args[0], args[1:]
209 aliases, i = findcmd(ui, cmd)
209 aliases, i = findcmd(ui, cmd)
210 cmd = aliases[0]
210 cmd = aliases[0]
211 defaults = ui.config("defaults", cmd)
211 defaults = ui.config("defaults", cmd)
212 if defaults:
212 if defaults:
213 args = shlex.split(defaults) + args
213 args = shlex.split(defaults) + args
214 c = list(i[1])
214 c = list(i[1])
215 else:
215 else:
216 cmd = None
216 cmd = None
217 c = []
217 c = []
218
218
219 # combine global options into local
219 # combine global options into local
220 for o in commands.globalopts:
220 for o in commands.globalopts:
221 c.append((o[0], o[1], options[o[1]], o[3]))
221 c.append((o[0], o[1], options[o[1]], o[3]))
222
222
223 try:
223 try:
224 args = fancyopts.fancyopts(args, c, cmdoptions)
224 args = fancyopts.fancyopts(args, c, cmdoptions)
225 except fancyopts.getopt.GetoptError, inst:
225 except fancyopts.getopt.GetoptError, inst:
226 raise ParseError(cmd, inst)
226 raise ParseError(cmd, inst)
227
227
228 # separate global options back out
228 # separate global options back out
229 for o in commands.globalopts:
229 for o in commands.globalopts:
230 n = o[1]
230 n = o[1]
231 options[n] = cmdoptions[n]
231 options[n] = cmdoptions[n]
232 del cmdoptions[n]
232 del cmdoptions[n]
233
233
234 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
234 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
235
235
236 def parseconfig(config):
236 def parseconfig(config):
237 """parse the --config options from the command line"""
237 """parse the --config options from the command line"""
238 parsed = []
238 parsed = []
239 for cfg in config:
239 for cfg in config:
240 try:
240 try:
241 name, value = cfg.split('=', 1)
241 name, value = cfg.split('=', 1)
242 section, name = name.split('.', 1)
242 section, name = name.split('.', 1)
243 if not section or not name:
243 if not section or not name:
244 raise IndexError
244 raise IndexError
245 parsed.append((section, name, value))
245 parsed.append((section, name, value))
246 except (IndexError, ValueError):
246 except (IndexError, ValueError):
247 raise util.Abort(_('malformed --config option: %s') % cfg)
247 raise util.Abort(_('malformed --config option: %s') % cfg)
248 return parsed
248 return parsed
249
249
250 def earlygetopt(aliases, args):
250 def earlygetopt(aliases, args):
251 if "--" in args:
251 if "--" in args:
252 args = args[:args.index("--")]
252 args = args[:args.index("--")]
253 for opt in aliases:
253 for opt in aliases:
254 if opt in args:
254 if opt in args:
255 return args[args.index(opt) + 1]
255 return args[args.index(opt) + 1]
256 return None
256 return None
257
257
258 def dispatch(ui, args):
258 def dispatch(ui, args):
259 # check for cwd first
259 # check for cwd first
260 cwd = earlygetopt(['--cwd'], args)
260 cwd = earlygetopt(['--cwd'], args)
261 if cwd:
261 if cwd:
262 os.chdir(cwd)
262 os.chdir(cwd)
263
263
264 extensions.loadall(ui)
264 extensions.loadall(ui)
265 ui.addreadhook(extensions.loadall)
265 ui.addreadhook(extensions.loadall)
266
266
267 # read the local repository .hgrc into a local ui object
267 # read the local repository .hgrc into a local ui object
268 # this will trigger its extensions to load
268 # this will trigger its extensions to load
269 path = earlygetopt(["-R", "--repository", "--repo"], args)
269 path = earlygetopt(["-R", "--repository", "--repo"], args)
270 if not path:
270 if not path:
271 path = findrepo() or ""
271 path = findrepo() or ""
272 if path:
272 if path:
273 try:
273 try:
274 lui = commands.ui.ui(parentui=ui)
274 lui = commands.ui.ui(parentui=ui)
275 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
275 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
276 except IOError:
276 except IOError:
277 pass
277 pass
278
278
279 cmd, func, args, options, cmdoptions = parse(ui, args)
279 cmd, func, args, options, cmdoptions = parse(ui, args)
280
280
281 if options["encoding"]:
281 if options["encoding"]:
282 util._encoding = options["encoding"]
282 util._encoding = options["encoding"]
283 if options["encodingmode"]:
283 if options["encodingmode"]:
284 util._encodingmode = options["encodingmode"]
284 util._encodingmode = options["encodingmode"]
285 if options["time"]:
285 if options["time"]:
286 def get_times():
286 def get_times():
287 t = os.times()
287 t = os.times()
288 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
288 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
289 t = (t[0], t[1], t[2], t[3], time.clock())
289 t = (t[0], t[1], t[2], t[3], time.clock())
290 return t
290 return t
291 s = get_times()
291 s = get_times()
292 def print_time():
292 def print_time():
293 t = get_times()
293 t = get_times()
294 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
294 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
295 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
295 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
296 atexit.register(print_time)
296 atexit.register(print_time)
297
297
298 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
298 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
299 not options["noninteractive"], options["traceback"],
299 not options["noninteractive"], options["traceback"],
300 parseconfig(options["config"]))
300 parseconfig(options["config"]))
301
301
302 if options['help']:
302 if options['help']:
303 return commands.help_(ui, cmd, options['version'])
303 return commands.help_(ui, cmd, options['version'])
304 elif options['version']:
304 elif options['version']:
305 return commands.version_(ui)
305 return commands.version_(ui)
306 elif not cmd:
306 elif not cmd:
307 return commands.help_(ui, 'shortlist')
307 return commands.help_(ui, 'shortlist')
308
308
309 if cmd not in commands.norepo.split():
309 if cmd not in commands.norepo.split():
310 repo = None
310 repo = None
311 try:
311 try:
312 repo = hg.repository(ui, path=path)
312 repo = hg.repository(ui, path=path)
313 ui = repo.ui
313 ui = repo.ui
314 if not repo.local():
314 if not repo.local():
315 raise util.Abort(_("repository '%s' is not local") % path)
315 raise util.Abort(_("repository '%s' is not local") % path)
316 except hg.RepoError:
316 except hg.RepoError:
317 if cmd not in commands.optionalrepo.split():
317 if cmd not in commands.optionalrepo.split():
318 raise
318 raise
319 d = lambda: func(ui, repo, *args, **cmdoptions)
319 d = lambda: func(ui, repo, *args, **cmdoptions)
320 else:
320 else:
321 d = lambda: func(ui, *args, **cmdoptions)
321 d = lambda: func(ui, *args, **cmdoptions)
322
322
323 return runcommand(ui, options, d)
323 return runcommand(ui, options, d)
324
324
325 def runcommand(ui, options, cmdfunc):
325 def runcommand(ui, options, cmdfunc):
326 if options['profile']:
326 if options['profile']:
327 import hotshot, hotshot.stats
327 import hotshot, hotshot.stats
328 prof = hotshot.Profile("hg.prof")
328 prof = hotshot.Profile("hg.prof")
329 try:
329 try:
330 try:
330 try:
331 return prof.runcall(cmdfunc)
331 return prof.runcall(cmdfunc)
332 except:
332 except:
333 try:
333 try:
334 ui.warn(_('exception raised - generating '
334 ui.warn(_('exception raised - generating '
335 'profile anyway\n'))
335 'profile anyway\n'))
336 except:
336 except:
337 pass
337 pass
338 raise
338 raise
339 finally:
339 finally:
340 prof.close()
340 prof.close()
341 stats = hotshot.stats.load("hg.prof")
341 stats = hotshot.stats.load("hg.prof")
342 stats.strip_dirs()
342 stats.strip_dirs()
343 stats.sort_stats('time', 'calls')
343 stats.sort_stats('time', 'calls')
344 stats.print_stats(40)
344 stats.print_stats(40)
345 elif options['lsprof']:
345 elif options['lsprof']:
346 try:
346 try:
347 from mercurial import lsprof
347 from mercurial import lsprof
348 except ImportError:
348 except ImportError:
349 raise util.Abort(_(
349 raise util.Abort(_(
350 'lsprof not available - install from '
350 'lsprof not available - install from '
351 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
351 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
352 p = lsprof.Profiler()
352 p = lsprof.Profiler()
353 p.enable(subcalls=True)
353 p.enable(subcalls=True)
354 try:
354 try:
355 return cmdfunc()
355 return cmdfunc()
356 finally:
356 finally:
357 p.disable()
357 p.disable()
358 stats = lsprof.Stats(p.getstats())
358 stats = lsprof.Stats(p.getstats())
359 stats.sort()
359 stats.sort()
360 stats.pprint(top=10, file=sys.stderr, climit=5)
360 stats.pprint(top=10, file=sys.stderr, climit=5)
361 else:
361 else:
362 return cmdfunc()
362 return cmdfunc()
363
363
364 def bail_if_changed(repo):
364 def bail_if_changed(repo):
365 modified, added, removed, deleted = repo.status()[:4]
365 modified, added, removed, deleted = repo.status()[:4]
366 if modified or added or removed or deleted:
366 if modified or added or removed or deleted:
367 raise util.Abort(_("outstanding uncommitted changes"))
367 raise util.Abort(_("outstanding uncommitted changes"))
368
368
369 def logmessage(opts):
369 def logmessage(opts):
370 """ get the log message according to -m and -l option """
370 """ get the log message according to -m and -l option """
371 message = opts['message']
371 message = opts['message']
372 logfile = opts['logfile']
372 logfile = opts['logfile']
373
373
374 if message and logfile:
374 if message and logfile:
375 raise util.Abort(_('options --message and --logfile are mutually '
375 raise util.Abort(_('options --message and --logfile are mutually '
376 'exclusive'))
376 'exclusive'))
377 if not message and logfile:
377 if not message and logfile:
378 try:
378 try:
379 if logfile == '-':
379 if logfile == '-':
380 message = sys.stdin.read()
380 message = sys.stdin.read()
381 else:
381 else:
382 message = open(logfile).read()
382 message = open(logfile).read()
383 except IOError, inst:
383 except IOError, inst:
384 raise util.Abort(_("can't read commit message '%s': %s") %
384 raise util.Abort(_("can't read commit message '%s': %s") %
385 (logfile, inst.strerror))
385 (logfile, inst.strerror))
386 return message
386 return message
387
387
388 def setremoteconfig(ui, opts):
388 def setremoteconfig(ui, opts):
389 "copy remote options to ui tree"
389 "copy remote options to ui tree"
390 if opts.get('ssh'):
390 if opts.get('ssh'):
391 ui.setconfig("ui", "ssh", opts['ssh'])
391 ui.setconfig("ui", "ssh", opts['ssh'])
392 if opts.get('remotecmd'):
392 if opts.get('remotecmd'):
393 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
393 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
394
394
395 def parseurl(url, revs):
395 def parseurl(url, revs):
396 '''parse url#branch, returning url, branch + revs'''
396 '''parse url#branch, returning url, branch + revs'''
397
397
398 if '#' not in url:
398 if '#' not in url:
399 return url, (revs or None)
399 return url, (revs or None)
400
400
401 url, rev = url.split('#', 1)
401 url, rev = url.split('#', 1)
402 return url, revs + [rev]
402 return url, revs + [rev]
403
403
404 def revpair(repo, revs):
404 def revpair(repo, revs):
405 '''return pair of nodes, given list of revisions. second item can
405 '''return pair of nodes, given list of revisions. second item can
406 be None, meaning use working dir.'''
406 be None, meaning use working dir.'''
407
407
408 def revfix(repo, val, defval):
408 def revfix(repo, val, defval):
409 if not val and val != 0 and defval is not None:
409 if not val and val != 0 and defval is not None:
410 val = defval
410 val = defval
411 return repo.lookup(val)
411 return repo.lookup(val)
412
412
413 if not revs:
413 if not revs:
414 return repo.dirstate.parents()[0], None
414 return repo.dirstate.parents()[0], None
415 end = None
415 end = None
416 if len(revs) == 1:
416 if len(revs) == 1:
417 if revrangesep in revs[0]:
417 if revrangesep in revs[0]:
418 start, end = revs[0].split(revrangesep, 1)
418 start, end = revs[0].split(revrangesep, 1)
419 start = revfix(repo, start, 0)
419 start = revfix(repo, start, 0)
420 end = revfix(repo, end, repo.changelog.count() - 1)
420 end = revfix(repo, end, repo.changelog.count() - 1)
421 else:
421 else:
422 start = revfix(repo, revs[0], None)
422 start = revfix(repo, revs[0], None)
423 elif len(revs) == 2:
423 elif len(revs) == 2:
424 if revrangesep in revs[0] or revrangesep in revs[1]:
424 if revrangesep in revs[0] or revrangesep in revs[1]:
425 raise util.Abort(_('too many revisions specified'))
425 raise util.Abort(_('too many revisions specified'))
426 start = revfix(repo, revs[0], None)
426 start = revfix(repo, revs[0], None)
427 end = revfix(repo, revs[1], None)
427 end = revfix(repo, revs[1], None)
428 else:
428 else:
429 raise util.Abort(_('too many revisions specified'))
429 raise util.Abort(_('too many revisions specified'))
430 return start, end
430 return start, end
431
431
432 def revrange(repo, revs):
432 def revrange(repo, revs):
433 """Yield revision as strings from a list of revision specifications."""
433 """Yield revision as strings from a list of revision specifications."""
434
434
435 def revfix(repo, val, defval):
435 def revfix(repo, val, defval):
436 if not val and val != 0 and defval is not None:
436 if not val and val != 0 and defval is not None:
437 return defval
437 return defval
438 return repo.changelog.rev(repo.lookup(val))
438 return repo.changelog.rev(repo.lookup(val))
439
439
440 seen, l = {}, []
440 seen, l = {}, []
441 for spec in revs:
441 for spec in revs:
442 if revrangesep in spec:
442 if revrangesep in spec:
443 start, end = spec.split(revrangesep, 1)
443 start, end = spec.split(revrangesep, 1)
444 start = revfix(repo, start, 0)
444 start = revfix(repo, start, 0)
445 end = revfix(repo, end, repo.changelog.count() - 1)
445 end = revfix(repo, end, repo.changelog.count() - 1)
446 step = start > end and -1 or 1
446 step = start > end and -1 or 1
447 for rev in xrange(start, end+step, step):
447 for rev in xrange(start, end+step, step):
448 if rev in seen:
448 if rev in seen:
449 continue
449 continue
450 seen[rev] = 1
450 seen[rev] = 1
451 l.append(rev)
451 l.append(rev)
452 else:
452 else:
453 rev = revfix(repo, spec, None)
453 rev = revfix(repo, spec, None)
454 if rev in seen:
454 if rev in seen:
455 continue
455 continue
456 seen[rev] = 1
456 seen[rev] = 1
457 l.append(rev)
457 l.append(rev)
458
458
459 return l
459 return l
460
460
461 def make_filename(repo, pat, node,
461 def make_filename(repo, pat, node,
462 total=None, seqno=None, revwidth=None, pathname=None):
462 total=None, seqno=None, revwidth=None, pathname=None):
463 node_expander = {
463 node_expander = {
464 'H': lambda: hex(node),
464 'H': lambda: hex(node),
465 'R': lambda: str(repo.changelog.rev(node)),
465 'R': lambda: str(repo.changelog.rev(node)),
466 'h': lambda: short(node),
466 'h': lambda: short(node),
467 }
467 }
468 expander = {
468 expander = {
469 '%': lambda: '%',
469 '%': lambda: '%',
470 'b': lambda: os.path.basename(repo.root),
470 'b': lambda: os.path.basename(repo.root),
471 }
471 }
472
472
473 try:
473 try:
474 if node:
474 if node:
475 expander.update(node_expander)
475 expander.update(node_expander)
476 if node and revwidth is not None:
476 if node and revwidth is not None:
477 expander['r'] = (lambda:
477 expander['r'] = (lambda:
478 str(repo.changelog.rev(node)).zfill(revwidth))
478 str(repo.changelog.rev(node)).zfill(revwidth))
479 if total is not None:
479 if total is not None:
480 expander['N'] = lambda: str(total)
480 expander['N'] = lambda: str(total)
481 if seqno is not None:
481 if seqno is not None:
482 expander['n'] = lambda: str(seqno)
482 expander['n'] = lambda: str(seqno)
483 if total is not None and seqno is not None:
483 if total is not None and seqno is not None:
484 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
484 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
485 if pathname is not None:
485 if pathname is not None:
486 expander['s'] = lambda: os.path.basename(pathname)
486 expander['s'] = lambda: os.path.basename(pathname)
487 expander['d'] = lambda: os.path.dirname(pathname) or '.'
487 expander['d'] = lambda: os.path.dirname(pathname) or '.'
488 expander['p'] = lambda: pathname
488 expander['p'] = lambda: pathname
489
489
490 newname = []
490 newname = []
491 patlen = len(pat)
491 patlen = len(pat)
492 i = 0
492 i = 0
493 while i < patlen:
493 while i < patlen:
494 c = pat[i]
494 c = pat[i]
495 if c == '%':
495 if c == '%':
496 i += 1
496 i += 1
497 c = pat[i]
497 c = pat[i]
498 c = expander[c]()
498 c = expander[c]()
499 newname.append(c)
499 newname.append(c)
500 i += 1
500 i += 1
501 return ''.join(newname)
501 return ''.join(newname)
502 except KeyError, inst:
502 except KeyError, inst:
503 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
503 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
504 inst.args[0])
504 inst.args[0])
505
505
506 def make_file(repo, pat, node=None,
506 def make_file(repo, pat, node=None,
507 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
507 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
508 if not pat or pat == '-':
508 if not pat or pat == '-':
509 return 'w' in mode and sys.stdout or sys.stdin
509 return 'w' in mode and sys.stdout or sys.stdin
510 if hasattr(pat, 'write') and 'w' in mode:
510 if hasattr(pat, 'write') and 'w' in mode:
511 return pat
511 return pat
512 if hasattr(pat, 'read') and 'r' in mode:
512 if hasattr(pat, 'read') and 'r' in mode:
513 return pat
513 return pat
514 return open(make_filename(repo, pat, node, total, seqno, revwidth,
514 return open(make_filename(repo, pat, node, total, seqno, revwidth,
515 pathname),
515 pathname),
516 mode)
516 mode)
517
517
518 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
518 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
519 cwd = repo.getcwd()
519 cwd = repo.getcwd()
520 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
520 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
521 opts.get('exclude'), globbed=globbed,
521 opts.get('exclude'), globbed=globbed,
522 default=default)
522 default=default)
523
523
524 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
524 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
525 default=None):
525 default=None):
526 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
526 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
527 default=default)
527 default=default)
528 exact = dict.fromkeys(files)
528 exact = dict.fromkeys(files)
529 cwd = repo.getcwd()
529 cwd = repo.getcwd()
530 for src, fn in repo.walk(node=node, files=files, match=matchfn,
530 for src, fn in repo.walk(node=node, files=files, match=matchfn,
531 badmatch=badmatch):
531 badmatch=badmatch):
532 yield src, fn, repo.pathto(fn, cwd), fn in exact
532 yield src, fn, repo.pathto(fn, cwd), fn in exact
533
533
534 def findrenames(repo, added=None, removed=None, threshold=0.5):
534 def findrenames(repo, added=None, removed=None, threshold=0.5):
535 '''find renamed files -- yields (before, after, score) tuples'''
535 '''find renamed files -- yields (before, after, score) tuples'''
536 if added is None or removed is None:
536 if added is None or removed is None:
537 added, removed = repo.status()[1:3]
537 added, removed = repo.status()[1:3]
538 ctx = repo.changectx()
538 ctx = repo.changectx()
539 for a in added:
539 for a in added:
540 aa = repo.wread(a)
540 aa = repo.wread(a)
541 bestname, bestscore = None, threshold
541 bestname, bestscore = None, threshold
542 for r in removed:
542 for r in removed:
543 rr = ctx.filectx(r).data()
543 rr = ctx.filectx(r).data()
544
544
545 # bdiff.blocks() returns blocks of matching lines
545 # bdiff.blocks() returns blocks of matching lines
546 # count the number of bytes in each
546 # count the number of bytes in each
547 equal = 0
547 equal = 0
548 alines = mdiff.splitnewlines(aa)
548 alines = mdiff.splitnewlines(aa)
549 matches = bdiff.blocks(aa, rr)
549 matches = bdiff.blocks(aa, rr)
550 for x1,x2,y1,y2 in matches:
550 for x1,x2,y1,y2 in matches:
551 for line in alines[x1:x2]:
551 for line in alines[x1:x2]:
552 equal += len(line)
552 equal += len(line)
553
553
554 lengths = len(aa) + len(rr)
554 lengths = len(aa) + len(rr)
555 if lengths:
555 if lengths:
556 myscore = equal*2.0 / lengths
556 myscore = equal*2.0 / lengths
557 if myscore >= bestscore:
557 if myscore >= bestscore:
558 bestname, bestscore = r, myscore
558 bestname, bestscore = r, myscore
559 if bestname:
559 if bestname:
560 yield bestname, a, bestscore
560 yield bestname, a, bestscore
561
561
562 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
562 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
563 similarity=None):
563 similarity=None):
564 if dry_run is None:
564 if dry_run is None:
565 dry_run = opts.get('dry_run')
565 dry_run = opts.get('dry_run')
566 if similarity is None:
566 if similarity is None:
567 similarity = float(opts.get('similarity') or 0)
567 similarity = float(opts.get('similarity') or 0)
568 add, remove = [], []
568 add, remove = [], []
569 mapping = {}
569 mapping = {}
570 for src, abs, rel, exact in walk(repo, pats, opts):
570 for src, abs, rel, exact in walk(repo, pats, opts):
571 target = repo.wjoin(abs)
571 target = repo.wjoin(abs)
572 if src == 'f' and repo.dirstate.state(abs) == '?':
572 if src == 'f' and repo.dirstate.state(abs) == '?':
573 add.append(abs)
573 add.append(abs)
574 mapping[abs] = rel, exact
574 mapping[abs] = rel, exact
575 if repo.ui.verbose or not exact:
575 if repo.ui.verbose or not exact:
576 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
576 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
577 islink = os.path.islink(target)
577 if repo.dirstate.state(abs) != 'r' and not util.lexists(target):
578 if (repo.dirstate.state(abs) != 'r' and not islink
579 and not os.path.exists(target)):
580 remove.append(abs)
578 remove.append(abs)
581 mapping[abs] = rel, exact
579 mapping[abs] = rel, exact
582 if repo.ui.verbose or not exact:
580 if repo.ui.verbose or not exact:
583 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
581 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
584 if not dry_run:
582 if not dry_run:
585 repo.add(add, wlock=wlock)
583 repo.add(add, wlock=wlock)
586 repo.remove(remove, wlock=wlock)
584 repo.remove(remove, wlock=wlock)
587 if similarity > 0:
585 if similarity > 0:
588 for old, new, score in findrenames(repo, add, remove, similarity):
586 for old, new, score in findrenames(repo, add, remove, similarity):
589 oldrel, oldexact = mapping[old]
587 oldrel, oldexact = mapping[old]
590 newrel, newexact = mapping[new]
588 newrel, newexact = mapping[new]
591 if repo.ui.verbose or not oldexact or not newexact:
589 if repo.ui.verbose or not oldexact or not newexact:
592 repo.ui.status(_('recording removal of %s as rename to %s '
590 repo.ui.status(_('recording removal of %s as rename to %s '
593 '(%d%% similar)\n') %
591 '(%d%% similar)\n') %
594 (oldrel, newrel, score * 100))
592 (oldrel, newrel, score * 100))
595 if not dry_run:
593 if not dry_run:
596 repo.copy(old, new, wlock=wlock)
594 repo.copy(old, new, wlock=wlock)
597
595
598 def service(opts, parentfn=None, initfn=None, runfn=None):
596 def service(opts, parentfn=None, initfn=None, runfn=None):
599 '''Run a command as a service.'''
597 '''Run a command as a service.'''
600
598
601 if opts['daemon'] and not opts['daemon_pipefds']:
599 if opts['daemon'] and not opts['daemon_pipefds']:
602 rfd, wfd = os.pipe()
600 rfd, wfd = os.pipe()
603 args = sys.argv[:]
601 args = sys.argv[:]
604 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
602 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
605 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
603 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
606 args[0], args)
604 args[0], args)
607 os.close(wfd)
605 os.close(wfd)
608 os.read(rfd, 1)
606 os.read(rfd, 1)
609 if parentfn:
607 if parentfn:
610 return parentfn(pid)
608 return parentfn(pid)
611 else:
609 else:
612 os._exit(0)
610 os._exit(0)
613
611
614 if initfn:
612 if initfn:
615 initfn()
613 initfn()
616
614
617 if opts['pid_file']:
615 if opts['pid_file']:
618 fp = open(opts['pid_file'], 'w')
616 fp = open(opts['pid_file'], 'w')
619 fp.write(str(os.getpid()) + '\n')
617 fp.write(str(os.getpid()) + '\n')
620 fp.close()
618 fp.close()
621
619
622 if opts['daemon_pipefds']:
620 if opts['daemon_pipefds']:
623 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
621 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
624 os.close(rfd)
622 os.close(rfd)
625 try:
623 try:
626 os.setsid()
624 os.setsid()
627 except AttributeError:
625 except AttributeError:
628 pass
626 pass
629 os.write(wfd, 'y')
627 os.write(wfd, 'y')
630 os.close(wfd)
628 os.close(wfd)
631 sys.stdout.flush()
629 sys.stdout.flush()
632 sys.stderr.flush()
630 sys.stderr.flush()
633 fd = os.open(util.nulldev, os.O_RDWR)
631 fd = os.open(util.nulldev, os.O_RDWR)
634 if fd != 0: os.dup2(fd, 0)
632 if fd != 0: os.dup2(fd, 0)
635 if fd != 1: os.dup2(fd, 1)
633 if fd != 1: os.dup2(fd, 1)
636 if fd != 2: os.dup2(fd, 2)
634 if fd != 2: os.dup2(fd, 2)
637 if fd not in (0, 1, 2): os.close(fd)
635 if fd not in (0, 1, 2): os.close(fd)
638
636
639 if runfn:
637 if runfn:
640 return runfn()
638 return runfn()
641
639
642 class changeset_printer(object):
640 class changeset_printer(object):
643 '''show changeset information when templating not requested.'''
641 '''show changeset information when templating not requested.'''
644
642
645 def __init__(self, ui, repo, patch, buffered):
643 def __init__(self, ui, repo, patch, buffered):
646 self.ui = ui
644 self.ui = ui
647 self.repo = repo
645 self.repo = repo
648 self.buffered = buffered
646 self.buffered = buffered
649 self.patch = patch
647 self.patch = patch
650 self.header = {}
648 self.header = {}
651 self.hunk = {}
649 self.hunk = {}
652 self.lastheader = None
650 self.lastheader = None
653
651
654 def flush(self, rev):
652 def flush(self, rev):
655 if rev in self.header:
653 if rev in self.header:
656 h = self.header[rev]
654 h = self.header[rev]
657 if h != self.lastheader:
655 if h != self.lastheader:
658 self.lastheader = h
656 self.lastheader = h
659 self.ui.write(h)
657 self.ui.write(h)
660 del self.header[rev]
658 del self.header[rev]
661 if rev in self.hunk:
659 if rev in self.hunk:
662 self.ui.write(self.hunk[rev])
660 self.ui.write(self.hunk[rev])
663 del self.hunk[rev]
661 del self.hunk[rev]
664 return 1
662 return 1
665 return 0
663 return 0
666
664
667 def show(self, rev=0, changenode=None, copies=(), **props):
665 def show(self, rev=0, changenode=None, copies=(), **props):
668 if self.buffered:
666 if self.buffered:
669 self.ui.pushbuffer()
667 self.ui.pushbuffer()
670 self._show(rev, changenode, copies, props)
668 self._show(rev, changenode, copies, props)
671 self.hunk[rev] = self.ui.popbuffer()
669 self.hunk[rev] = self.ui.popbuffer()
672 else:
670 else:
673 self._show(rev, changenode, copies, props)
671 self._show(rev, changenode, copies, props)
674
672
675 def _show(self, rev, changenode, copies, props):
673 def _show(self, rev, changenode, copies, props):
676 '''show a single changeset or file revision'''
674 '''show a single changeset or file revision'''
677 log = self.repo.changelog
675 log = self.repo.changelog
678 if changenode is None:
676 if changenode is None:
679 changenode = log.node(rev)
677 changenode = log.node(rev)
680 elif not rev:
678 elif not rev:
681 rev = log.rev(changenode)
679 rev = log.rev(changenode)
682
680
683 if self.ui.quiet:
681 if self.ui.quiet:
684 self.ui.write("%d:%s\n" % (rev, short(changenode)))
682 self.ui.write("%d:%s\n" % (rev, short(changenode)))
685 return
683 return
686
684
687 changes = log.read(changenode)
685 changes = log.read(changenode)
688 date = util.datestr(changes[2])
686 date = util.datestr(changes[2])
689 extra = changes[5]
687 extra = changes[5]
690 branch = extra.get("branch")
688 branch = extra.get("branch")
691
689
692 hexfunc = self.ui.debugflag and hex or short
690 hexfunc = self.ui.debugflag and hex or short
693
691
694 parents = log.parentrevs(rev)
692 parents = log.parentrevs(rev)
695 if not self.ui.debugflag:
693 if not self.ui.debugflag:
696 if parents[1] == nullrev:
694 if parents[1] == nullrev:
697 if parents[0] >= rev - 1:
695 if parents[0] >= rev - 1:
698 parents = []
696 parents = []
699 else:
697 else:
700 parents = [parents[0]]
698 parents = [parents[0]]
701 parents = [(p, hexfunc(log.node(p))) for p in parents]
699 parents = [(p, hexfunc(log.node(p))) for p in parents]
702
700
703 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
701 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
704
702
705 # don't show the default branch name
703 # don't show the default branch name
706 if branch != 'default':
704 if branch != 'default':
707 branch = util.tolocal(branch)
705 branch = util.tolocal(branch)
708 self.ui.write(_("branch: %s\n") % branch)
706 self.ui.write(_("branch: %s\n") % branch)
709 for tag in self.repo.nodetags(changenode):
707 for tag in self.repo.nodetags(changenode):
710 self.ui.write(_("tag: %s\n") % tag)
708 self.ui.write(_("tag: %s\n") % tag)
711 for parent in parents:
709 for parent in parents:
712 self.ui.write(_("parent: %d:%s\n") % parent)
710 self.ui.write(_("parent: %d:%s\n") % parent)
713
711
714 if self.ui.debugflag:
712 if self.ui.debugflag:
715 self.ui.write(_("manifest: %d:%s\n") %
713 self.ui.write(_("manifest: %d:%s\n") %
716 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
714 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
717 self.ui.write(_("user: %s\n") % changes[1])
715 self.ui.write(_("user: %s\n") % changes[1])
718 self.ui.write(_("date: %s\n") % date)
716 self.ui.write(_("date: %s\n") % date)
719
717
720 if self.ui.debugflag:
718 if self.ui.debugflag:
721 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
719 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
722 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
720 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
723 files):
721 files):
724 if value:
722 if value:
725 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
723 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
726 elif changes[3] and self.ui.verbose:
724 elif changes[3] and self.ui.verbose:
727 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
725 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
728 if copies and self.ui.verbose:
726 if copies and self.ui.verbose:
729 copies = ['%s (%s)' % c for c in copies]
727 copies = ['%s (%s)' % c for c in copies]
730 self.ui.write(_("copies: %s\n") % ' '.join(copies))
728 self.ui.write(_("copies: %s\n") % ' '.join(copies))
731
729
732 if extra and self.ui.debugflag:
730 if extra and self.ui.debugflag:
733 extraitems = extra.items()
731 extraitems = extra.items()
734 extraitems.sort()
732 extraitems.sort()
735 for key, value in extraitems:
733 for key, value in extraitems:
736 self.ui.write(_("extra: %s=%s\n")
734 self.ui.write(_("extra: %s=%s\n")
737 % (key, value.encode('string_escape')))
735 % (key, value.encode('string_escape')))
738
736
739 description = changes[4].strip()
737 description = changes[4].strip()
740 if description:
738 if description:
741 if self.ui.verbose:
739 if self.ui.verbose:
742 self.ui.write(_("description:\n"))
740 self.ui.write(_("description:\n"))
743 self.ui.write(description)
741 self.ui.write(description)
744 self.ui.write("\n\n")
742 self.ui.write("\n\n")
745 else:
743 else:
746 self.ui.write(_("summary: %s\n") %
744 self.ui.write(_("summary: %s\n") %
747 description.splitlines()[0])
745 description.splitlines()[0])
748 self.ui.write("\n")
746 self.ui.write("\n")
749
747
750 self.showpatch(changenode)
748 self.showpatch(changenode)
751
749
752 def showpatch(self, node):
750 def showpatch(self, node):
753 if self.patch:
751 if self.patch:
754 prev = self.repo.changelog.parents(node)[0]
752 prev = self.repo.changelog.parents(node)[0]
755 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
753 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
756 self.ui.write("\n")
754 self.ui.write("\n")
757
755
758 class changeset_templater(changeset_printer):
756 class changeset_templater(changeset_printer):
759 '''format changeset information.'''
757 '''format changeset information.'''
760
758
761 def __init__(self, ui, repo, patch, mapfile, buffered):
759 def __init__(self, ui, repo, patch, mapfile, buffered):
762 changeset_printer.__init__(self, ui, repo, patch, buffered)
760 changeset_printer.__init__(self, ui, repo, patch, buffered)
763 filters = templater.common_filters.copy()
761 filters = templater.common_filters.copy()
764 filters['formatnode'] = (ui.debugflag and (lambda x: x)
762 filters['formatnode'] = (ui.debugflag and (lambda x: x)
765 or (lambda x: x[:12]))
763 or (lambda x: x[:12]))
766 self.t = templater.templater(mapfile, filters,
764 self.t = templater.templater(mapfile, filters,
767 cache={
765 cache={
768 'parent': '{rev}:{node|formatnode} ',
766 'parent': '{rev}:{node|formatnode} ',
769 'manifest': '{rev}:{node|formatnode}',
767 'manifest': '{rev}:{node|formatnode}',
770 'filecopy': '{name} ({source})'})
768 'filecopy': '{name} ({source})'})
771
769
772 def use_template(self, t):
770 def use_template(self, t):
773 '''set template string to use'''
771 '''set template string to use'''
774 self.t.cache['changeset'] = t
772 self.t.cache['changeset'] = t
775
773
776 def _show(self, rev, changenode, copies, props):
774 def _show(self, rev, changenode, copies, props):
777 '''show a single changeset or file revision'''
775 '''show a single changeset or file revision'''
778 log = self.repo.changelog
776 log = self.repo.changelog
779 if changenode is None:
777 if changenode is None:
780 changenode = log.node(rev)
778 changenode = log.node(rev)
781 elif not rev:
779 elif not rev:
782 rev = log.rev(changenode)
780 rev = log.rev(changenode)
783
781
784 changes = log.read(changenode)
782 changes = log.read(changenode)
785
783
786 def showlist(name, values, plural=None, **args):
784 def showlist(name, values, plural=None, **args):
787 '''expand set of values.
785 '''expand set of values.
788 name is name of key in template map.
786 name is name of key in template map.
789 values is list of strings or dicts.
787 values is list of strings or dicts.
790 plural is plural of name, if not simply name + 's'.
788 plural is plural of name, if not simply name + 's'.
791
789
792 expansion works like this, given name 'foo'.
790 expansion works like this, given name 'foo'.
793
791
794 if values is empty, expand 'no_foos'.
792 if values is empty, expand 'no_foos'.
795
793
796 if 'foo' not in template map, return values as a string,
794 if 'foo' not in template map, return values as a string,
797 joined by space.
795 joined by space.
798
796
799 expand 'start_foos'.
797 expand 'start_foos'.
800
798
801 for each value, expand 'foo'. if 'last_foo' in template
799 for each value, expand 'foo'. if 'last_foo' in template
802 map, expand it instead of 'foo' for last key.
800 map, expand it instead of 'foo' for last key.
803
801
804 expand 'end_foos'.
802 expand 'end_foos'.
805 '''
803 '''
806 if plural: names = plural
804 if plural: names = plural
807 else: names = name + 's'
805 else: names = name + 's'
808 if not values:
806 if not values:
809 noname = 'no_' + names
807 noname = 'no_' + names
810 if noname in self.t:
808 if noname in self.t:
811 yield self.t(noname, **args)
809 yield self.t(noname, **args)
812 return
810 return
813 if name not in self.t:
811 if name not in self.t:
814 if isinstance(values[0], str):
812 if isinstance(values[0], str):
815 yield ' '.join(values)
813 yield ' '.join(values)
816 else:
814 else:
817 for v in values:
815 for v in values:
818 yield dict(v, **args)
816 yield dict(v, **args)
819 return
817 return
820 startname = 'start_' + names
818 startname = 'start_' + names
821 if startname in self.t:
819 if startname in self.t:
822 yield self.t(startname, **args)
820 yield self.t(startname, **args)
823 vargs = args.copy()
821 vargs = args.copy()
824 def one(v, tag=name):
822 def one(v, tag=name):
825 try:
823 try:
826 vargs.update(v)
824 vargs.update(v)
827 except (AttributeError, ValueError):
825 except (AttributeError, ValueError):
828 try:
826 try:
829 for a, b in v:
827 for a, b in v:
830 vargs[a] = b
828 vargs[a] = b
831 except ValueError:
829 except ValueError:
832 vargs[name] = v
830 vargs[name] = v
833 return self.t(tag, **vargs)
831 return self.t(tag, **vargs)
834 lastname = 'last_' + name
832 lastname = 'last_' + name
835 if lastname in self.t:
833 if lastname in self.t:
836 last = values.pop()
834 last = values.pop()
837 else:
835 else:
838 last = None
836 last = None
839 for v in values:
837 for v in values:
840 yield one(v)
838 yield one(v)
841 if last is not None:
839 if last is not None:
842 yield one(last, tag=lastname)
840 yield one(last, tag=lastname)
843 endname = 'end_' + names
841 endname = 'end_' + names
844 if endname in self.t:
842 if endname in self.t:
845 yield self.t(endname, **args)
843 yield self.t(endname, **args)
846
844
847 def showbranches(**args):
845 def showbranches(**args):
848 branch = changes[5].get("branch")
846 branch = changes[5].get("branch")
849 if branch != 'default':
847 if branch != 'default':
850 branch = util.tolocal(branch)
848 branch = util.tolocal(branch)
851 return showlist('branch', [branch], plural='branches', **args)
849 return showlist('branch', [branch], plural='branches', **args)
852
850
853 def showparents(**args):
851 def showparents(**args):
854 parents = [[('rev', log.rev(p)), ('node', hex(p))]
852 parents = [[('rev', log.rev(p)), ('node', hex(p))]
855 for p in log.parents(changenode)
853 for p in log.parents(changenode)
856 if self.ui.debugflag or p != nullid]
854 if self.ui.debugflag or p != nullid]
857 if (not self.ui.debugflag and len(parents) == 1 and
855 if (not self.ui.debugflag and len(parents) == 1 and
858 parents[0][0][1] == rev - 1):
856 parents[0][0][1] == rev - 1):
859 return
857 return
860 return showlist('parent', parents, **args)
858 return showlist('parent', parents, **args)
861
859
862 def showtags(**args):
860 def showtags(**args):
863 return showlist('tag', self.repo.nodetags(changenode), **args)
861 return showlist('tag', self.repo.nodetags(changenode), **args)
864
862
865 def showextras(**args):
863 def showextras(**args):
866 extras = changes[5].items()
864 extras = changes[5].items()
867 extras.sort()
865 extras.sort()
868 for key, value in extras:
866 for key, value in extras:
869 args = args.copy()
867 args = args.copy()
870 args.update(dict(key=key, value=value))
868 args.update(dict(key=key, value=value))
871 yield self.t('extra', **args)
869 yield self.t('extra', **args)
872
870
873 def showcopies(**args):
871 def showcopies(**args):
874 c = [{'name': x[0], 'source': x[1]} for x in copies]
872 c = [{'name': x[0], 'source': x[1]} for x in copies]
875 return showlist('file_copy', c, plural='file_copies', **args)
873 return showlist('file_copy', c, plural='file_copies', **args)
876
874
877 if self.ui.debugflag:
875 if self.ui.debugflag:
878 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
876 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
879 def showfiles(**args):
877 def showfiles(**args):
880 return showlist('file', files[0], **args)
878 return showlist('file', files[0], **args)
881 def showadds(**args):
879 def showadds(**args):
882 return showlist('file_add', files[1], **args)
880 return showlist('file_add', files[1], **args)
883 def showdels(**args):
881 def showdels(**args):
884 return showlist('file_del', files[2], **args)
882 return showlist('file_del', files[2], **args)
885 def showmanifest(**args):
883 def showmanifest(**args):
886 args = args.copy()
884 args = args.copy()
887 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
885 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
888 node=hex(changes[0])))
886 node=hex(changes[0])))
889 return self.t('manifest', **args)
887 return self.t('manifest', **args)
890 else:
888 else:
891 def showfiles(**args):
889 def showfiles(**args):
892 return showlist('file', changes[3], **args)
890 return showlist('file', changes[3], **args)
893 showadds = ''
891 showadds = ''
894 showdels = ''
892 showdels = ''
895 showmanifest = ''
893 showmanifest = ''
896
894
897 defprops = {
895 defprops = {
898 'author': changes[1],
896 'author': changes[1],
899 'branches': showbranches,
897 'branches': showbranches,
900 'date': changes[2],
898 'date': changes[2],
901 'desc': changes[4],
899 'desc': changes[4],
902 'file_adds': showadds,
900 'file_adds': showadds,
903 'file_dels': showdels,
901 'file_dels': showdels,
904 'files': showfiles,
902 'files': showfiles,
905 'file_copies': showcopies,
903 'file_copies': showcopies,
906 'manifest': showmanifest,
904 'manifest': showmanifest,
907 'node': hex(changenode),
905 'node': hex(changenode),
908 'parents': showparents,
906 'parents': showparents,
909 'rev': rev,
907 'rev': rev,
910 'tags': showtags,
908 'tags': showtags,
911 'extras': showextras,
909 'extras': showextras,
912 }
910 }
913 props = props.copy()
911 props = props.copy()
914 props.update(defprops)
912 props.update(defprops)
915
913
916 try:
914 try:
917 if self.ui.debugflag and 'header_debug' in self.t:
915 if self.ui.debugflag and 'header_debug' in self.t:
918 key = 'header_debug'
916 key = 'header_debug'
919 elif self.ui.quiet and 'header_quiet' in self.t:
917 elif self.ui.quiet and 'header_quiet' in self.t:
920 key = 'header_quiet'
918 key = 'header_quiet'
921 elif self.ui.verbose and 'header_verbose' in self.t:
919 elif self.ui.verbose and 'header_verbose' in self.t:
922 key = 'header_verbose'
920 key = 'header_verbose'
923 elif 'header' in self.t:
921 elif 'header' in self.t:
924 key = 'header'
922 key = 'header'
925 else:
923 else:
926 key = ''
924 key = ''
927 if key:
925 if key:
928 h = templater.stringify(self.t(key, **props))
926 h = templater.stringify(self.t(key, **props))
929 if self.buffered:
927 if self.buffered:
930 self.header[rev] = h
928 self.header[rev] = h
931 else:
929 else:
932 self.ui.write(h)
930 self.ui.write(h)
933 if self.ui.debugflag and 'changeset_debug' in self.t:
931 if self.ui.debugflag and 'changeset_debug' in self.t:
934 key = 'changeset_debug'
932 key = 'changeset_debug'
935 elif self.ui.quiet and 'changeset_quiet' in self.t:
933 elif self.ui.quiet and 'changeset_quiet' in self.t:
936 key = 'changeset_quiet'
934 key = 'changeset_quiet'
937 elif self.ui.verbose and 'changeset_verbose' in self.t:
935 elif self.ui.verbose and 'changeset_verbose' in self.t:
938 key = 'changeset_verbose'
936 key = 'changeset_verbose'
939 else:
937 else:
940 key = 'changeset'
938 key = 'changeset'
941 self.ui.write(templater.stringify(self.t(key, **props)))
939 self.ui.write(templater.stringify(self.t(key, **props)))
942 self.showpatch(changenode)
940 self.showpatch(changenode)
943 except KeyError, inst:
941 except KeyError, inst:
944 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
942 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
945 inst.args[0]))
943 inst.args[0]))
946 except SyntaxError, inst:
944 except SyntaxError, inst:
947 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
945 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
948
946
949 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
947 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
950 """show one changeset using template or regular display.
948 """show one changeset using template or regular display.
951
949
952 Display format will be the first non-empty hit of:
950 Display format will be the first non-empty hit of:
953 1. option 'template'
951 1. option 'template'
954 2. option 'style'
952 2. option 'style'
955 3. [ui] setting 'logtemplate'
953 3. [ui] setting 'logtemplate'
956 4. [ui] setting 'style'
954 4. [ui] setting 'style'
957 If all of these values are either the unset or the empty string,
955 If all of these values are either the unset or the empty string,
958 regular display via changeset_printer() is done.
956 regular display via changeset_printer() is done.
959 """
957 """
960 # options
958 # options
961 patch = False
959 patch = False
962 if opts.get('patch'):
960 if opts.get('patch'):
963 patch = matchfn or util.always
961 patch = matchfn or util.always
964
962
965 tmpl = opts.get('template')
963 tmpl = opts.get('template')
966 mapfile = None
964 mapfile = None
967 if tmpl:
965 if tmpl:
968 tmpl = templater.parsestring(tmpl, quoted=False)
966 tmpl = templater.parsestring(tmpl, quoted=False)
969 else:
967 else:
970 mapfile = opts.get('style')
968 mapfile = opts.get('style')
971 # ui settings
969 # ui settings
972 if not mapfile:
970 if not mapfile:
973 tmpl = ui.config('ui', 'logtemplate')
971 tmpl = ui.config('ui', 'logtemplate')
974 if tmpl:
972 if tmpl:
975 tmpl = templater.parsestring(tmpl)
973 tmpl = templater.parsestring(tmpl)
976 else:
974 else:
977 mapfile = ui.config('ui', 'style')
975 mapfile = ui.config('ui', 'style')
978
976
979 if tmpl or mapfile:
977 if tmpl or mapfile:
980 if mapfile:
978 if mapfile:
981 if not os.path.split(mapfile)[0]:
979 if not os.path.split(mapfile)[0]:
982 mapname = (templater.templatepath('map-cmdline.' + mapfile)
980 mapname = (templater.templatepath('map-cmdline.' + mapfile)
983 or templater.templatepath(mapfile))
981 or templater.templatepath(mapfile))
984 if mapname: mapfile = mapname
982 if mapname: mapfile = mapname
985 try:
983 try:
986 t = changeset_templater(ui, repo, patch, mapfile, buffered)
984 t = changeset_templater(ui, repo, patch, mapfile, buffered)
987 except SyntaxError, inst:
985 except SyntaxError, inst:
988 raise util.Abort(inst.args[0])
986 raise util.Abort(inst.args[0])
989 if tmpl: t.use_template(tmpl)
987 if tmpl: t.use_template(tmpl)
990 return t
988 return t
991 return changeset_printer(ui, repo, patch, buffered)
989 return changeset_printer(ui, repo, patch, buffered)
992
990
993 def finddate(ui, repo, date):
991 def finddate(ui, repo, date):
994 """Find the tipmost changeset that matches the given date spec"""
992 """Find the tipmost changeset that matches the given date spec"""
995 df = util.matchdate(date + " to " + date)
993 df = util.matchdate(date + " to " + date)
996 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
994 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
997 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
995 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
998 results = {}
996 results = {}
999 for st, rev, fns in changeiter:
997 for st, rev, fns in changeiter:
1000 if st == 'add':
998 if st == 'add':
1001 d = get(rev)[2]
999 d = get(rev)[2]
1002 if df(d[0]):
1000 if df(d[0]):
1003 results[rev] = d
1001 results[rev] = d
1004 elif st == 'iter':
1002 elif st == 'iter':
1005 if rev in results:
1003 if rev in results:
1006 ui.status("Found revision %s from %s\n" %
1004 ui.status("Found revision %s from %s\n" %
1007 (rev, util.datestr(results[rev])))
1005 (rev, util.datestr(results[rev])))
1008 return str(rev)
1006 return str(rev)
1009
1007
1010 raise util.Abort(_("revision matching date not found"))
1008 raise util.Abort(_("revision matching date not found"))
1011
1009
1012 def walkchangerevs(ui, repo, pats, change, opts):
1010 def walkchangerevs(ui, repo, pats, change, opts):
1013 '''Iterate over files and the revs they changed in.
1011 '''Iterate over files and the revs they changed in.
1014
1012
1015 Callers most commonly need to iterate backwards over the history
1013 Callers most commonly need to iterate backwards over the history
1016 it is interested in. Doing so has awful (quadratic-looking)
1014 it is interested in. Doing so has awful (quadratic-looking)
1017 performance, so we use iterators in a "windowed" way.
1015 performance, so we use iterators in a "windowed" way.
1018
1016
1019 We walk a window of revisions in the desired order. Within the
1017 We walk a window of revisions in the desired order. Within the
1020 window, we first walk forwards to gather data, then in the desired
1018 window, we first walk forwards to gather data, then in the desired
1021 order (usually backwards) to display it.
1019 order (usually backwards) to display it.
1022
1020
1023 This function returns an (iterator, matchfn) tuple. The iterator
1021 This function returns an (iterator, matchfn) tuple. The iterator
1024 yields 3-tuples. They will be of one of the following forms:
1022 yields 3-tuples. They will be of one of the following forms:
1025
1023
1026 "window", incrementing, lastrev: stepping through a window,
1024 "window", incrementing, lastrev: stepping through a window,
1027 positive if walking forwards through revs, last rev in the
1025 positive if walking forwards through revs, last rev in the
1028 sequence iterated over - use to reset state for the current window
1026 sequence iterated over - use to reset state for the current window
1029
1027
1030 "add", rev, fns: out-of-order traversal of the given file names
1028 "add", rev, fns: out-of-order traversal of the given file names
1031 fns, which changed during revision rev - use to gather data for
1029 fns, which changed during revision rev - use to gather data for
1032 possible display
1030 possible display
1033
1031
1034 "iter", rev, None: in-order traversal of the revs earlier iterated
1032 "iter", rev, None: in-order traversal of the revs earlier iterated
1035 over with "add" - use to display data'''
1033 over with "add" - use to display data'''
1036
1034
1037 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1035 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1038 if start < end:
1036 if start < end:
1039 while start < end:
1037 while start < end:
1040 yield start, min(windowsize, end-start)
1038 yield start, min(windowsize, end-start)
1041 start += windowsize
1039 start += windowsize
1042 if windowsize < sizelimit:
1040 if windowsize < sizelimit:
1043 windowsize *= 2
1041 windowsize *= 2
1044 else:
1042 else:
1045 while start > end:
1043 while start > end:
1046 yield start, min(windowsize, start-end-1)
1044 yield start, min(windowsize, start-end-1)
1047 start -= windowsize
1045 start -= windowsize
1048 if windowsize < sizelimit:
1046 if windowsize < sizelimit:
1049 windowsize *= 2
1047 windowsize *= 2
1050
1048
1051 files, matchfn, anypats = matchpats(repo, pats, opts)
1049 files, matchfn, anypats = matchpats(repo, pats, opts)
1052 follow = opts.get('follow') or opts.get('follow_first')
1050 follow = opts.get('follow') or opts.get('follow_first')
1053
1051
1054 if repo.changelog.count() == 0:
1052 if repo.changelog.count() == 0:
1055 return [], matchfn
1053 return [], matchfn
1056
1054
1057 if follow:
1055 if follow:
1058 defrange = '%s:0' % repo.changectx().rev()
1056 defrange = '%s:0' % repo.changectx().rev()
1059 else:
1057 else:
1060 defrange = 'tip:0'
1058 defrange = 'tip:0'
1061 revs = revrange(repo, opts['rev'] or [defrange])
1059 revs = revrange(repo, opts['rev'] or [defrange])
1062 wanted = {}
1060 wanted = {}
1063 slowpath = anypats or opts.get('removed')
1061 slowpath = anypats or opts.get('removed')
1064 fncache = {}
1062 fncache = {}
1065
1063
1066 if not slowpath and not files:
1064 if not slowpath and not files:
1067 # No files, no patterns. Display all revs.
1065 # No files, no patterns. Display all revs.
1068 wanted = dict.fromkeys(revs)
1066 wanted = dict.fromkeys(revs)
1069 copies = []
1067 copies = []
1070 if not slowpath:
1068 if not slowpath:
1071 # Only files, no patterns. Check the history of each file.
1069 # Only files, no patterns. Check the history of each file.
1072 def filerevgen(filelog, node):
1070 def filerevgen(filelog, node):
1073 cl_count = repo.changelog.count()
1071 cl_count = repo.changelog.count()
1074 if node is None:
1072 if node is None:
1075 last = filelog.count() - 1
1073 last = filelog.count() - 1
1076 else:
1074 else:
1077 last = filelog.rev(node)
1075 last = filelog.rev(node)
1078 for i, window in increasing_windows(last, nullrev):
1076 for i, window in increasing_windows(last, nullrev):
1079 revs = []
1077 revs = []
1080 for j in xrange(i - window, i + 1):
1078 for j in xrange(i - window, i + 1):
1081 n = filelog.node(j)
1079 n = filelog.node(j)
1082 revs.append((filelog.linkrev(n),
1080 revs.append((filelog.linkrev(n),
1083 follow and filelog.renamed(n)))
1081 follow and filelog.renamed(n)))
1084 revs.reverse()
1082 revs.reverse()
1085 for rev in revs:
1083 for rev in revs:
1086 # only yield rev for which we have the changelog, it can
1084 # only yield rev for which we have the changelog, it can
1087 # happen while doing "hg log" during a pull or commit
1085 # happen while doing "hg log" during a pull or commit
1088 if rev[0] < cl_count:
1086 if rev[0] < cl_count:
1089 yield rev
1087 yield rev
1090 def iterfiles():
1088 def iterfiles():
1091 for filename in files:
1089 for filename in files:
1092 yield filename, None
1090 yield filename, None
1093 for filename_node in copies:
1091 for filename_node in copies:
1094 yield filename_node
1092 yield filename_node
1095 minrev, maxrev = min(revs), max(revs)
1093 minrev, maxrev = min(revs), max(revs)
1096 for file_, node in iterfiles():
1094 for file_, node in iterfiles():
1097 filelog = repo.file(file_)
1095 filelog = repo.file(file_)
1098 # A zero count may be a directory or deleted file, so
1096 # A zero count may be a directory or deleted file, so
1099 # try to find matching entries on the slow path.
1097 # try to find matching entries on the slow path.
1100 if filelog.count() == 0:
1098 if filelog.count() == 0:
1101 slowpath = True
1099 slowpath = True
1102 break
1100 break
1103 for rev, copied in filerevgen(filelog, node):
1101 for rev, copied in filerevgen(filelog, node):
1104 if rev <= maxrev:
1102 if rev <= maxrev:
1105 if rev < minrev:
1103 if rev < minrev:
1106 break
1104 break
1107 fncache.setdefault(rev, [])
1105 fncache.setdefault(rev, [])
1108 fncache[rev].append(file_)
1106 fncache[rev].append(file_)
1109 wanted[rev] = 1
1107 wanted[rev] = 1
1110 if follow and copied:
1108 if follow and copied:
1111 copies.append(copied)
1109 copies.append(copied)
1112 if slowpath:
1110 if slowpath:
1113 if follow:
1111 if follow:
1114 raise util.Abort(_('can only follow copies/renames for explicit '
1112 raise util.Abort(_('can only follow copies/renames for explicit '
1115 'file names'))
1113 'file names'))
1116
1114
1117 # The slow path checks files modified in every changeset.
1115 # The slow path checks files modified in every changeset.
1118 def changerevgen():
1116 def changerevgen():
1119 for i, window in increasing_windows(repo.changelog.count()-1,
1117 for i, window in increasing_windows(repo.changelog.count()-1,
1120 nullrev):
1118 nullrev):
1121 for j in xrange(i - window, i + 1):
1119 for j in xrange(i - window, i + 1):
1122 yield j, change(j)[3]
1120 yield j, change(j)[3]
1123
1121
1124 for rev, changefiles in changerevgen():
1122 for rev, changefiles in changerevgen():
1125 matches = filter(matchfn, changefiles)
1123 matches = filter(matchfn, changefiles)
1126 if matches:
1124 if matches:
1127 fncache[rev] = matches
1125 fncache[rev] = matches
1128 wanted[rev] = 1
1126 wanted[rev] = 1
1129
1127
1130 class followfilter:
1128 class followfilter:
1131 def __init__(self, onlyfirst=False):
1129 def __init__(self, onlyfirst=False):
1132 self.startrev = nullrev
1130 self.startrev = nullrev
1133 self.roots = []
1131 self.roots = []
1134 self.onlyfirst = onlyfirst
1132 self.onlyfirst = onlyfirst
1135
1133
1136 def match(self, rev):
1134 def match(self, rev):
1137 def realparents(rev):
1135 def realparents(rev):
1138 if self.onlyfirst:
1136 if self.onlyfirst:
1139 return repo.changelog.parentrevs(rev)[0:1]
1137 return repo.changelog.parentrevs(rev)[0:1]
1140 else:
1138 else:
1141 return filter(lambda x: x != nullrev,
1139 return filter(lambda x: x != nullrev,
1142 repo.changelog.parentrevs(rev))
1140 repo.changelog.parentrevs(rev))
1143
1141
1144 if self.startrev == nullrev:
1142 if self.startrev == nullrev:
1145 self.startrev = rev
1143 self.startrev = rev
1146 return True
1144 return True
1147
1145
1148 if rev > self.startrev:
1146 if rev > self.startrev:
1149 # forward: all descendants
1147 # forward: all descendants
1150 if not self.roots:
1148 if not self.roots:
1151 self.roots.append(self.startrev)
1149 self.roots.append(self.startrev)
1152 for parent in realparents(rev):
1150 for parent in realparents(rev):
1153 if parent in self.roots:
1151 if parent in self.roots:
1154 self.roots.append(rev)
1152 self.roots.append(rev)
1155 return True
1153 return True
1156 else:
1154 else:
1157 # backwards: all parents
1155 # backwards: all parents
1158 if not self.roots:
1156 if not self.roots:
1159 self.roots.extend(realparents(self.startrev))
1157 self.roots.extend(realparents(self.startrev))
1160 if rev in self.roots:
1158 if rev in self.roots:
1161 self.roots.remove(rev)
1159 self.roots.remove(rev)
1162 self.roots.extend(realparents(rev))
1160 self.roots.extend(realparents(rev))
1163 return True
1161 return True
1164
1162
1165 return False
1163 return False
1166
1164
1167 # it might be worthwhile to do this in the iterator if the rev range
1165 # it might be worthwhile to do this in the iterator if the rev range
1168 # is descending and the prune args are all within that range
1166 # is descending and the prune args are all within that range
1169 for rev in opts.get('prune', ()):
1167 for rev in opts.get('prune', ()):
1170 rev = repo.changelog.rev(repo.lookup(rev))
1168 rev = repo.changelog.rev(repo.lookup(rev))
1171 ff = followfilter()
1169 ff = followfilter()
1172 stop = min(revs[0], revs[-1])
1170 stop = min(revs[0], revs[-1])
1173 for x in xrange(rev, stop-1, -1):
1171 for x in xrange(rev, stop-1, -1):
1174 if ff.match(x) and x in wanted:
1172 if ff.match(x) and x in wanted:
1175 del wanted[x]
1173 del wanted[x]
1176
1174
1177 def iterate():
1175 def iterate():
1178 if follow and not files:
1176 if follow and not files:
1179 ff = followfilter(onlyfirst=opts.get('follow_first'))
1177 ff = followfilter(onlyfirst=opts.get('follow_first'))
1180 def want(rev):
1178 def want(rev):
1181 if ff.match(rev) and rev in wanted:
1179 if ff.match(rev) and rev in wanted:
1182 return True
1180 return True
1183 return False
1181 return False
1184 else:
1182 else:
1185 def want(rev):
1183 def want(rev):
1186 return rev in wanted
1184 return rev in wanted
1187
1185
1188 for i, window in increasing_windows(0, len(revs)):
1186 for i, window in increasing_windows(0, len(revs)):
1189 yield 'window', revs[0] < revs[-1], revs[-1]
1187 yield 'window', revs[0] < revs[-1], revs[-1]
1190 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1188 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1191 srevs = list(nrevs)
1189 srevs = list(nrevs)
1192 srevs.sort()
1190 srevs.sort()
1193 for rev in srevs:
1191 for rev in srevs:
1194 fns = fncache.get(rev)
1192 fns = fncache.get(rev)
1195 if not fns:
1193 if not fns:
1196 def fns_generator():
1194 def fns_generator():
1197 for f in change(rev)[3]:
1195 for f in change(rev)[3]:
1198 if matchfn(f):
1196 if matchfn(f):
1199 yield f
1197 yield f
1200 fns = fns_generator()
1198 fns = fns_generator()
1201 yield 'add', rev, fns
1199 yield 'add', rev, fns
1202 for rev in nrevs:
1200 for rev in nrevs:
1203 yield 'iter', rev, None
1201 yield 'iter', rev, None
1204 return iterate(), matchfn
1202 return iterate(), matchfn
@@ -1,3009 +1,3008 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import demandimport; demandimport.enable()
8 import demandimport; demandimport.enable()
9 from node import *
9 from node import *
10 from i18n import _
10 from i18n import _
11 import bisect, os, re, sys, urllib, shlex, stat
11 import bisect, os, re, sys, urllib, shlex, stat
12 import ui, hg, util, revlog, bundlerepo, extensions
12 import ui, hg, util, revlog, bundlerepo, extensions
13 import difflib, patch, time, help, mdiff, tempfile
13 import difflib, patch, time, help, mdiff, tempfile
14 import errno, version, socket
14 import errno, version, socket
15 import archival, changegroup, cmdutil, hgweb.server, sshserver
15 import archival, changegroup, cmdutil, hgweb.server, sshserver
16
16
17 # Commands start here, listed alphabetically
17 # Commands start here, listed alphabetically
18
18
19 def add(ui, repo, *pats, **opts):
19 def add(ui, repo, *pats, **opts):
20 """add the specified files on the next commit
20 """add the specified files on the next commit
21
21
22 Schedule files to be version controlled and added to the repository.
22 Schedule files to be version controlled and added to the repository.
23
23
24 The files will be added to the repository at the next commit. To
24 The files will be added to the repository at the next commit. To
25 undo an add before that, see hg revert.
25 undo an add before that, see hg revert.
26
26
27 If no names are given, add all files in the repository.
27 If no names are given, add all files in the repository.
28 """
28 """
29
29
30 names = []
30 names = []
31 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
31 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
32 if exact:
32 if exact:
33 if ui.verbose:
33 if ui.verbose:
34 ui.status(_('adding %s\n') % rel)
34 ui.status(_('adding %s\n') % rel)
35 names.append(abs)
35 names.append(abs)
36 elif repo.dirstate.state(abs) == '?':
36 elif repo.dirstate.state(abs) == '?':
37 ui.status(_('adding %s\n') % rel)
37 ui.status(_('adding %s\n') % rel)
38 names.append(abs)
38 names.append(abs)
39 if not opts.get('dry_run'):
39 if not opts.get('dry_run'):
40 repo.add(names)
40 repo.add(names)
41
41
42 def addremove(ui, repo, *pats, **opts):
42 def addremove(ui, repo, *pats, **opts):
43 """add all new files, delete all missing files
43 """add all new files, delete all missing files
44
44
45 Add all new files and remove all missing files from the repository.
45 Add all new files and remove all missing files from the repository.
46
46
47 New files are ignored if they match any of the patterns in .hgignore. As
47 New files are ignored if they match any of the patterns in .hgignore. As
48 with add, these changes take effect at the next commit.
48 with add, these changes take effect at the next commit.
49
49
50 Use the -s option to detect renamed files. With a parameter > 0,
50 Use the -s option to detect renamed files. With a parameter > 0,
51 this compares every removed file with every added file and records
51 this compares every removed file with every added file and records
52 those similar enough as renames. This option takes a percentage
52 those similar enough as renames. This option takes a percentage
53 between 0 (disabled) and 100 (files must be identical) as its
53 between 0 (disabled) and 100 (files must be identical) as its
54 parameter. Detecting renamed files this way can be expensive.
54 parameter. Detecting renamed files this way can be expensive.
55 """
55 """
56 sim = float(opts.get('similarity') or 0)
56 sim = float(opts.get('similarity') or 0)
57 if sim < 0 or sim > 100:
57 if sim < 0 or sim > 100:
58 raise util.Abort(_('similarity must be between 0 and 100'))
58 raise util.Abort(_('similarity must be between 0 and 100'))
59 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
59 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
60
60
61 def annotate(ui, repo, *pats, **opts):
61 def annotate(ui, repo, *pats, **opts):
62 """show changeset information per file line
62 """show changeset information per file line
63
63
64 List changes in files, showing the revision id responsible for each line
64 List changes in files, showing the revision id responsible for each line
65
65
66 This command is useful to discover who did a change or when a change took
66 This command is useful to discover who did a change or when a change took
67 place.
67 place.
68
68
69 Without the -a option, annotate will avoid processing files it
69 Without the -a option, annotate will avoid processing files it
70 detects as binary. With -a, annotate will generate an annotation
70 detects as binary. With -a, annotate will generate an annotation
71 anyway, probably with undesirable results.
71 anyway, probably with undesirable results.
72 """
72 """
73 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
73 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
74
74
75 if not pats:
75 if not pats:
76 raise util.Abort(_('at least one file name or pattern required'))
76 raise util.Abort(_('at least one file name or pattern required'))
77
77
78 opmap = [['user', lambda x: ui.shortuser(x.user())],
78 opmap = [['user', lambda x: ui.shortuser(x.user())],
79 ['number', lambda x: str(x.rev())],
79 ['number', lambda x: str(x.rev())],
80 ['changeset', lambda x: short(x.node())],
80 ['changeset', lambda x: short(x.node())],
81 ['date', getdate], ['follow', lambda x: x.path()]]
81 ['date', getdate], ['follow', lambda x: x.path()]]
82 if (not opts['user'] and not opts['changeset'] and not opts['date']
82 if (not opts['user'] and not opts['changeset'] and not opts['date']
83 and not opts['follow']):
83 and not opts['follow']):
84 opts['number'] = 1
84 opts['number'] = 1
85
85
86 ctx = repo.changectx(opts['rev'])
86 ctx = repo.changectx(opts['rev'])
87
87
88 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
88 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
89 node=ctx.node()):
89 node=ctx.node()):
90 fctx = ctx.filectx(abs)
90 fctx = ctx.filectx(abs)
91 if not opts['text'] and util.binary(fctx.data()):
91 if not opts['text'] and util.binary(fctx.data()):
92 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
92 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
93 continue
93 continue
94
94
95 lines = fctx.annotate(follow=opts.get('follow'))
95 lines = fctx.annotate(follow=opts.get('follow'))
96 pieces = []
96 pieces = []
97
97
98 for o, f in opmap:
98 for o, f in opmap:
99 if opts[o]:
99 if opts[o]:
100 l = [f(n) for n, dummy in lines]
100 l = [f(n) for n, dummy in lines]
101 if l:
101 if l:
102 m = max(map(len, l))
102 m = max(map(len, l))
103 pieces.append(["%*s" % (m, x) for x in l])
103 pieces.append(["%*s" % (m, x) for x in l])
104
104
105 if pieces:
105 if pieces:
106 for p, l in zip(zip(*pieces), lines):
106 for p, l in zip(zip(*pieces), lines):
107 ui.write("%s: %s" % (" ".join(p), l[1]))
107 ui.write("%s: %s" % (" ".join(p), l[1]))
108
108
109 def archive(ui, repo, dest, **opts):
109 def archive(ui, repo, dest, **opts):
110 '''create unversioned archive of a repository revision
110 '''create unversioned archive of a repository revision
111
111
112 By default, the revision used is the parent of the working
112 By default, the revision used is the parent of the working
113 directory; use "-r" to specify a different revision.
113 directory; use "-r" to specify a different revision.
114
114
115 To specify the type of archive to create, use "-t". Valid
115 To specify the type of archive to create, use "-t". Valid
116 types are:
116 types are:
117
117
118 "files" (default): a directory full of files
118 "files" (default): a directory full of files
119 "tar": tar archive, uncompressed
119 "tar": tar archive, uncompressed
120 "tbz2": tar archive, compressed using bzip2
120 "tbz2": tar archive, compressed using bzip2
121 "tgz": tar archive, compressed using gzip
121 "tgz": tar archive, compressed using gzip
122 "uzip": zip archive, uncompressed
122 "uzip": zip archive, uncompressed
123 "zip": zip archive, compressed using deflate
123 "zip": zip archive, compressed using deflate
124
124
125 The exact name of the destination archive or directory is given
125 The exact name of the destination archive or directory is given
126 using a format string; see "hg help export" for details.
126 using a format string; see "hg help export" for details.
127
127
128 Each member added to an archive file has a directory prefix
128 Each member added to an archive file has a directory prefix
129 prepended. Use "-p" to specify a format string for the prefix.
129 prepended. Use "-p" to specify a format string for the prefix.
130 The default is the basename of the archive, with suffixes removed.
130 The default is the basename of the archive, with suffixes removed.
131 '''
131 '''
132
132
133 node = repo.changectx(opts['rev']).node()
133 node = repo.changectx(opts['rev']).node()
134 dest = cmdutil.make_filename(repo, dest, node)
134 dest = cmdutil.make_filename(repo, dest, node)
135 if os.path.realpath(dest) == repo.root:
135 if os.path.realpath(dest) == repo.root:
136 raise util.Abort(_('repository root cannot be destination'))
136 raise util.Abort(_('repository root cannot be destination'))
137 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
137 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
138 kind = opts.get('type') or 'files'
138 kind = opts.get('type') or 'files'
139 prefix = opts['prefix']
139 prefix = opts['prefix']
140 if dest == '-':
140 if dest == '-':
141 if kind == 'files':
141 if kind == 'files':
142 raise util.Abort(_('cannot archive plain files to stdout'))
142 raise util.Abort(_('cannot archive plain files to stdout'))
143 dest = sys.stdout
143 dest = sys.stdout
144 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
144 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
145 prefix = cmdutil.make_filename(repo, prefix, node)
145 prefix = cmdutil.make_filename(repo, prefix, node)
146 archival.archive(repo, dest, node, kind, not opts['no_decode'],
146 archival.archive(repo, dest, node, kind, not opts['no_decode'],
147 matchfn, prefix)
147 matchfn, prefix)
148
148
149 def backout(ui, repo, node=None, rev=None, **opts):
149 def backout(ui, repo, node=None, rev=None, **opts):
150 '''reverse effect of earlier changeset
150 '''reverse effect of earlier changeset
151
151
152 Commit the backed out changes as a new changeset. The new
152 Commit the backed out changes as a new changeset. The new
153 changeset is a child of the backed out changeset.
153 changeset is a child of the backed out changeset.
154
154
155 If you back out a changeset other than the tip, a new head is
155 If you back out a changeset other than the tip, a new head is
156 created. This head is the parent of the working directory. If
156 created. This head is the parent of the working directory. If
157 you back out an old changeset, your working directory will appear
157 you back out an old changeset, your working directory will appear
158 old after the backout. You should merge the backout changeset
158 old after the backout. You should merge the backout changeset
159 with another head.
159 with another head.
160
160
161 The --merge option remembers the parent of the working directory
161 The --merge option remembers the parent of the working directory
162 before starting the backout, then merges the new head with that
162 before starting the backout, then merges the new head with that
163 changeset afterwards. This saves you from doing the merge by
163 changeset afterwards. This saves you from doing the merge by
164 hand. The result of this merge is not committed, as for a normal
164 hand. The result of this merge is not committed, as for a normal
165 merge.'''
165 merge.'''
166 if rev and node:
166 if rev and node:
167 raise util.Abort(_("please specify just one revision"))
167 raise util.Abort(_("please specify just one revision"))
168
168
169 if not rev:
169 if not rev:
170 rev = node
170 rev = node
171
171
172 cmdutil.bail_if_changed(repo)
172 cmdutil.bail_if_changed(repo)
173 op1, op2 = repo.dirstate.parents()
173 op1, op2 = repo.dirstate.parents()
174 if op2 != nullid:
174 if op2 != nullid:
175 raise util.Abort(_('outstanding uncommitted merge'))
175 raise util.Abort(_('outstanding uncommitted merge'))
176 node = repo.lookup(rev)
176 node = repo.lookup(rev)
177 p1, p2 = repo.changelog.parents(node)
177 p1, p2 = repo.changelog.parents(node)
178 if p1 == nullid:
178 if p1 == nullid:
179 raise util.Abort(_('cannot back out a change with no parents'))
179 raise util.Abort(_('cannot back out a change with no parents'))
180 if p2 != nullid:
180 if p2 != nullid:
181 if not opts['parent']:
181 if not opts['parent']:
182 raise util.Abort(_('cannot back out a merge changeset without '
182 raise util.Abort(_('cannot back out a merge changeset without '
183 '--parent'))
183 '--parent'))
184 p = repo.lookup(opts['parent'])
184 p = repo.lookup(opts['parent'])
185 if p not in (p1, p2):
185 if p not in (p1, p2):
186 raise util.Abort(_('%s is not a parent of %s') %
186 raise util.Abort(_('%s is not a parent of %s') %
187 (short(p), short(node)))
187 (short(p), short(node)))
188 parent = p
188 parent = p
189 else:
189 else:
190 if opts['parent']:
190 if opts['parent']:
191 raise util.Abort(_('cannot use --parent on non-merge changeset'))
191 raise util.Abort(_('cannot use --parent on non-merge changeset'))
192 parent = p1
192 parent = p1
193 hg.clean(repo, node, show_stats=False)
193 hg.clean(repo, node, show_stats=False)
194 revert_opts = opts.copy()
194 revert_opts = opts.copy()
195 revert_opts['date'] = None
195 revert_opts['date'] = None
196 revert_opts['all'] = True
196 revert_opts['all'] = True
197 revert_opts['rev'] = hex(parent)
197 revert_opts['rev'] = hex(parent)
198 revert(ui, repo, **revert_opts)
198 revert(ui, repo, **revert_opts)
199 commit_opts = opts.copy()
199 commit_opts = opts.copy()
200 commit_opts['addremove'] = False
200 commit_opts['addremove'] = False
201 if not commit_opts['message'] and not commit_opts['logfile']:
201 if not commit_opts['message'] and not commit_opts['logfile']:
202 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
202 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
203 commit_opts['force_editor'] = True
203 commit_opts['force_editor'] = True
204 commit(ui, repo, **commit_opts)
204 commit(ui, repo, **commit_opts)
205 def nice(node):
205 def nice(node):
206 return '%d:%s' % (repo.changelog.rev(node), short(node))
206 return '%d:%s' % (repo.changelog.rev(node), short(node))
207 ui.status(_('changeset %s backs out changeset %s\n') %
207 ui.status(_('changeset %s backs out changeset %s\n') %
208 (nice(repo.changelog.tip()), nice(node)))
208 (nice(repo.changelog.tip()), nice(node)))
209 if op1 != node:
209 if op1 != node:
210 if opts['merge']:
210 if opts['merge']:
211 ui.status(_('merging with changeset %s\n') % nice(op1))
211 ui.status(_('merging with changeset %s\n') % nice(op1))
212 hg.merge(repo, hex(op1))
212 hg.merge(repo, hex(op1))
213 else:
213 else:
214 ui.status(_('the backout changeset is a new head - '
214 ui.status(_('the backout changeset is a new head - '
215 'do not forget to merge\n'))
215 'do not forget to merge\n'))
216 ui.status(_('(use "backout --merge" '
216 ui.status(_('(use "backout --merge" '
217 'if you want to auto-merge)\n'))
217 'if you want to auto-merge)\n'))
218
218
219 def branch(ui, repo, label=None, **opts):
219 def branch(ui, repo, label=None, **opts):
220 """set or show the current branch name
220 """set or show the current branch name
221
221
222 With <name>, set the current branch name. Otherwise, show the
222 With <name>, set the current branch name. Otherwise, show the
223 current branch name.
223 current branch name.
224
224
225 Unless --force is specified, branch will not let you set a
225 Unless --force is specified, branch will not let you set a
226 branch name that shadows an existing branch.
226 branch name that shadows an existing branch.
227 """
227 """
228
228
229 if label:
229 if label:
230 if not opts.get('force') and label in repo.branchtags():
230 if not opts.get('force') and label in repo.branchtags():
231 if label not in [p.branch() for p in repo.workingctx().parents()]:
231 if label not in [p.branch() for p in repo.workingctx().parents()]:
232 raise util.Abort(_('a branch of the same name already exists'
232 raise util.Abort(_('a branch of the same name already exists'
233 ' (use --force to override)'))
233 ' (use --force to override)'))
234 repo.dirstate.setbranch(util.fromlocal(label))
234 repo.dirstate.setbranch(util.fromlocal(label))
235 else:
235 else:
236 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
236 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
237
237
238 def branches(ui, repo):
238 def branches(ui, repo):
239 """list repository named branches
239 """list repository named branches
240
240
241 List the repository's named branches.
241 List the repository's named branches.
242 """
242 """
243 b = repo.branchtags()
243 b = repo.branchtags()
244 l = [(-repo.changelog.rev(n), n, t) for t, n in b.items()]
244 l = [(-repo.changelog.rev(n), n, t) for t, n in b.items()]
245 l.sort()
245 l.sort()
246 for r, n, t in l:
246 for r, n, t in l:
247 hexfunc = ui.debugflag and hex or short
247 hexfunc = ui.debugflag and hex or short
248 if ui.quiet:
248 if ui.quiet:
249 ui.write("%s\n" % t)
249 ui.write("%s\n" % t)
250 else:
250 else:
251 spaces = " " * (30 - util.locallen(t))
251 spaces = " " * (30 - util.locallen(t))
252 ui.write("%s%s %s:%s\n" % (t, spaces, -r, hexfunc(n)))
252 ui.write("%s%s %s:%s\n" % (t, spaces, -r, hexfunc(n)))
253
253
254 def bundle(ui, repo, fname, dest=None, **opts):
254 def bundle(ui, repo, fname, dest=None, **opts):
255 """create a changegroup file
255 """create a changegroup file
256
256
257 Generate a compressed changegroup file collecting changesets not
257 Generate a compressed changegroup file collecting changesets not
258 found in the other repository.
258 found in the other repository.
259
259
260 If no destination repository is specified the destination is assumed
260 If no destination repository is specified the destination is assumed
261 to have all the nodes specified by one or more --base parameters.
261 to have all the nodes specified by one or more --base parameters.
262
262
263 The bundle file can then be transferred using conventional means and
263 The bundle file can then be transferred using conventional means and
264 applied to another repository with the unbundle or pull command.
264 applied to another repository with the unbundle or pull command.
265 This is useful when direct push and pull are not available or when
265 This is useful when direct push and pull are not available or when
266 exporting an entire repository is undesirable.
266 exporting an entire repository is undesirable.
267
267
268 Applying bundles preserves all changeset contents including
268 Applying bundles preserves all changeset contents including
269 permissions, copy/rename information, and revision history.
269 permissions, copy/rename information, and revision history.
270 """
270 """
271 revs = opts.get('rev') or None
271 revs = opts.get('rev') or None
272 if revs:
272 if revs:
273 revs = [repo.lookup(rev) for rev in revs]
273 revs = [repo.lookup(rev) for rev in revs]
274 base = opts.get('base')
274 base = opts.get('base')
275 if base:
275 if base:
276 if dest:
276 if dest:
277 raise util.Abort(_("--base is incompatible with specifiying "
277 raise util.Abort(_("--base is incompatible with specifiying "
278 "a destination"))
278 "a destination"))
279 base = [repo.lookup(rev) for rev in base]
279 base = [repo.lookup(rev) for rev in base]
280 # create the right base
280 # create the right base
281 # XXX: nodesbetween / changegroup* should be "fixed" instead
281 # XXX: nodesbetween / changegroup* should be "fixed" instead
282 o = []
282 o = []
283 has = {nullid: None}
283 has = {nullid: None}
284 for n in base:
284 for n in base:
285 has.update(repo.changelog.reachable(n))
285 has.update(repo.changelog.reachable(n))
286 if revs:
286 if revs:
287 visit = list(revs)
287 visit = list(revs)
288 else:
288 else:
289 visit = repo.changelog.heads()
289 visit = repo.changelog.heads()
290 seen = {}
290 seen = {}
291 while visit:
291 while visit:
292 n = visit.pop(0)
292 n = visit.pop(0)
293 parents = [p for p in repo.changelog.parents(n) if p not in has]
293 parents = [p for p in repo.changelog.parents(n) if p not in has]
294 if len(parents) == 0:
294 if len(parents) == 0:
295 o.insert(0, n)
295 o.insert(0, n)
296 else:
296 else:
297 for p in parents:
297 for p in parents:
298 if p not in seen:
298 if p not in seen:
299 seen[p] = 1
299 seen[p] = 1
300 visit.append(p)
300 visit.append(p)
301 else:
301 else:
302 cmdutil.setremoteconfig(ui, opts)
302 cmdutil.setremoteconfig(ui, opts)
303 dest, revs = cmdutil.parseurl(
303 dest, revs = cmdutil.parseurl(
304 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
304 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
305 other = hg.repository(ui, dest)
305 other = hg.repository(ui, dest)
306 o = repo.findoutgoing(other, force=opts['force'])
306 o = repo.findoutgoing(other, force=opts['force'])
307
307
308 if revs:
308 if revs:
309 cg = repo.changegroupsubset(o, revs, 'bundle')
309 cg = repo.changegroupsubset(o, revs, 'bundle')
310 else:
310 else:
311 cg = repo.changegroup(o, 'bundle')
311 cg = repo.changegroup(o, 'bundle')
312 changegroup.writebundle(cg, fname, "HG10BZ")
312 changegroup.writebundle(cg, fname, "HG10BZ")
313
313
314 def cat(ui, repo, file1, *pats, **opts):
314 def cat(ui, repo, file1, *pats, **opts):
315 """output the current or given revision of files
315 """output the current or given revision of files
316
316
317 Print the specified files as they were at the given revision.
317 Print the specified files as they were at the given revision.
318 If no revision is given, the parent of the working directory is used,
318 If no revision is given, the parent of the working directory is used,
319 or tip if no revision is checked out.
319 or tip if no revision is checked out.
320
320
321 Output may be to a file, in which case the name of the file is
321 Output may be to a file, in which case the name of the file is
322 given using a format string. The formatting rules are the same as
322 given using a format string. The formatting rules are the same as
323 for the export command, with the following additions:
323 for the export command, with the following additions:
324
324
325 %s basename of file being printed
325 %s basename of file being printed
326 %d dirname of file being printed, or '.' if in repo root
326 %d dirname of file being printed, or '.' if in repo root
327 %p root-relative path name of file being printed
327 %p root-relative path name of file being printed
328 """
328 """
329 ctx = repo.changectx(opts['rev'])
329 ctx = repo.changectx(opts['rev'])
330 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
330 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
331 ctx.node()):
331 ctx.node()):
332 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
332 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
333 fp.write(ctx.filectx(abs).data())
333 fp.write(ctx.filectx(abs).data())
334
334
335 def clone(ui, source, dest=None, **opts):
335 def clone(ui, source, dest=None, **opts):
336 """make a copy of an existing repository
336 """make a copy of an existing repository
337
337
338 Create a copy of an existing repository in a new directory.
338 Create a copy of an existing repository in a new directory.
339
339
340 If no destination directory name is specified, it defaults to the
340 If no destination directory name is specified, it defaults to the
341 basename of the source.
341 basename of the source.
342
342
343 The location of the source is added to the new repository's
343 The location of the source is added to the new repository's
344 .hg/hgrc file, as the default to be used for future pulls.
344 .hg/hgrc file, as the default to be used for future pulls.
345
345
346 For efficiency, hardlinks are used for cloning whenever the source
346 For efficiency, hardlinks are used for cloning whenever the source
347 and destination are on the same filesystem (note this applies only
347 and destination are on the same filesystem (note this applies only
348 to the repository data, not to the checked out files). Some
348 to the repository data, not to the checked out files). Some
349 filesystems, such as AFS, implement hardlinking incorrectly, but
349 filesystems, such as AFS, implement hardlinking incorrectly, but
350 do not report errors. In these cases, use the --pull option to
350 do not report errors. In these cases, use the --pull option to
351 avoid hardlinking.
351 avoid hardlinking.
352
352
353 You can safely clone repositories and checked out files using full
353 You can safely clone repositories and checked out files using full
354 hardlinks with
354 hardlinks with
355
355
356 $ cp -al REPO REPOCLONE
356 $ cp -al REPO REPOCLONE
357
357
358 which is the fastest way to clone. However, the operation is not
358 which is the fastest way to clone. However, the operation is not
359 atomic (making sure REPO is not modified during the operation is
359 atomic (making sure REPO is not modified during the operation is
360 up to you) and you have to make sure your editor breaks hardlinks
360 up to you) and you have to make sure your editor breaks hardlinks
361 (Emacs and most Linux Kernel tools do so).
361 (Emacs and most Linux Kernel tools do so).
362
362
363 If you use the -r option to clone up to a specific revision, no
363 If you use the -r option to clone up to a specific revision, no
364 subsequent revisions will be present in the cloned repository.
364 subsequent revisions will be present in the cloned repository.
365 This option implies --pull, even on local repositories.
365 This option implies --pull, even on local repositories.
366
366
367 See pull for valid source format details.
367 See pull for valid source format details.
368
368
369 It is possible to specify an ssh:// URL as the destination, but no
369 It is possible to specify an ssh:// URL as the destination, but no
370 .hg/hgrc and working directory will be created on the remote side.
370 .hg/hgrc and working directory will be created on the remote side.
371 Look at the help text for the pull command for important details
371 Look at the help text for the pull command for important details
372 about ssh:// URLs.
372 about ssh:// URLs.
373 """
373 """
374 cmdutil.setremoteconfig(ui, opts)
374 cmdutil.setremoteconfig(ui, opts)
375 hg.clone(ui, source, dest,
375 hg.clone(ui, source, dest,
376 pull=opts['pull'],
376 pull=opts['pull'],
377 stream=opts['uncompressed'],
377 stream=opts['uncompressed'],
378 rev=opts['rev'],
378 rev=opts['rev'],
379 update=not opts['noupdate'])
379 update=not opts['noupdate'])
380
380
381 def commit(ui, repo, *pats, **opts):
381 def commit(ui, repo, *pats, **opts):
382 """commit the specified files or all outstanding changes
382 """commit the specified files or all outstanding changes
383
383
384 Commit changes to the given files into the repository.
384 Commit changes to the given files into the repository.
385
385
386 If a list of files is omitted, all changes reported by "hg status"
386 If a list of files is omitted, all changes reported by "hg status"
387 will be committed.
387 will be committed.
388
388
389 If no commit message is specified, the editor configured in your hgrc
389 If no commit message is specified, the editor configured in your hgrc
390 or in the EDITOR environment variable is started to enter a message.
390 or in the EDITOR environment variable is started to enter a message.
391 """
391 """
392 message = cmdutil.logmessage(opts)
392 message = cmdutil.logmessage(opts)
393
393
394 if opts['addremove']:
394 if opts['addremove']:
395 cmdutil.addremove(repo, pats, opts)
395 cmdutil.addremove(repo, pats, opts)
396 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
396 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
397 if pats:
397 if pats:
398 status = repo.status(files=fns, match=match)
398 status = repo.status(files=fns, match=match)
399 modified, added, removed, deleted, unknown = status[:5]
399 modified, added, removed, deleted, unknown = status[:5]
400 files = modified + added + removed
400 files = modified + added + removed
401 slist = None
401 slist = None
402 for f in fns:
402 for f in fns:
403 if f == '.':
403 if f == '.':
404 continue
404 continue
405 if f not in files:
405 if f not in files:
406 rf = repo.wjoin(f)
406 rf = repo.wjoin(f)
407 if f in unknown:
408 raise util.Abort(_("file %s not tracked!") % rf)
409 try:
407 try:
410 mode = os.lstat(rf)[stat.ST_MODE]
408 mode = os.lstat(rf)[stat.ST_MODE]
411 except OSError:
409 except OSError:
412 raise util.Abort(_("file %s not found!") % rf)
410 raise util.Abort(_("file %s not found!") % rf)
413 if stat.S_ISDIR(mode):
411 if stat.S_ISDIR(mode):
414 name = f + '/'
412 name = f + '/'
415 if slist is None:
413 if slist is None:
416 slist = list(files)
414 slist = list(files)
417 slist.sort()
415 slist.sort()
418 i = bisect.bisect(slist, name)
416 i = bisect.bisect(slist, name)
419 if i >= len(slist) or not slist[i].startswith(name):
417 if i >= len(slist) or not slist[i].startswith(name):
420 raise util.Abort(_("no match under directory %s!")
418 raise util.Abort(_("no match under directory %s!")
421 % rf)
419 % rf)
422 elif not stat.S_ISREG(mode):
420 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
423 raise util.Abort(_("can't commit %s: "
421 raise util.Abort(_("can't commit %s: "
424 "unsupported file type!") % rf)
422 "unsupported file type!") % rf)
423 elif repo.dirstate.state(f) == '?':
424 raise util.Abort(_("file %s not tracked!") % rf)
425 else:
425 else:
426 files = []
426 files = []
427 try:
427 try:
428 repo.commit(files, message, opts['user'], opts['date'], match,
428 repo.commit(files, message, opts['user'], opts['date'], match,
429 force_editor=opts.get('force_editor'))
429 force_editor=opts.get('force_editor'))
430 except ValueError, inst:
430 except ValueError, inst:
431 raise util.Abort(str(inst))
431 raise util.Abort(str(inst))
432
432
433 def docopy(ui, repo, pats, opts, wlock):
433 def docopy(ui, repo, pats, opts, wlock):
434 # called with the repo lock held
434 # called with the repo lock held
435 #
435 #
436 # hgsep => pathname that uses "/" to separate directories
436 # hgsep => pathname that uses "/" to separate directories
437 # ossep => pathname that uses os.sep to separate directories
437 # ossep => pathname that uses os.sep to separate directories
438 cwd = repo.getcwd()
438 cwd = repo.getcwd()
439 errors = 0
439 errors = 0
440 copied = []
440 copied = []
441 targets = {}
441 targets = {}
442
442
443 # abs: hgsep
443 # abs: hgsep
444 # rel: ossep
444 # rel: ossep
445 # return: hgsep
445 # return: hgsep
446 def okaytocopy(abs, rel, exact):
446 def okaytocopy(abs, rel, exact):
447 reasons = {'?': _('is not managed'),
447 reasons = {'?': _('is not managed'),
448 'a': _('has been marked for add'),
448 'a': _('has been marked for add'),
449 'r': _('has been marked for remove')}
449 'r': _('has been marked for remove')}
450 state = repo.dirstate.state(abs)
450 state = repo.dirstate.state(abs)
451 reason = reasons.get(state)
451 reason = reasons.get(state)
452 if reason:
452 if reason:
453 if state == 'a':
453 if state == 'a':
454 origsrc = repo.dirstate.copied(abs)
454 origsrc = repo.dirstate.copied(abs)
455 if origsrc is not None:
455 if origsrc is not None:
456 return origsrc
456 return origsrc
457 if exact:
457 if exact:
458 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
458 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
459 else:
459 else:
460 return abs
460 return abs
461
461
462 # origsrc: hgsep
462 # origsrc: hgsep
463 # abssrc: hgsep
463 # abssrc: hgsep
464 # relsrc: ossep
464 # relsrc: ossep
465 # otarget: ossep
465 # otarget: ossep
466 def copy(origsrc, abssrc, relsrc, otarget, exact):
466 def copy(origsrc, abssrc, relsrc, otarget, exact):
467 abstarget = util.canonpath(repo.root, cwd, otarget)
467 abstarget = util.canonpath(repo.root, cwd, otarget)
468 reltarget = repo.pathto(abstarget, cwd)
468 reltarget = repo.pathto(abstarget, cwd)
469 prevsrc = targets.get(abstarget)
469 prevsrc = targets.get(abstarget)
470 src = repo.wjoin(abssrc)
470 src = repo.wjoin(abssrc)
471 target = repo.wjoin(abstarget)
471 target = repo.wjoin(abstarget)
472 if prevsrc is not None:
472 if prevsrc is not None:
473 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
473 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
474 (reltarget, repo.pathto(abssrc, cwd),
474 (reltarget, repo.pathto(abssrc, cwd),
475 repo.pathto(prevsrc, cwd)))
475 repo.pathto(prevsrc, cwd)))
476 return
476 return
477 if (not opts['after'] and os.path.exists(target) or
477 if (not opts['after'] and os.path.exists(target) or
478 opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
478 opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
479 if not opts['force']:
479 if not opts['force']:
480 ui.warn(_('%s: not overwriting - file exists\n') %
480 ui.warn(_('%s: not overwriting - file exists\n') %
481 reltarget)
481 reltarget)
482 return
482 return
483 if not opts['after'] and not opts.get('dry_run'):
483 if not opts['after'] and not opts.get('dry_run'):
484 os.unlink(target)
484 os.unlink(target)
485 if opts['after']:
485 if opts['after']:
486 if not os.path.exists(target):
486 if not os.path.exists(target):
487 return
487 return
488 else:
488 else:
489 targetdir = os.path.dirname(target) or '.'
489 targetdir = os.path.dirname(target) or '.'
490 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
490 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
491 os.makedirs(targetdir)
491 os.makedirs(targetdir)
492 try:
492 try:
493 restore = repo.dirstate.state(abstarget) == 'r'
493 restore = repo.dirstate.state(abstarget) == 'r'
494 if restore and not opts.get('dry_run'):
494 if restore and not opts.get('dry_run'):
495 repo.undelete([abstarget], wlock)
495 repo.undelete([abstarget], wlock)
496 try:
496 try:
497 if not opts.get('dry_run'):
497 if not opts.get('dry_run'):
498 util.copyfile(src, target)
498 util.copyfile(src, target)
499 restore = False
499 restore = False
500 finally:
500 finally:
501 if restore:
501 if restore:
502 repo.remove([abstarget], wlock=wlock)
502 repo.remove([abstarget], wlock=wlock)
503 except IOError, inst:
503 except IOError, inst:
504 if inst.errno == errno.ENOENT:
504 if inst.errno == errno.ENOENT:
505 ui.warn(_('%s: deleted in working copy\n') % relsrc)
505 ui.warn(_('%s: deleted in working copy\n') % relsrc)
506 else:
506 else:
507 ui.warn(_('%s: cannot copy - %s\n') %
507 ui.warn(_('%s: cannot copy - %s\n') %
508 (relsrc, inst.strerror))
508 (relsrc, inst.strerror))
509 errors += 1
509 errors += 1
510 return
510 return
511 if ui.verbose or not exact:
511 if ui.verbose or not exact:
512 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
512 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
513 targets[abstarget] = abssrc
513 targets[abstarget] = abssrc
514 if abstarget != origsrc and not opts.get('dry_run'):
514 if abstarget != origsrc and not opts.get('dry_run'):
515 repo.copy(origsrc, abstarget, wlock)
515 repo.copy(origsrc, abstarget, wlock)
516 copied.append((abssrc, relsrc, exact))
516 copied.append((abssrc, relsrc, exact))
517
517
518 # pat: ossep
518 # pat: ossep
519 # dest ossep
519 # dest ossep
520 # srcs: list of (hgsep, hgsep, ossep, bool)
520 # srcs: list of (hgsep, hgsep, ossep, bool)
521 # return: function that takes hgsep and returns ossep
521 # return: function that takes hgsep and returns ossep
522 def targetpathfn(pat, dest, srcs):
522 def targetpathfn(pat, dest, srcs):
523 if os.path.isdir(pat):
523 if os.path.isdir(pat):
524 abspfx = util.canonpath(repo.root, cwd, pat)
524 abspfx = util.canonpath(repo.root, cwd, pat)
525 abspfx = util.localpath(abspfx)
525 abspfx = util.localpath(abspfx)
526 if destdirexists:
526 if destdirexists:
527 striplen = len(os.path.split(abspfx)[0])
527 striplen = len(os.path.split(abspfx)[0])
528 else:
528 else:
529 striplen = len(abspfx)
529 striplen = len(abspfx)
530 if striplen:
530 if striplen:
531 striplen += len(os.sep)
531 striplen += len(os.sep)
532 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
532 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
533 elif destdirexists:
533 elif destdirexists:
534 res = lambda p: os.path.join(dest,
534 res = lambda p: os.path.join(dest,
535 os.path.basename(util.localpath(p)))
535 os.path.basename(util.localpath(p)))
536 else:
536 else:
537 res = lambda p: dest
537 res = lambda p: dest
538 return res
538 return res
539
539
540 # pat: ossep
540 # pat: ossep
541 # dest ossep
541 # dest ossep
542 # srcs: list of (hgsep, hgsep, ossep, bool)
542 # srcs: list of (hgsep, hgsep, ossep, bool)
543 # return: function that takes hgsep and returns ossep
543 # return: function that takes hgsep and returns ossep
544 def targetpathafterfn(pat, dest, srcs):
544 def targetpathafterfn(pat, dest, srcs):
545 if util.patkind(pat, None)[0]:
545 if util.patkind(pat, None)[0]:
546 # a mercurial pattern
546 # a mercurial pattern
547 res = lambda p: os.path.join(dest,
547 res = lambda p: os.path.join(dest,
548 os.path.basename(util.localpath(p)))
548 os.path.basename(util.localpath(p)))
549 else:
549 else:
550 abspfx = util.canonpath(repo.root, cwd, pat)
550 abspfx = util.canonpath(repo.root, cwd, pat)
551 if len(abspfx) < len(srcs[0][0]):
551 if len(abspfx) < len(srcs[0][0]):
552 # A directory. Either the target path contains the last
552 # A directory. Either the target path contains the last
553 # component of the source path or it does not.
553 # component of the source path or it does not.
554 def evalpath(striplen):
554 def evalpath(striplen):
555 score = 0
555 score = 0
556 for s in srcs:
556 for s in srcs:
557 t = os.path.join(dest, util.localpath(s[0])[striplen:])
557 t = os.path.join(dest, util.localpath(s[0])[striplen:])
558 if os.path.exists(t):
558 if os.path.exists(t):
559 score += 1
559 score += 1
560 return score
560 return score
561
561
562 abspfx = util.localpath(abspfx)
562 abspfx = util.localpath(abspfx)
563 striplen = len(abspfx)
563 striplen = len(abspfx)
564 if striplen:
564 if striplen:
565 striplen += len(os.sep)
565 striplen += len(os.sep)
566 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
566 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
567 score = evalpath(striplen)
567 score = evalpath(striplen)
568 striplen1 = len(os.path.split(abspfx)[0])
568 striplen1 = len(os.path.split(abspfx)[0])
569 if striplen1:
569 if striplen1:
570 striplen1 += len(os.sep)
570 striplen1 += len(os.sep)
571 if evalpath(striplen1) > score:
571 if evalpath(striplen1) > score:
572 striplen = striplen1
572 striplen = striplen1
573 res = lambda p: os.path.join(dest,
573 res = lambda p: os.path.join(dest,
574 util.localpath(p)[striplen:])
574 util.localpath(p)[striplen:])
575 else:
575 else:
576 # a file
576 # a file
577 if destdirexists:
577 if destdirexists:
578 res = lambda p: os.path.join(dest,
578 res = lambda p: os.path.join(dest,
579 os.path.basename(util.localpath(p)))
579 os.path.basename(util.localpath(p)))
580 else:
580 else:
581 res = lambda p: dest
581 res = lambda p: dest
582 return res
582 return res
583
583
584
584
585 pats = util.expand_glob(pats)
585 pats = util.expand_glob(pats)
586 if not pats:
586 if not pats:
587 raise util.Abort(_('no source or destination specified'))
587 raise util.Abort(_('no source or destination specified'))
588 if len(pats) == 1:
588 if len(pats) == 1:
589 raise util.Abort(_('no destination specified'))
589 raise util.Abort(_('no destination specified'))
590 dest = pats.pop()
590 dest = pats.pop()
591 destdirexists = os.path.isdir(dest)
591 destdirexists = os.path.isdir(dest)
592 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
592 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
593 raise util.Abort(_('with multiple sources, destination must be an '
593 raise util.Abort(_('with multiple sources, destination must be an '
594 'existing directory'))
594 'existing directory'))
595 if opts['after']:
595 if opts['after']:
596 tfn = targetpathafterfn
596 tfn = targetpathafterfn
597 else:
597 else:
598 tfn = targetpathfn
598 tfn = targetpathfn
599 copylist = []
599 copylist = []
600 for pat in pats:
600 for pat in pats:
601 srcs = []
601 srcs = []
602 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
602 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
603 globbed=True):
603 globbed=True):
604 origsrc = okaytocopy(abssrc, relsrc, exact)
604 origsrc = okaytocopy(abssrc, relsrc, exact)
605 if origsrc:
605 if origsrc:
606 srcs.append((origsrc, abssrc, relsrc, exact))
606 srcs.append((origsrc, abssrc, relsrc, exact))
607 if not srcs:
607 if not srcs:
608 continue
608 continue
609 copylist.append((tfn(pat, dest, srcs), srcs))
609 copylist.append((tfn(pat, dest, srcs), srcs))
610 if not copylist:
610 if not copylist:
611 raise util.Abort(_('no files to copy'))
611 raise util.Abort(_('no files to copy'))
612
612
613 for targetpath, srcs in copylist:
613 for targetpath, srcs in copylist:
614 for origsrc, abssrc, relsrc, exact in srcs:
614 for origsrc, abssrc, relsrc, exact in srcs:
615 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
615 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
616
616
617 if errors:
617 if errors:
618 ui.warn(_('(consider using --after)\n'))
618 ui.warn(_('(consider using --after)\n'))
619 return errors, copied
619 return errors, copied
620
620
621 def copy(ui, repo, *pats, **opts):
621 def copy(ui, repo, *pats, **opts):
622 """mark files as copied for the next commit
622 """mark files as copied for the next commit
623
623
624 Mark dest as having copies of source files. If dest is a
624 Mark dest as having copies of source files. If dest is a
625 directory, copies are put in that directory. If dest is a file,
625 directory, copies are put in that directory. If dest is a file,
626 there can only be one source.
626 there can only be one source.
627
627
628 By default, this command copies the contents of files as they
628 By default, this command copies the contents of files as they
629 stand in the working directory. If invoked with --after, the
629 stand in the working directory. If invoked with --after, the
630 operation is recorded, but no copying is performed.
630 operation is recorded, but no copying is performed.
631
631
632 This command takes effect in the next commit. To undo a copy
632 This command takes effect in the next commit. To undo a copy
633 before that, see hg revert.
633 before that, see hg revert.
634 """
634 """
635 wlock = repo.wlock(0)
635 wlock = repo.wlock(0)
636 errs, copied = docopy(ui, repo, pats, opts, wlock)
636 errs, copied = docopy(ui, repo, pats, opts, wlock)
637 return errs
637 return errs
638
638
639 def debugancestor(ui, index, rev1, rev2):
639 def debugancestor(ui, index, rev1, rev2):
640 """find the ancestor revision of two revisions in a given index"""
640 """find the ancestor revision of two revisions in a given index"""
641 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
641 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
642 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
642 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
643 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
643 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
644
644
645 def debugcomplete(ui, cmd='', **opts):
645 def debugcomplete(ui, cmd='', **opts):
646 """returns the completion list associated with the given command"""
646 """returns the completion list associated with the given command"""
647
647
648 if opts['options']:
648 if opts['options']:
649 options = []
649 options = []
650 otables = [globalopts]
650 otables = [globalopts]
651 if cmd:
651 if cmd:
652 aliases, entry = cmdutil.findcmd(ui, cmd)
652 aliases, entry = cmdutil.findcmd(ui, cmd)
653 otables.append(entry[1])
653 otables.append(entry[1])
654 for t in otables:
654 for t in otables:
655 for o in t:
655 for o in t:
656 if o[0]:
656 if o[0]:
657 options.append('-%s' % o[0])
657 options.append('-%s' % o[0])
658 options.append('--%s' % o[1])
658 options.append('--%s' % o[1])
659 ui.write("%s\n" % "\n".join(options))
659 ui.write("%s\n" % "\n".join(options))
660 return
660 return
661
661
662 clist = cmdutil.findpossible(ui, cmd).keys()
662 clist = cmdutil.findpossible(ui, cmd).keys()
663 clist.sort()
663 clist.sort()
664 ui.write("%s\n" % "\n".join(clist))
664 ui.write("%s\n" % "\n".join(clist))
665
665
666 def debugrebuildstate(ui, repo, rev=""):
666 def debugrebuildstate(ui, repo, rev=""):
667 """rebuild the dirstate as it would look like for the given revision"""
667 """rebuild the dirstate as it would look like for the given revision"""
668 if rev == "":
668 if rev == "":
669 rev = repo.changelog.tip()
669 rev = repo.changelog.tip()
670 ctx = repo.changectx(rev)
670 ctx = repo.changectx(rev)
671 files = ctx.manifest()
671 files = ctx.manifest()
672 wlock = repo.wlock()
672 wlock = repo.wlock()
673 repo.dirstate.rebuild(rev, files)
673 repo.dirstate.rebuild(rev, files)
674
674
675 def debugcheckstate(ui, repo):
675 def debugcheckstate(ui, repo):
676 """validate the correctness of the current dirstate"""
676 """validate the correctness of the current dirstate"""
677 parent1, parent2 = repo.dirstate.parents()
677 parent1, parent2 = repo.dirstate.parents()
678 repo.dirstate.read()
678 repo.dirstate.read()
679 dc = repo.dirstate.map
679 dc = repo.dirstate.map
680 keys = dc.keys()
680 keys = dc.keys()
681 keys.sort()
681 keys.sort()
682 m1 = repo.changectx(parent1).manifest()
682 m1 = repo.changectx(parent1).manifest()
683 m2 = repo.changectx(parent2).manifest()
683 m2 = repo.changectx(parent2).manifest()
684 errors = 0
684 errors = 0
685 for f in dc:
685 for f in dc:
686 state = repo.dirstate.state(f)
686 state = repo.dirstate.state(f)
687 if state in "nr" and f not in m1:
687 if state in "nr" and f not in m1:
688 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
688 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
689 errors += 1
689 errors += 1
690 if state in "a" and f in m1:
690 if state in "a" and f in m1:
691 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
691 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
692 errors += 1
692 errors += 1
693 if state in "m" and f not in m1 and f not in m2:
693 if state in "m" and f not in m1 and f not in m2:
694 ui.warn(_("%s in state %s, but not in either manifest\n") %
694 ui.warn(_("%s in state %s, but not in either manifest\n") %
695 (f, state))
695 (f, state))
696 errors += 1
696 errors += 1
697 for f in m1:
697 for f in m1:
698 state = repo.dirstate.state(f)
698 state = repo.dirstate.state(f)
699 if state not in "nrm":
699 if state not in "nrm":
700 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
700 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
701 errors += 1
701 errors += 1
702 if errors:
702 if errors:
703 error = _(".hg/dirstate inconsistent with current parent's manifest")
703 error = _(".hg/dirstate inconsistent with current parent's manifest")
704 raise util.Abort(error)
704 raise util.Abort(error)
705
705
706 def showconfig(ui, repo, *values, **opts):
706 def showconfig(ui, repo, *values, **opts):
707 """show combined config settings from all hgrc files
707 """show combined config settings from all hgrc files
708
708
709 With no args, print names and values of all config items.
709 With no args, print names and values of all config items.
710
710
711 With one arg of the form section.name, print just the value of
711 With one arg of the form section.name, print just the value of
712 that config item.
712 that config item.
713
713
714 With multiple args, print names and values of all config items
714 With multiple args, print names and values of all config items
715 with matching section names."""
715 with matching section names."""
716
716
717 untrusted = bool(opts.get('untrusted'))
717 untrusted = bool(opts.get('untrusted'))
718 if values:
718 if values:
719 if len([v for v in values if '.' in v]) > 1:
719 if len([v for v in values if '.' in v]) > 1:
720 raise util.Abort(_('only one config item permitted'))
720 raise util.Abort(_('only one config item permitted'))
721 for section, name, value in ui.walkconfig(untrusted=untrusted):
721 for section, name, value in ui.walkconfig(untrusted=untrusted):
722 sectname = section + '.' + name
722 sectname = section + '.' + name
723 if values:
723 if values:
724 for v in values:
724 for v in values:
725 if v == section:
725 if v == section:
726 ui.write('%s=%s\n' % (sectname, value))
726 ui.write('%s=%s\n' % (sectname, value))
727 elif v == sectname:
727 elif v == sectname:
728 ui.write(value, '\n')
728 ui.write(value, '\n')
729 else:
729 else:
730 ui.write('%s=%s\n' % (sectname, value))
730 ui.write('%s=%s\n' % (sectname, value))
731
731
732 def debugsetparents(ui, repo, rev1, rev2=None):
732 def debugsetparents(ui, repo, rev1, rev2=None):
733 """manually set the parents of the current working directory
733 """manually set the parents of the current working directory
734
734
735 This is useful for writing repository conversion tools, but should
735 This is useful for writing repository conversion tools, but should
736 be used with care.
736 be used with care.
737 """
737 """
738
738
739 if not rev2:
739 if not rev2:
740 rev2 = hex(nullid)
740 rev2 = hex(nullid)
741
741
742 wlock = repo.wlock()
742 wlock = repo.wlock()
743 try:
743 try:
744 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
744 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
745 finally:
745 finally:
746 wlock.release()
746 wlock.release()
747
747
748 def debugstate(ui, repo):
748 def debugstate(ui, repo):
749 """show the contents of the current dirstate"""
749 """show the contents of the current dirstate"""
750 repo.dirstate.read()
750 repo.dirstate.read()
751 dc = repo.dirstate.map
751 dc = repo.dirstate.map
752 keys = dc.keys()
752 keys = dc.keys()
753 keys.sort()
753 keys.sort()
754 for file_ in keys:
754 for file_ in keys:
755 if dc[file_][3] == -1:
755 if dc[file_][3] == -1:
756 # Pad or slice to locale representation
756 # Pad or slice to locale representation
757 locale_len = len(time.strftime("%x %X", time.localtime(0)))
757 locale_len = len(time.strftime("%x %X", time.localtime(0)))
758 timestr = 'unset'
758 timestr = 'unset'
759 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
759 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
760 else:
760 else:
761 timestr = time.strftime("%x %X", time.localtime(dc[file_][3]))
761 timestr = time.strftime("%x %X", time.localtime(dc[file_][3]))
762 ui.write("%c %3o %10d %s %s\n"
762 ui.write("%c %3o %10d %s %s\n"
763 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
763 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
764 timestr, file_))
764 timestr, file_))
765 for f in repo.dirstate.copies():
765 for f in repo.dirstate.copies():
766 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
766 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
767
767
768 def debugdata(ui, file_, rev):
768 def debugdata(ui, file_, rev):
769 """dump the contents of a data file revision"""
769 """dump the contents of a data file revision"""
770 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
770 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
771 try:
771 try:
772 ui.write(r.revision(r.lookup(rev)))
772 ui.write(r.revision(r.lookup(rev)))
773 except KeyError:
773 except KeyError:
774 raise util.Abort(_('invalid revision identifier %s') % rev)
774 raise util.Abort(_('invalid revision identifier %s') % rev)
775
775
776 def debugdate(ui, date, range=None, **opts):
776 def debugdate(ui, date, range=None, **opts):
777 """parse and display a date"""
777 """parse and display a date"""
778 if opts["extended"]:
778 if opts["extended"]:
779 d = util.parsedate(date, util.extendeddateformats)
779 d = util.parsedate(date, util.extendeddateformats)
780 else:
780 else:
781 d = util.parsedate(date)
781 d = util.parsedate(date)
782 ui.write("internal: %s %s\n" % d)
782 ui.write("internal: %s %s\n" % d)
783 ui.write("standard: %s\n" % util.datestr(d))
783 ui.write("standard: %s\n" % util.datestr(d))
784 if range:
784 if range:
785 m = util.matchdate(range)
785 m = util.matchdate(range)
786 ui.write("match: %s\n" % m(d[0]))
786 ui.write("match: %s\n" % m(d[0]))
787
787
788 def debugindex(ui, file_):
788 def debugindex(ui, file_):
789 """dump the contents of an index file"""
789 """dump the contents of an index file"""
790 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
790 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
791 ui.write(" rev offset length base linkrev" +
791 ui.write(" rev offset length base linkrev" +
792 " nodeid p1 p2\n")
792 " nodeid p1 p2\n")
793 for i in xrange(r.count()):
793 for i in xrange(r.count()):
794 node = r.node(i)
794 node = r.node(i)
795 pp = r.parents(node)
795 pp = r.parents(node)
796 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
796 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
797 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
797 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
798 short(node), short(pp[0]), short(pp[1])))
798 short(node), short(pp[0]), short(pp[1])))
799
799
800 def debugindexdot(ui, file_):
800 def debugindexdot(ui, file_):
801 """dump an index DAG as a .dot file"""
801 """dump an index DAG as a .dot file"""
802 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
802 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
803 ui.write("digraph G {\n")
803 ui.write("digraph G {\n")
804 for i in xrange(r.count()):
804 for i in xrange(r.count()):
805 node = r.node(i)
805 node = r.node(i)
806 pp = r.parents(node)
806 pp = r.parents(node)
807 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
807 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
808 if pp[1] != nullid:
808 if pp[1] != nullid:
809 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
809 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
810 ui.write("}\n")
810 ui.write("}\n")
811
811
812 def debuginstall(ui):
812 def debuginstall(ui):
813 '''test Mercurial installation'''
813 '''test Mercurial installation'''
814
814
815 def writetemp(contents):
815 def writetemp(contents):
816 (fd, name) = tempfile.mkstemp()
816 (fd, name) = tempfile.mkstemp()
817 f = os.fdopen(fd, "wb")
817 f = os.fdopen(fd, "wb")
818 f.write(contents)
818 f.write(contents)
819 f.close()
819 f.close()
820 return name
820 return name
821
821
822 problems = 0
822 problems = 0
823
823
824 # encoding
824 # encoding
825 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
825 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
826 try:
826 try:
827 util.fromlocal("test")
827 util.fromlocal("test")
828 except util.Abort, inst:
828 except util.Abort, inst:
829 ui.write(" %s\n" % inst)
829 ui.write(" %s\n" % inst)
830 ui.write(_(" (check that your locale is properly set)\n"))
830 ui.write(_(" (check that your locale is properly set)\n"))
831 problems += 1
831 problems += 1
832
832
833 # compiled modules
833 # compiled modules
834 ui.status(_("Checking extensions...\n"))
834 ui.status(_("Checking extensions...\n"))
835 try:
835 try:
836 import bdiff, mpatch, base85
836 import bdiff, mpatch, base85
837 except Exception, inst:
837 except Exception, inst:
838 ui.write(" %s\n" % inst)
838 ui.write(" %s\n" % inst)
839 ui.write(_(" One or more extensions could not be found"))
839 ui.write(_(" One or more extensions could not be found"))
840 ui.write(_(" (check that you compiled the extensions)\n"))
840 ui.write(_(" (check that you compiled the extensions)\n"))
841 problems += 1
841 problems += 1
842
842
843 # templates
843 # templates
844 ui.status(_("Checking templates...\n"))
844 ui.status(_("Checking templates...\n"))
845 try:
845 try:
846 import templater
846 import templater
847 t = templater.templater(templater.templatepath("map-cmdline.default"))
847 t = templater.templater(templater.templatepath("map-cmdline.default"))
848 except Exception, inst:
848 except Exception, inst:
849 ui.write(" %s\n" % inst)
849 ui.write(" %s\n" % inst)
850 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
850 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
851 problems += 1
851 problems += 1
852
852
853 # patch
853 # patch
854 ui.status(_("Checking patch...\n"))
854 ui.status(_("Checking patch...\n"))
855 patcher = ui.config('ui', 'patch')
855 patcher = ui.config('ui', 'patch')
856 patcher = ((patcher and util.find_exe(patcher)) or
856 patcher = ((patcher and util.find_exe(patcher)) or
857 util.find_exe('gpatch') or
857 util.find_exe('gpatch') or
858 util.find_exe('patch'))
858 util.find_exe('patch'))
859 if not patcher:
859 if not patcher:
860 ui.write(_(" Can't find patch or gpatch in PATH\n"))
860 ui.write(_(" Can't find patch or gpatch in PATH\n"))
861 ui.write(_(" (specify a patch utility in your .hgrc file)\n"))
861 ui.write(_(" (specify a patch utility in your .hgrc file)\n"))
862 problems += 1
862 problems += 1
863 else:
863 else:
864 # actually attempt a patch here
864 # actually attempt a patch here
865 a = "1\n2\n3\n4\n"
865 a = "1\n2\n3\n4\n"
866 b = "1\n2\n3\ninsert\n4\n"
866 b = "1\n2\n3\ninsert\n4\n"
867 fa = writetemp(a)
867 fa = writetemp(a)
868 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
868 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
869 fd = writetemp(d)
869 fd = writetemp(d)
870
870
871 files = {}
871 files = {}
872 try:
872 try:
873 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
873 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
874 except util.Abort, e:
874 except util.Abort, e:
875 ui.write(_(" patch call failed:\n"))
875 ui.write(_(" patch call failed:\n"))
876 ui.write(" " + str(e) + "\n")
876 ui.write(" " + str(e) + "\n")
877 problems += 1
877 problems += 1
878 else:
878 else:
879 if list(files) != [os.path.basename(fa)]:
879 if list(files) != [os.path.basename(fa)]:
880 ui.write(_(" unexpected patch output!"))
880 ui.write(_(" unexpected patch output!"))
881 ui.write(_(" (you may have an incompatible version of patch)\n"))
881 ui.write(_(" (you may have an incompatible version of patch)\n"))
882 problems += 1
882 problems += 1
883 a = file(fa).read()
883 a = file(fa).read()
884 if a != b:
884 if a != b:
885 ui.write(_(" patch test failed!"))
885 ui.write(_(" patch test failed!"))
886 ui.write(_(" (you may have an incompatible version of patch)\n"))
886 ui.write(_(" (you may have an incompatible version of patch)\n"))
887 problems += 1
887 problems += 1
888
888
889 os.unlink(fa)
889 os.unlink(fa)
890 os.unlink(fd)
890 os.unlink(fd)
891
891
892 # merge helper
892 # merge helper
893 ui.status(_("Checking merge helper...\n"))
893 ui.status(_("Checking merge helper...\n"))
894 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
894 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
895 or "hgmerge")
895 or "hgmerge")
896 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
896 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
897 if not cmdpath:
897 if not cmdpath:
898 if cmd == 'hgmerge':
898 if cmd == 'hgmerge':
899 ui.write(_(" No merge helper set and can't find default"
899 ui.write(_(" No merge helper set and can't find default"
900 " hgmerge script in PATH\n"))
900 " hgmerge script in PATH\n"))
901 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
901 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
902 else:
902 else:
903 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
903 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
904 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
904 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
905 problems += 1
905 problems += 1
906 else:
906 else:
907 # actually attempt a patch here
907 # actually attempt a patch here
908 fa = writetemp("1\n2\n3\n4\n")
908 fa = writetemp("1\n2\n3\n4\n")
909 fl = writetemp("1\n2\n3\ninsert\n4\n")
909 fl = writetemp("1\n2\n3\ninsert\n4\n")
910 fr = writetemp("begin\n1\n2\n3\n4\n")
910 fr = writetemp("begin\n1\n2\n3\n4\n")
911 r = os.system('%s %s %s %s' % (cmd, fl, fa, fr))
911 r = os.system('%s %s %s %s' % (cmd, fl, fa, fr))
912 if r:
912 if r:
913 ui.write(_(" got unexpected merge error %d!") % r)
913 ui.write(_(" got unexpected merge error %d!") % r)
914 problems += 1
914 problems += 1
915 m = file(fl).read()
915 m = file(fl).read()
916 if m != "begin\n1\n2\n3\ninsert\n4\n":
916 if m != "begin\n1\n2\n3\ninsert\n4\n":
917 ui.write(_(" got unexpected merge results!") % r)
917 ui.write(_(" got unexpected merge results!") % r)
918 ui.write(_(" (your merge helper may have the"
918 ui.write(_(" (your merge helper may have the"
919 " wrong argument order)\n"))
919 " wrong argument order)\n"))
920 ui.write(m)
920 ui.write(m)
921 os.unlink(fa)
921 os.unlink(fa)
922 os.unlink(fl)
922 os.unlink(fl)
923 os.unlink(fr)
923 os.unlink(fr)
924
924
925 # editor
925 # editor
926 ui.status(_("Checking commit editor...\n"))
926 ui.status(_("Checking commit editor...\n"))
927 editor = (os.environ.get("HGEDITOR") or
927 editor = (os.environ.get("HGEDITOR") or
928 ui.config("ui", "editor") or
928 ui.config("ui", "editor") or
929 os.environ.get("EDITOR", "vi"))
929 os.environ.get("EDITOR", "vi"))
930 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
930 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
931 if not cmdpath:
931 if not cmdpath:
932 if editor == 'vi':
932 if editor == 'vi':
933 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
933 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
934 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
934 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
935 else:
935 else:
936 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
936 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
937 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
937 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
938 problems += 1
938 problems += 1
939
939
940 # check username
940 # check username
941 ui.status(_("Checking username...\n"))
941 ui.status(_("Checking username...\n"))
942 user = os.environ.get("HGUSER")
942 user = os.environ.get("HGUSER")
943 if user is None:
943 if user is None:
944 user = ui.config("ui", "username")
944 user = ui.config("ui", "username")
945 if user is None:
945 if user is None:
946 user = os.environ.get("EMAIL")
946 user = os.environ.get("EMAIL")
947 if not user:
947 if not user:
948 ui.warn(" ")
948 ui.warn(" ")
949 ui.username()
949 ui.username()
950 ui.write(_(" (specify a username in your .hgrc file)\n"))
950 ui.write(_(" (specify a username in your .hgrc file)\n"))
951
951
952 if not problems:
952 if not problems:
953 ui.status(_("No problems detected\n"))
953 ui.status(_("No problems detected\n"))
954 else:
954 else:
955 ui.write(_("%s problems detected,"
955 ui.write(_("%s problems detected,"
956 " please check your install!\n") % problems)
956 " please check your install!\n") % problems)
957
957
958 return problems
958 return problems
959
959
960 def debugrename(ui, repo, file1, *pats, **opts):
960 def debugrename(ui, repo, file1, *pats, **opts):
961 """dump rename information"""
961 """dump rename information"""
962
962
963 ctx = repo.changectx(opts.get('rev', 'tip'))
963 ctx = repo.changectx(opts.get('rev', 'tip'))
964 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
964 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
965 ctx.node()):
965 ctx.node()):
966 m = ctx.filectx(abs).renamed()
966 m = ctx.filectx(abs).renamed()
967 if m:
967 if m:
968 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
968 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
969 else:
969 else:
970 ui.write(_("%s not renamed\n") % rel)
970 ui.write(_("%s not renamed\n") % rel)
971
971
972 def debugwalk(ui, repo, *pats, **opts):
972 def debugwalk(ui, repo, *pats, **opts):
973 """show how files match on given patterns"""
973 """show how files match on given patterns"""
974 items = list(cmdutil.walk(repo, pats, opts))
974 items = list(cmdutil.walk(repo, pats, opts))
975 if not items:
975 if not items:
976 return
976 return
977 fmt = '%%s %%-%ds %%-%ds %%s' % (
977 fmt = '%%s %%-%ds %%-%ds %%s' % (
978 max([len(abs) for (src, abs, rel, exact) in items]),
978 max([len(abs) for (src, abs, rel, exact) in items]),
979 max([len(rel) for (src, abs, rel, exact) in items]))
979 max([len(rel) for (src, abs, rel, exact) in items]))
980 for src, abs, rel, exact in items:
980 for src, abs, rel, exact in items:
981 line = fmt % (src, abs, rel, exact and 'exact' or '')
981 line = fmt % (src, abs, rel, exact and 'exact' or '')
982 ui.write("%s\n" % line.rstrip())
982 ui.write("%s\n" % line.rstrip())
983
983
984 def diff(ui, repo, *pats, **opts):
984 def diff(ui, repo, *pats, **opts):
985 """diff repository (or selected files)
985 """diff repository (or selected files)
986
986
987 Show differences between revisions for the specified files.
987 Show differences between revisions for the specified files.
988
988
989 Differences between files are shown using the unified diff format.
989 Differences between files are shown using the unified diff format.
990
990
991 NOTE: diff may generate unexpected results for merges, as it will
991 NOTE: diff may generate unexpected results for merges, as it will
992 default to comparing against the working directory's first parent
992 default to comparing against the working directory's first parent
993 changeset if no revisions are specified.
993 changeset if no revisions are specified.
994
994
995 When two revision arguments are given, then changes are shown
995 When two revision arguments are given, then changes are shown
996 between those revisions. If only one revision is specified then
996 between those revisions. If only one revision is specified then
997 that revision is compared to the working directory, and, when no
997 that revision is compared to the working directory, and, when no
998 revisions are specified, the working directory files are compared
998 revisions are specified, the working directory files are compared
999 to its parent.
999 to its parent.
1000
1000
1001 Without the -a option, diff will avoid generating diffs of files
1001 Without the -a option, diff will avoid generating diffs of files
1002 it detects as binary. With -a, diff will generate a diff anyway,
1002 it detects as binary. With -a, diff will generate a diff anyway,
1003 probably with undesirable results.
1003 probably with undesirable results.
1004 """
1004 """
1005 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1005 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1006
1006
1007 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1007 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1008
1008
1009 patch.diff(repo, node1, node2, fns, match=matchfn,
1009 patch.diff(repo, node1, node2, fns, match=matchfn,
1010 opts=patch.diffopts(ui, opts))
1010 opts=patch.diffopts(ui, opts))
1011
1011
1012 def export(ui, repo, *changesets, **opts):
1012 def export(ui, repo, *changesets, **opts):
1013 """dump the header and diffs for one or more changesets
1013 """dump the header and diffs for one or more changesets
1014
1014
1015 Print the changeset header and diffs for one or more revisions.
1015 Print the changeset header and diffs for one or more revisions.
1016
1016
1017 The information shown in the changeset header is: author,
1017 The information shown in the changeset header is: author,
1018 changeset hash, parent(s) and commit comment.
1018 changeset hash, parent(s) and commit comment.
1019
1019
1020 NOTE: export may generate unexpected diff output for merge changesets,
1020 NOTE: export may generate unexpected diff output for merge changesets,
1021 as it will compare the merge changeset against its first parent only.
1021 as it will compare the merge changeset against its first parent only.
1022
1022
1023 Output may be to a file, in which case the name of the file is
1023 Output may be to a file, in which case the name of the file is
1024 given using a format string. The formatting rules are as follows:
1024 given using a format string. The formatting rules are as follows:
1025
1025
1026 %% literal "%" character
1026 %% literal "%" character
1027 %H changeset hash (40 bytes of hexadecimal)
1027 %H changeset hash (40 bytes of hexadecimal)
1028 %N number of patches being generated
1028 %N number of patches being generated
1029 %R changeset revision number
1029 %R changeset revision number
1030 %b basename of the exporting repository
1030 %b basename of the exporting repository
1031 %h short-form changeset hash (12 bytes of hexadecimal)
1031 %h short-form changeset hash (12 bytes of hexadecimal)
1032 %n zero-padded sequence number, starting at 1
1032 %n zero-padded sequence number, starting at 1
1033 %r zero-padded changeset revision number
1033 %r zero-padded changeset revision number
1034
1034
1035 Without the -a option, export will avoid generating diffs of files
1035 Without the -a option, export will avoid generating diffs of files
1036 it detects as binary. With -a, export will generate a diff anyway,
1036 it detects as binary. With -a, export will generate a diff anyway,
1037 probably with undesirable results.
1037 probably with undesirable results.
1038
1038
1039 With the --switch-parent option, the diff will be against the second
1039 With the --switch-parent option, the diff will be against the second
1040 parent. It can be useful to review a merge.
1040 parent. It can be useful to review a merge.
1041 """
1041 """
1042 if not changesets:
1042 if not changesets:
1043 raise util.Abort(_("export requires at least one changeset"))
1043 raise util.Abort(_("export requires at least one changeset"))
1044 revs = cmdutil.revrange(repo, changesets)
1044 revs = cmdutil.revrange(repo, changesets)
1045 if len(revs) > 1:
1045 if len(revs) > 1:
1046 ui.note(_('exporting patches:\n'))
1046 ui.note(_('exporting patches:\n'))
1047 else:
1047 else:
1048 ui.note(_('exporting patch:\n'))
1048 ui.note(_('exporting patch:\n'))
1049 patch.export(repo, revs, template=opts['output'],
1049 patch.export(repo, revs, template=opts['output'],
1050 switch_parent=opts['switch_parent'],
1050 switch_parent=opts['switch_parent'],
1051 opts=patch.diffopts(ui, opts))
1051 opts=patch.diffopts(ui, opts))
1052
1052
1053 def grep(ui, repo, pattern, *pats, **opts):
1053 def grep(ui, repo, pattern, *pats, **opts):
1054 """search for a pattern in specified files and revisions
1054 """search for a pattern in specified files and revisions
1055
1055
1056 Search revisions of files for a regular expression.
1056 Search revisions of files for a regular expression.
1057
1057
1058 This command behaves differently than Unix grep. It only accepts
1058 This command behaves differently than Unix grep. It only accepts
1059 Python/Perl regexps. It searches repository history, not the
1059 Python/Perl regexps. It searches repository history, not the
1060 working directory. It always prints the revision number in which
1060 working directory. It always prints the revision number in which
1061 a match appears.
1061 a match appears.
1062
1062
1063 By default, grep only prints output for the first revision of a
1063 By default, grep only prints output for the first revision of a
1064 file in which it finds a match. To get it to print every revision
1064 file in which it finds a match. To get it to print every revision
1065 that contains a change in match status ("-" for a match that
1065 that contains a change in match status ("-" for a match that
1066 becomes a non-match, or "+" for a non-match that becomes a match),
1066 becomes a non-match, or "+" for a non-match that becomes a match),
1067 use the --all flag.
1067 use the --all flag.
1068 """
1068 """
1069 reflags = 0
1069 reflags = 0
1070 if opts['ignore_case']:
1070 if opts['ignore_case']:
1071 reflags |= re.I
1071 reflags |= re.I
1072 regexp = re.compile(pattern, reflags)
1072 regexp = re.compile(pattern, reflags)
1073 sep, eol = ':', '\n'
1073 sep, eol = ':', '\n'
1074 if opts['print0']:
1074 if opts['print0']:
1075 sep = eol = '\0'
1075 sep = eol = '\0'
1076
1076
1077 fcache = {}
1077 fcache = {}
1078 def getfile(fn):
1078 def getfile(fn):
1079 if fn not in fcache:
1079 if fn not in fcache:
1080 fcache[fn] = repo.file(fn)
1080 fcache[fn] = repo.file(fn)
1081 return fcache[fn]
1081 return fcache[fn]
1082
1082
1083 def matchlines(body):
1083 def matchlines(body):
1084 begin = 0
1084 begin = 0
1085 linenum = 0
1085 linenum = 0
1086 while True:
1086 while True:
1087 match = regexp.search(body, begin)
1087 match = regexp.search(body, begin)
1088 if not match:
1088 if not match:
1089 break
1089 break
1090 mstart, mend = match.span()
1090 mstart, mend = match.span()
1091 linenum += body.count('\n', begin, mstart) + 1
1091 linenum += body.count('\n', begin, mstart) + 1
1092 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1092 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1093 lend = body.find('\n', mend)
1093 lend = body.find('\n', mend)
1094 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1094 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1095 begin = lend + 1
1095 begin = lend + 1
1096
1096
1097 class linestate(object):
1097 class linestate(object):
1098 def __init__(self, line, linenum, colstart, colend):
1098 def __init__(self, line, linenum, colstart, colend):
1099 self.line = line
1099 self.line = line
1100 self.linenum = linenum
1100 self.linenum = linenum
1101 self.colstart = colstart
1101 self.colstart = colstart
1102 self.colend = colend
1102 self.colend = colend
1103
1103
1104 def __eq__(self, other):
1104 def __eq__(self, other):
1105 return self.line == other.line
1105 return self.line == other.line
1106
1106
1107 matches = {}
1107 matches = {}
1108 copies = {}
1108 copies = {}
1109 def grepbody(fn, rev, body):
1109 def grepbody(fn, rev, body):
1110 matches[rev].setdefault(fn, [])
1110 matches[rev].setdefault(fn, [])
1111 m = matches[rev][fn]
1111 m = matches[rev][fn]
1112 for lnum, cstart, cend, line in matchlines(body):
1112 for lnum, cstart, cend, line in matchlines(body):
1113 s = linestate(line, lnum, cstart, cend)
1113 s = linestate(line, lnum, cstart, cend)
1114 m.append(s)
1114 m.append(s)
1115
1115
1116 def difflinestates(a, b):
1116 def difflinestates(a, b):
1117 sm = difflib.SequenceMatcher(None, a, b)
1117 sm = difflib.SequenceMatcher(None, a, b)
1118 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1118 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1119 if tag == 'insert':
1119 if tag == 'insert':
1120 for i in xrange(blo, bhi):
1120 for i in xrange(blo, bhi):
1121 yield ('+', b[i])
1121 yield ('+', b[i])
1122 elif tag == 'delete':
1122 elif tag == 'delete':
1123 for i in xrange(alo, ahi):
1123 for i in xrange(alo, ahi):
1124 yield ('-', a[i])
1124 yield ('-', a[i])
1125 elif tag == 'replace':
1125 elif tag == 'replace':
1126 for i in xrange(alo, ahi):
1126 for i in xrange(alo, ahi):
1127 yield ('-', a[i])
1127 yield ('-', a[i])
1128 for i in xrange(blo, bhi):
1128 for i in xrange(blo, bhi):
1129 yield ('+', b[i])
1129 yield ('+', b[i])
1130
1130
1131 prev = {}
1131 prev = {}
1132 def display(fn, rev, states, prevstates):
1132 def display(fn, rev, states, prevstates):
1133 found = False
1133 found = False
1134 filerevmatches = {}
1134 filerevmatches = {}
1135 r = prev.get(fn, -1)
1135 r = prev.get(fn, -1)
1136 if opts['all']:
1136 if opts['all']:
1137 iter = difflinestates(states, prevstates)
1137 iter = difflinestates(states, prevstates)
1138 else:
1138 else:
1139 iter = [('', l) for l in prevstates]
1139 iter = [('', l) for l in prevstates]
1140 for change, l in iter:
1140 for change, l in iter:
1141 cols = [fn, str(r)]
1141 cols = [fn, str(r)]
1142 if opts['line_number']:
1142 if opts['line_number']:
1143 cols.append(str(l.linenum))
1143 cols.append(str(l.linenum))
1144 if opts['all']:
1144 if opts['all']:
1145 cols.append(change)
1145 cols.append(change)
1146 if opts['user']:
1146 if opts['user']:
1147 cols.append(ui.shortuser(get(r)[1]))
1147 cols.append(ui.shortuser(get(r)[1]))
1148 if opts['files_with_matches']:
1148 if opts['files_with_matches']:
1149 c = (fn, r)
1149 c = (fn, r)
1150 if c in filerevmatches:
1150 if c in filerevmatches:
1151 continue
1151 continue
1152 filerevmatches[c] = 1
1152 filerevmatches[c] = 1
1153 else:
1153 else:
1154 cols.append(l.line)
1154 cols.append(l.line)
1155 ui.write(sep.join(cols), eol)
1155 ui.write(sep.join(cols), eol)
1156 found = True
1156 found = True
1157 return found
1157 return found
1158
1158
1159 fstate = {}
1159 fstate = {}
1160 skip = {}
1160 skip = {}
1161 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1161 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1162 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1162 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1163 found = False
1163 found = False
1164 follow = opts.get('follow')
1164 follow = opts.get('follow')
1165 for st, rev, fns in changeiter:
1165 for st, rev, fns in changeiter:
1166 if st == 'window':
1166 if st == 'window':
1167 matches.clear()
1167 matches.clear()
1168 elif st == 'add':
1168 elif st == 'add':
1169 mf = repo.changectx(rev).manifest()
1169 mf = repo.changectx(rev).manifest()
1170 matches[rev] = {}
1170 matches[rev] = {}
1171 for fn in fns:
1171 for fn in fns:
1172 if fn in skip:
1172 if fn in skip:
1173 continue
1173 continue
1174 fstate.setdefault(fn, {})
1174 fstate.setdefault(fn, {})
1175 try:
1175 try:
1176 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1176 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1177 if follow:
1177 if follow:
1178 copied = getfile(fn).renamed(mf[fn])
1178 copied = getfile(fn).renamed(mf[fn])
1179 if copied:
1179 if copied:
1180 copies.setdefault(rev, {})[fn] = copied[0]
1180 copies.setdefault(rev, {})[fn] = copied[0]
1181 except KeyError:
1181 except KeyError:
1182 pass
1182 pass
1183 elif st == 'iter':
1183 elif st == 'iter':
1184 states = matches[rev].items()
1184 states = matches[rev].items()
1185 states.sort()
1185 states.sort()
1186 for fn, m in states:
1186 for fn, m in states:
1187 copy = copies.get(rev, {}).get(fn)
1187 copy = copies.get(rev, {}).get(fn)
1188 if fn in skip:
1188 if fn in skip:
1189 if copy:
1189 if copy:
1190 skip[copy] = True
1190 skip[copy] = True
1191 continue
1191 continue
1192 if fn in prev or fstate[fn]:
1192 if fn in prev or fstate[fn]:
1193 r = display(fn, rev, m, fstate[fn])
1193 r = display(fn, rev, m, fstate[fn])
1194 found = found or r
1194 found = found or r
1195 if r and not opts['all']:
1195 if r and not opts['all']:
1196 skip[fn] = True
1196 skip[fn] = True
1197 if copy:
1197 if copy:
1198 skip[copy] = True
1198 skip[copy] = True
1199 fstate[fn] = m
1199 fstate[fn] = m
1200 if copy:
1200 if copy:
1201 fstate[copy] = m
1201 fstate[copy] = m
1202 prev[fn] = rev
1202 prev[fn] = rev
1203
1203
1204 fstate = fstate.items()
1204 fstate = fstate.items()
1205 fstate.sort()
1205 fstate.sort()
1206 for fn, state in fstate:
1206 for fn, state in fstate:
1207 if fn in skip:
1207 if fn in skip:
1208 continue
1208 continue
1209 if fn not in copies.get(prev[fn], {}):
1209 if fn not in copies.get(prev[fn], {}):
1210 found = display(fn, rev, {}, state) or found
1210 found = display(fn, rev, {}, state) or found
1211 return (not found and 1) or 0
1211 return (not found and 1) or 0
1212
1212
1213 def heads(ui, repo, **opts):
1213 def heads(ui, repo, **opts):
1214 """show current repository heads
1214 """show current repository heads
1215
1215
1216 Show all repository head changesets.
1216 Show all repository head changesets.
1217
1217
1218 Repository "heads" are changesets that don't have children
1218 Repository "heads" are changesets that don't have children
1219 changesets. They are where development generally takes place and
1219 changesets. They are where development generally takes place and
1220 are the usual targets for update and merge operations.
1220 are the usual targets for update and merge operations.
1221 """
1221 """
1222 if opts['rev']:
1222 if opts['rev']:
1223 heads = repo.heads(repo.lookup(opts['rev']))
1223 heads = repo.heads(repo.lookup(opts['rev']))
1224 else:
1224 else:
1225 heads = repo.heads()
1225 heads = repo.heads()
1226 displayer = cmdutil.show_changeset(ui, repo, opts)
1226 displayer = cmdutil.show_changeset(ui, repo, opts)
1227 for n in heads:
1227 for n in heads:
1228 displayer.show(changenode=n)
1228 displayer.show(changenode=n)
1229
1229
1230 def help_(ui, name=None, with_version=False):
1230 def help_(ui, name=None, with_version=False):
1231 """show help for a command, extension, or list of commands
1231 """show help for a command, extension, or list of commands
1232
1232
1233 With no arguments, print a list of commands and short help.
1233 With no arguments, print a list of commands and short help.
1234
1234
1235 Given a command name, print help for that command.
1235 Given a command name, print help for that command.
1236
1236
1237 Given an extension name, print help for that extension, and the
1237 Given an extension name, print help for that extension, and the
1238 commands it provides."""
1238 commands it provides."""
1239 option_lists = []
1239 option_lists = []
1240
1240
1241 def addglobalopts(aliases):
1241 def addglobalopts(aliases):
1242 if ui.verbose:
1242 if ui.verbose:
1243 option_lists.append((_("global options:"), globalopts))
1243 option_lists.append((_("global options:"), globalopts))
1244 if name == 'shortlist':
1244 if name == 'shortlist':
1245 option_lists.append((_('use "hg help" for the full list '
1245 option_lists.append((_('use "hg help" for the full list '
1246 'of commands'), ()))
1246 'of commands'), ()))
1247 else:
1247 else:
1248 if name == 'shortlist':
1248 if name == 'shortlist':
1249 msg = _('use "hg help" for the full list of commands '
1249 msg = _('use "hg help" for the full list of commands '
1250 'or "hg -v" for details')
1250 'or "hg -v" for details')
1251 elif aliases:
1251 elif aliases:
1252 msg = _('use "hg -v help%s" to show aliases and '
1252 msg = _('use "hg -v help%s" to show aliases and '
1253 'global options') % (name and " " + name or "")
1253 'global options') % (name and " " + name or "")
1254 else:
1254 else:
1255 msg = _('use "hg -v help %s" to show global options') % name
1255 msg = _('use "hg -v help %s" to show global options') % name
1256 option_lists.append((msg, ()))
1256 option_lists.append((msg, ()))
1257
1257
1258 def helpcmd(name):
1258 def helpcmd(name):
1259 if with_version:
1259 if with_version:
1260 version_(ui)
1260 version_(ui)
1261 ui.write('\n')
1261 ui.write('\n')
1262 aliases, i = cmdutil.findcmd(ui, name)
1262 aliases, i = cmdutil.findcmd(ui, name)
1263 # synopsis
1263 # synopsis
1264 ui.write("%s\n\n" % i[2])
1264 ui.write("%s\n\n" % i[2])
1265
1265
1266 # description
1266 # description
1267 doc = i[0].__doc__
1267 doc = i[0].__doc__
1268 if not doc:
1268 if not doc:
1269 doc = _("(No help text available)")
1269 doc = _("(No help text available)")
1270 if ui.quiet:
1270 if ui.quiet:
1271 doc = doc.splitlines(0)[0]
1271 doc = doc.splitlines(0)[0]
1272 ui.write("%s\n" % doc.rstrip())
1272 ui.write("%s\n" % doc.rstrip())
1273
1273
1274 if not ui.quiet:
1274 if not ui.quiet:
1275 # aliases
1275 # aliases
1276 if len(aliases) > 1:
1276 if len(aliases) > 1:
1277 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1277 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1278
1278
1279 # options
1279 # options
1280 if i[1]:
1280 if i[1]:
1281 option_lists.append((_("options:\n"), i[1]))
1281 option_lists.append((_("options:\n"), i[1]))
1282
1282
1283 addglobalopts(False)
1283 addglobalopts(False)
1284
1284
1285 def helplist(select=None):
1285 def helplist(select=None):
1286 h = {}
1286 h = {}
1287 cmds = {}
1287 cmds = {}
1288 for c, e in table.items():
1288 for c, e in table.items():
1289 f = c.split("|", 1)[0]
1289 f = c.split("|", 1)[0]
1290 if select and not select(f):
1290 if select and not select(f):
1291 continue
1291 continue
1292 if name == "shortlist" and not f.startswith("^"):
1292 if name == "shortlist" and not f.startswith("^"):
1293 continue
1293 continue
1294 f = f.lstrip("^")
1294 f = f.lstrip("^")
1295 if not ui.debugflag and f.startswith("debug"):
1295 if not ui.debugflag and f.startswith("debug"):
1296 continue
1296 continue
1297 doc = e[0].__doc__
1297 doc = e[0].__doc__
1298 if not doc:
1298 if not doc:
1299 doc = _("(No help text available)")
1299 doc = _("(No help text available)")
1300 h[f] = doc.splitlines(0)[0].rstrip()
1300 h[f] = doc.splitlines(0)[0].rstrip()
1301 cmds[f] = c.lstrip("^")
1301 cmds[f] = c.lstrip("^")
1302
1302
1303 fns = h.keys()
1303 fns = h.keys()
1304 fns.sort()
1304 fns.sort()
1305 m = max(map(len, fns))
1305 m = max(map(len, fns))
1306 for f in fns:
1306 for f in fns:
1307 if ui.verbose:
1307 if ui.verbose:
1308 commands = cmds[f].replace("|",", ")
1308 commands = cmds[f].replace("|",", ")
1309 ui.write(" %s:\n %s\n"%(commands, h[f]))
1309 ui.write(" %s:\n %s\n"%(commands, h[f]))
1310 else:
1310 else:
1311 ui.write(' %-*s %s\n' % (m, f, h[f]))
1311 ui.write(' %-*s %s\n' % (m, f, h[f]))
1312
1312
1313 if not ui.quiet:
1313 if not ui.quiet:
1314 addglobalopts(True)
1314 addglobalopts(True)
1315
1315
1316 def helptopic(name):
1316 def helptopic(name):
1317 v = None
1317 v = None
1318 for i in help.helptable:
1318 for i in help.helptable:
1319 l = i.split('|')
1319 l = i.split('|')
1320 if name in l:
1320 if name in l:
1321 v = i
1321 v = i
1322 header = l[-1]
1322 header = l[-1]
1323 if not v:
1323 if not v:
1324 raise cmdutil.UnknownCommand(name)
1324 raise cmdutil.UnknownCommand(name)
1325
1325
1326 # description
1326 # description
1327 doc = help.helptable[v]
1327 doc = help.helptable[v]
1328 if not doc:
1328 if not doc:
1329 doc = _("(No help text available)")
1329 doc = _("(No help text available)")
1330 if callable(doc):
1330 if callable(doc):
1331 doc = doc()
1331 doc = doc()
1332
1332
1333 ui.write("%s\n" % header)
1333 ui.write("%s\n" % header)
1334 ui.write("%s\n" % doc.rstrip())
1334 ui.write("%s\n" % doc.rstrip())
1335
1335
1336 def helpext(name):
1336 def helpext(name):
1337 try:
1337 try:
1338 mod = extensions.find(name)
1338 mod = extensions.find(name)
1339 except KeyError:
1339 except KeyError:
1340 raise cmdutil.UnknownCommand(name)
1340 raise cmdutil.UnknownCommand(name)
1341
1341
1342 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1342 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1343 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1343 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1344 for d in doc[1:]:
1344 for d in doc[1:]:
1345 ui.write(d, '\n')
1345 ui.write(d, '\n')
1346
1346
1347 ui.status('\n')
1347 ui.status('\n')
1348
1348
1349 try:
1349 try:
1350 ct = mod.cmdtable
1350 ct = mod.cmdtable
1351 except AttributeError:
1351 except AttributeError:
1352 ui.status(_('no commands defined\n'))
1352 ui.status(_('no commands defined\n'))
1353 return
1353 return
1354
1354
1355 ui.status(_('list of commands:\n\n'))
1355 ui.status(_('list of commands:\n\n'))
1356 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1356 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1357 helplist(modcmds.has_key)
1357 helplist(modcmds.has_key)
1358
1358
1359 if name and name != 'shortlist':
1359 if name and name != 'shortlist':
1360 i = None
1360 i = None
1361 for f in (helpcmd, helptopic, helpext):
1361 for f in (helpcmd, helptopic, helpext):
1362 try:
1362 try:
1363 f(name)
1363 f(name)
1364 i = None
1364 i = None
1365 break
1365 break
1366 except cmdutil.UnknownCommand, inst:
1366 except cmdutil.UnknownCommand, inst:
1367 i = inst
1367 i = inst
1368 if i:
1368 if i:
1369 raise i
1369 raise i
1370
1370
1371 else:
1371 else:
1372 # program name
1372 # program name
1373 if ui.verbose or with_version:
1373 if ui.verbose or with_version:
1374 version_(ui)
1374 version_(ui)
1375 else:
1375 else:
1376 ui.status(_("Mercurial Distributed SCM\n"))
1376 ui.status(_("Mercurial Distributed SCM\n"))
1377 ui.status('\n')
1377 ui.status('\n')
1378
1378
1379 # list of commands
1379 # list of commands
1380 if name == "shortlist":
1380 if name == "shortlist":
1381 ui.status(_('basic commands:\n\n'))
1381 ui.status(_('basic commands:\n\n'))
1382 else:
1382 else:
1383 ui.status(_('list of commands:\n\n'))
1383 ui.status(_('list of commands:\n\n'))
1384
1384
1385 helplist()
1385 helplist()
1386
1386
1387 # list all option lists
1387 # list all option lists
1388 opt_output = []
1388 opt_output = []
1389 for title, options in option_lists:
1389 for title, options in option_lists:
1390 opt_output.append(("\n%s" % title, None))
1390 opt_output.append(("\n%s" % title, None))
1391 for shortopt, longopt, default, desc in options:
1391 for shortopt, longopt, default, desc in options:
1392 if "DEPRECATED" in desc and not ui.verbose: continue
1392 if "DEPRECATED" in desc and not ui.verbose: continue
1393 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1393 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1394 longopt and " --%s" % longopt),
1394 longopt and " --%s" % longopt),
1395 "%s%s" % (desc,
1395 "%s%s" % (desc,
1396 default
1396 default
1397 and _(" (default: %s)") % default
1397 and _(" (default: %s)") % default
1398 or "")))
1398 or "")))
1399
1399
1400 if opt_output:
1400 if opt_output:
1401 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1401 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1402 for first, second in opt_output:
1402 for first, second in opt_output:
1403 if second:
1403 if second:
1404 ui.write(" %-*s %s\n" % (opts_len, first, second))
1404 ui.write(" %-*s %s\n" % (opts_len, first, second))
1405 else:
1405 else:
1406 ui.write("%s\n" % first)
1406 ui.write("%s\n" % first)
1407
1407
1408 def identify(ui, repo):
1408 def identify(ui, repo):
1409 """print information about the working copy
1409 """print information about the working copy
1410
1410
1411 Print a short summary of the current state of the repo.
1411 Print a short summary of the current state of the repo.
1412
1412
1413 This summary identifies the repository state using one or two parent
1413 This summary identifies the repository state using one or two parent
1414 hash identifiers, followed by a "+" if there are uncommitted changes
1414 hash identifiers, followed by a "+" if there are uncommitted changes
1415 in the working directory, followed by a list of tags for this revision.
1415 in the working directory, followed by a list of tags for this revision.
1416 """
1416 """
1417 parents = [p for p in repo.dirstate.parents() if p != nullid]
1417 parents = [p for p in repo.dirstate.parents() if p != nullid]
1418 if not parents:
1418 if not parents:
1419 ui.write(_("unknown\n"))
1419 ui.write(_("unknown\n"))
1420 return
1420 return
1421
1421
1422 hexfunc = ui.debugflag and hex or short
1422 hexfunc = ui.debugflag and hex or short
1423 modified, added, removed, deleted = repo.status()[:4]
1423 modified, added, removed, deleted = repo.status()[:4]
1424 output = ["%s%s" %
1424 output = ["%s%s" %
1425 ('+'.join([hexfunc(parent) for parent in parents]),
1425 ('+'.join([hexfunc(parent) for parent in parents]),
1426 (modified or added or removed or deleted) and "+" or "")]
1426 (modified or added or removed or deleted) and "+" or "")]
1427
1427
1428 if not ui.quiet:
1428 if not ui.quiet:
1429
1429
1430 branch = util.tolocal(repo.workingctx().branch())
1430 branch = util.tolocal(repo.workingctx().branch())
1431 if branch != 'default':
1431 if branch != 'default':
1432 output.append("(%s)" % branch)
1432 output.append("(%s)" % branch)
1433
1433
1434 # multiple tags for a single parent separated by '/'
1434 # multiple tags for a single parent separated by '/'
1435 parenttags = ['/'.join(tags)
1435 parenttags = ['/'.join(tags)
1436 for tags in map(repo.nodetags, parents) if tags]
1436 for tags in map(repo.nodetags, parents) if tags]
1437 # tags for multiple parents separated by ' + '
1437 # tags for multiple parents separated by ' + '
1438 if parenttags:
1438 if parenttags:
1439 output.append(' + '.join(parenttags))
1439 output.append(' + '.join(parenttags))
1440
1440
1441 ui.write("%s\n" % ' '.join(output))
1441 ui.write("%s\n" % ' '.join(output))
1442
1442
1443 def import_(ui, repo, patch1, *patches, **opts):
1443 def import_(ui, repo, patch1, *patches, **opts):
1444 """import an ordered set of patches
1444 """import an ordered set of patches
1445
1445
1446 Import a list of patches and commit them individually.
1446 Import a list of patches and commit them individually.
1447
1447
1448 If there are outstanding changes in the working directory, import
1448 If there are outstanding changes in the working directory, import
1449 will abort unless given the -f flag.
1449 will abort unless given the -f flag.
1450
1450
1451 You can import a patch straight from a mail message. Even patches
1451 You can import a patch straight from a mail message. Even patches
1452 as attachments work (body part must be type text/plain or
1452 as attachments work (body part must be type text/plain or
1453 text/x-patch to be used). From and Subject headers of email
1453 text/x-patch to be used). From and Subject headers of email
1454 message are used as default committer and commit message. All
1454 message are used as default committer and commit message. All
1455 text/plain body parts before first diff are added to commit
1455 text/plain body parts before first diff are added to commit
1456 message.
1456 message.
1457
1457
1458 If the imported patch was generated by hg export, user and description
1458 If the imported patch was generated by hg export, user and description
1459 from patch override values from message headers and body. Values
1459 from patch override values from message headers and body. Values
1460 given on command line with -m and -u override these.
1460 given on command line with -m and -u override these.
1461
1461
1462 If --exact is specified, import will set the working directory
1462 If --exact is specified, import will set the working directory
1463 to the parent of each patch before applying it, and will abort
1463 to the parent of each patch before applying it, and will abort
1464 if the resulting changeset has a different ID than the one
1464 if the resulting changeset has a different ID than the one
1465 recorded in the patch. This may happen due to character set
1465 recorded in the patch. This may happen due to character set
1466 problems or other deficiencies in the text patch format.
1466 problems or other deficiencies in the text patch format.
1467
1467
1468 To read a patch from standard input, use patch name "-".
1468 To read a patch from standard input, use patch name "-".
1469 """
1469 """
1470 patches = (patch1,) + patches
1470 patches = (patch1,) + patches
1471
1471
1472 if opts.get('exact') or not opts['force']:
1472 if opts.get('exact') or not opts['force']:
1473 cmdutil.bail_if_changed(repo)
1473 cmdutil.bail_if_changed(repo)
1474
1474
1475 d = opts["base"]
1475 d = opts["base"]
1476 strip = opts["strip"]
1476 strip = opts["strip"]
1477
1477
1478 wlock = repo.wlock()
1478 wlock = repo.wlock()
1479 lock = repo.lock()
1479 lock = repo.lock()
1480
1480
1481 for p in patches:
1481 for p in patches:
1482 pf = os.path.join(d, p)
1482 pf = os.path.join(d, p)
1483
1483
1484 if pf == '-':
1484 if pf == '-':
1485 ui.status(_("applying patch from stdin\n"))
1485 ui.status(_("applying patch from stdin\n"))
1486 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, sys.stdin)
1486 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, sys.stdin)
1487 else:
1487 else:
1488 ui.status(_("applying %s\n") % p)
1488 ui.status(_("applying %s\n") % p)
1489 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, file(pf))
1489 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, file(pf))
1490
1490
1491 if tmpname is None:
1491 if tmpname is None:
1492 raise util.Abort(_('no diffs found'))
1492 raise util.Abort(_('no diffs found'))
1493
1493
1494 try:
1494 try:
1495 cmdline_message = cmdutil.logmessage(opts)
1495 cmdline_message = cmdutil.logmessage(opts)
1496 if cmdline_message:
1496 if cmdline_message:
1497 # pickup the cmdline msg
1497 # pickup the cmdline msg
1498 message = cmdline_message
1498 message = cmdline_message
1499 elif message:
1499 elif message:
1500 # pickup the patch msg
1500 # pickup the patch msg
1501 message = message.strip()
1501 message = message.strip()
1502 else:
1502 else:
1503 # launch the editor
1503 # launch the editor
1504 message = None
1504 message = None
1505 ui.debug(_('message:\n%s\n') % message)
1505 ui.debug(_('message:\n%s\n') % message)
1506
1506
1507 wp = repo.workingctx().parents()
1507 wp = repo.workingctx().parents()
1508 if opts.get('exact'):
1508 if opts.get('exact'):
1509 if not nodeid or not p1:
1509 if not nodeid or not p1:
1510 raise util.Abort(_('not a mercurial patch'))
1510 raise util.Abort(_('not a mercurial patch'))
1511 p1 = repo.lookup(p1)
1511 p1 = repo.lookup(p1)
1512 p2 = repo.lookup(p2 or hex(nullid))
1512 p2 = repo.lookup(p2 or hex(nullid))
1513
1513
1514 if p1 != wp[0].node():
1514 if p1 != wp[0].node():
1515 hg.clean(repo, p1, wlock=wlock)
1515 hg.clean(repo, p1, wlock=wlock)
1516 repo.dirstate.setparents(p1, p2)
1516 repo.dirstate.setparents(p1, p2)
1517 repo.dirstate.setbranch(branch or 'default')
1517 repo.dirstate.setbranch(branch or 'default')
1518 elif p2:
1518 elif p2:
1519 try:
1519 try:
1520 p1 = repo.lookup(p1)
1520 p1 = repo.lookup(p1)
1521 p2 = repo.lookup(p2)
1521 p2 = repo.lookup(p2)
1522 if p1 == wp[0].node():
1522 if p1 == wp[0].node():
1523 repo.dirstate.setparents(p1, p2)
1523 repo.dirstate.setparents(p1, p2)
1524 except hg.RepoError:
1524 except hg.RepoError:
1525 pass
1525 pass
1526
1526
1527 files = {}
1527 files = {}
1528 try:
1528 try:
1529 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1529 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1530 files=files)
1530 files=files)
1531 finally:
1531 finally:
1532 files = patch.updatedir(ui, repo, files, wlock=wlock)
1532 files = patch.updatedir(ui, repo, files, wlock=wlock)
1533 n = repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1533 n = repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1534 if opts.get('exact'):
1534 if opts.get('exact'):
1535 if hex(n) != nodeid:
1535 if hex(n) != nodeid:
1536 repo.rollback(wlock=wlock, lock=lock)
1536 repo.rollback(wlock=wlock, lock=lock)
1537 raise util.Abort(_('patch is damaged or loses information'))
1537 raise util.Abort(_('patch is damaged or loses information'))
1538 finally:
1538 finally:
1539 os.unlink(tmpname)
1539 os.unlink(tmpname)
1540
1540
1541 def incoming(ui, repo, source="default", **opts):
1541 def incoming(ui, repo, source="default", **opts):
1542 """show new changesets found in source
1542 """show new changesets found in source
1543
1543
1544 Show new changesets found in the specified path/URL or the default
1544 Show new changesets found in the specified path/URL or the default
1545 pull location. These are the changesets that would be pulled if a pull
1545 pull location. These are the changesets that would be pulled if a pull
1546 was requested.
1546 was requested.
1547
1547
1548 For remote repository, using --bundle avoids downloading the changesets
1548 For remote repository, using --bundle avoids downloading the changesets
1549 twice if the incoming is followed by a pull.
1549 twice if the incoming is followed by a pull.
1550
1550
1551 See pull for valid source format details.
1551 See pull for valid source format details.
1552 """
1552 """
1553 source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
1553 source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
1554 cmdutil.setremoteconfig(ui, opts)
1554 cmdutil.setremoteconfig(ui, opts)
1555
1555
1556 other = hg.repository(ui, source)
1556 other = hg.repository(ui, source)
1557 ui.status(_('comparing with %s\n') % source)
1557 ui.status(_('comparing with %s\n') % source)
1558 if revs:
1558 if revs:
1559 if 'lookup' in other.capabilities:
1559 if 'lookup' in other.capabilities:
1560 revs = [other.lookup(rev) for rev in revs]
1560 revs = [other.lookup(rev) for rev in revs]
1561 else:
1561 else:
1562 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1562 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1563 raise util.Abort(error)
1563 raise util.Abort(error)
1564 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1564 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1565 if not incoming:
1565 if not incoming:
1566 try:
1566 try:
1567 os.unlink(opts["bundle"])
1567 os.unlink(opts["bundle"])
1568 except:
1568 except:
1569 pass
1569 pass
1570 ui.status(_("no changes found\n"))
1570 ui.status(_("no changes found\n"))
1571 return 1
1571 return 1
1572
1572
1573 cleanup = None
1573 cleanup = None
1574 try:
1574 try:
1575 fname = opts["bundle"]
1575 fname = opts["bundle"]
1576 if fname or not other.local():
1576 if fname or not other.local():
1577 # create a bundle (uncompressed if other repo is not local)
1577 # create a bundle (uncompressed if other repo is not local)
1578 if revs is None:
1578 if revs is None:
1579 cg = other.changegroup(incoming, "incoming")
1579 cg = other.changegroup(incoming, "incoming")
1580 else:
1580 else:
1581 if 'changegroupsubset' not in other.capabilities:
1581 if 'changegroupsubset' not in other.capabilities:
1582 raise util.Abort(_("Partial incoming cannot be done because other repository doesn't support changegroupsubset."))
1582 raise util.Abort(_("Partial incoming cannot be done because other repository doesn't support changegroupsubset."))
1583 cg = other.changegroupsubset(incoming, revs, 'incoming')
1583 cg = other.changegroupsubset(incoming, revs, 'incoming')
1584 bundletype = other.local() and "HG10BZ" or "HG10UN"
1584 bundletype = other.local() and "HG10BZ" or "HG10UN"
1585 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1585 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1586 # keep written bundle?
1586 # keep written bundle?
1587 if opts["bundle"]:
1587 if opts["bundle"]:
1588 cleanup = None
1588 cleanup = None
1589 if not other.local():
1589 if not other.local():
1590 # use the created uncompressed bundlerepo
1590 # use the created uncompressed bundlerepo
1591 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1591 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1592
1592
1593 o = other.changelog.nodesbetween(incoming, revs)[0]
1593 o = other.changelog.nodesbetween(incoming, revs)[0]
1594 if opts['newest_first']:
1594 if opts['newest_first']:
1595 o.reverse()
1595 o.reverse()
1596 displayer = cmdutil.show_changeset(ui, other, opts)
1596 displayer = cmdutil.show_changeset(ui, other, opts)
1597 for n in o:
1597 for n in o:
1598 parents = [p for p in other.changelog.parents(n) if p != nullid]
1598 parents = [p for p in other.changelog.parents(n) if p != nullid]
1599 if opts['no_merges'] and len(parents) == 2:
1599 if opts['no_merges'] and len(parents) == 2:
1600 continue
1600 continue
1601 displayer.show(changenode=n)
1601 displayer.show(changenode=n)
1602 finally:
1602 finally:
1603 if hasattr(other, 'close'):
1603 if hasattr(other, 'close'):
1604 other.close()
1604 other.close()
1605 if cleanup:
1605 if cleanup:
1606 os.unlink(cleanup)
1606 os.unlink(cleanup)
1607
1607
1608 def init(ui, dest=".", **opts):
1608 def init(ui, dest=".", **opts):
1609 """create a new repository in the given directory
1609 """create a new repository in the given directory
1610
1610
1611 Initialize a new repository in the given directory. If the given
1611 Initialize a new repository in the given directory. If the given
1612 directory does not exist, it is created.
1612 directory does not exist, it is created.
1613
1613
1614 If no directory is given, the current directory is used.
1614 If no directory is given, the current directory is used.
1615
1615
1616 It is possible to specify an ssh:// URL as the destination.
1616 It is possible to specify an ssh:// URL as the destination.
1617 Look at the help text for the pull command for important details
1617 Look at the help text for the pull command for important details
1618 about ssh:// URLs.
1618 about ssh:// URLs.
1619 """
1619 """
1620 cmdutil.setremoteconfig(ui, opts)
1620 cmdutil.setremoteconfig(ui, opts)
1621 hg.repository(ui, dest, create=1)
1621 hg.repository(ui, dest, create=1)
1622
1622
1623 def locate(ui, repo, *pats, **opts):
1623 def locate(ui, repo, *pats, **opts):
1624 """locate files matching specific patterns
1624 """locate files matching specific patterns
1625
1625
1626 Print all files under Mercurial control whose names match the
1626 Print all files under Mercurial control whose names match the
1627 given patterns.
1627 given patterns.
1628
1628
1629 This command searches the entire repository by default. To search
1629 This command searches the entire repository by default. To search
1630 just the current directory and its subdirectories, use "--include .".
1630 just the current directory and its subdirectories, use "--include .".
1631
1631
1632 If no patterns are given to match, this command prints all file
1632 If no patterns are given to match, this command prints all file
1633 names.
1633 names.
1634
1634
1635 If you want to feed the output of this command into the "xargs"
1635 If you want to feed the output of this command into the "xargs"
1636 command, use the "-0" option to both this command and "xargs".
1636 command, use the "-0" option to both this command and "xargs".
1637 This will avoid the problem of "xargs" treating single filenames
1637 This will avoid the problem of "xargs" treating single filenames
1638 that contain white space as multiple filenames.
1638 that contain white space as multiple filenames.
1639 """
1639 """
1640 end = opts['print0'] and '\0' or '\n'
1640 end = opts['print0'] and '\0' or '\n'
1641 rev = opts['rev']
1641 rev = opts['rev']
1642 if rev:
1642 if rev:
1643 node = repo.lookup(rev)
1643 node = repo.lookup(rev)
1644 else:
1644 else:
1645 node = None
1645 node = None
1646
1646
1647 ret = 1
1647 ret = 1
1648 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1648 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1649 badmatch=util.always,
1649 badmatch=util.always,
1650 default='relglob'):
1650 default='relglob'):
1651 if src == 'b':
1651 if src == 'b':
1652 continue
1652 continue
1653 if not node and repo.dirstate.state(abs) == '?':
1653 if not node and repo.dirstate.state(abs) == '?':
1654 continue
1654 continue
1655 if opts['fullpath']:
1655 if opts['fullpath']:
1656 ui.write(os.path.join(repo.root, abs), end)
1656 ui.write(os.path.join(repo.root, abs), end)
1657 else:
1657 else:
1658 ui.write(((pats and rel) or abs), end)
1658 ui.write(((pats and rel) or abs), end)
1659 ret = 0
1659 ret = 0
1660
1660
1661 return ret
1661 return ret
1662
1662
1663 def log(ui, repo, *pats, **opts):
1663 def log(ui, repo, *pats, **opts):
1664 """show revision history of entire repository or files
1664 """show revision history of entire repository or files
1665
1665
1666 Print the revision history of the specified files or the entire
1666 Print the revision history of the specified files or the entire
1667 project.
1667 project.
1668
1668
1669 File history is shown without following rename or copy history of
1669 File history is shown without following rename or copy history of
1670 files. Use -f/--follow with a file name to follow history across
1670 files. Use -f/--follow with a file name to follow history across
1671 renames and copies. --follow without a file name will only show
1671 renames and copies. --follow without a file name will only show
1672 ancestors or descendants of the starting revision. --follow-first
1672 ancestors or descendants of the starting revision. --follow-first
1673 only follows the first parent of merge revisions.
1673 only follows the first parent of merge revisions.
1674
1674
1675 If no revision range is specified, the default is tip:0 unless
1675 If no revision range is specified, the default is tip:0 unless
1676 --follow is set, in which case the working directory parent is
1676 --follow is set, in which case the working directory parent is
1677 used as the starting revision.
1677 used as the starting revision.
1678
1678
1679 By default this command outputs: changeset id and hash, tags,
1679 By default this command outputs: changeset id and hash, tags,
1680 non-trivial parents, user, date and time, and a summary for each
1680 non-trivial parents, user, date and time, and a summary for each
1681 commit. When the -v/--verbose switch is used, the list of changed
1681 commit. When the -v/--verbose switch is used, the list of changed
1682 files and full commit message is shown.
1682 files and full commit message is shown.
1683
1683
1684 NOTE: log -p may generate unexpected diff output for merge
1684 NOTE: log -p may generate unexpected diff output for merge
1685 changesets, as it will compare the merge changeset against its
1685 changesets, as it will compare the merge changeset against its
1686 first parent only. Also, the files: list will only reflect files
1686 first parent only. Also, the files: list will only reflect files
1687 that are different from BOTH parents.
1687 that are different from BOTH parents.
1688
1688
1689 """
1689 """
1690
1690
1691 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1691 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1692 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1692 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1693
1693
1694 if opts['limit']:
1694 if opts['limit']:
1695 try:
1695 try:
1696 limit = int(opts['limit'])
1696 limit = int(opts['limit'])
1697 except ValueError:
1697 except ValueError:
1698 raise util.Abort(_('limit must be a positive integer'))
1698 raise util.Abort(_('limit must be a positive integer'))
1699 if limit <= 0: raise util.Abort(_('limit must be positive'))
1699 if limit <= 0: raise util.Abort(_('limit must be positive'))
1700 else:
1700 else:
1701 limit = sys.maxint
1701 limit = sys.maxint
1702 count = 0
1702 count = 0
1703
1703
1704 if opts['copies'] and opts['rev']:
1704 if opts['copies'] and opts['rev']:
1705 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1705 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1706 else:
1706 else:
1707 endrev = repo.changelog.count()
1707 endrev = repo.changelog.count()
1708 rcache = {}
1708 rcache = {}
1709 ncache = {}
1709 ncache = {}
1710 dcache = []
1710 dcache = []
1711 def getrenamed(fn, rev, man):
1711 def getrenamed(fn, rev, man):
1712 '''looks up all renames for a file (up to endrev) the first
1712 '''looks up all renames for a file (up to endrev) the first
1713 time the file is given. It indexes on the changerev and only
1713 time the file is given. It indexes on the changerev and only
1714 parses the manifest if linkrev != changerev.
1714 parses the manifest if linkrev != changerev.
1715 Returns rename info for fn at changerev rev.'''
1715 Returns rename info for fn at changerev rev.'''
1716 if fn not in rcache:
1716 if fn not in rcache:
1717 rcache[fn] = {}
1717 rcache[fn] = {}
1718 ncache[fn] = {}
1718 ncache[fn] = {}
1719 fl = repo.file(fn)
1719 fl = repo.file(fn)
1720 for i in xrange(fl.count()):
1720 for i in xrange(fl.count()):
1721 node = fl.node(i)
1721 node = fl.node(i)
1722 lr = fl.linkrev(node)
1722 lr = fl.linkrev(node)
1723 renamed = fl.renamed(node)
1723 renamed = fl.renamed(node)
1724 rcache[fn][lr] = renamed
1724 rcache[fn][lr] = renamed
1725 if renamed:
1725 if renamed:
1726 ncache[fn][node] = renamed
1726 ncache[fn][node] = renamed
1727 if lr >= endrev:
1727 if lr >= endrev:
1728 break
1728 break
1729 if rev in rcache[fn]:
1729 if rev in rcache[fn]:
1730 return rcache[fn][rev]
1730 return rcache[fn][rev]
1731 mr = repo.manifest.rev(man)
1731 mr = repo.manifest.rev(man)
1732 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1732 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1733 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1733 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1734 if not dcache or dcache[0] != man:
1734 if not dcache or dcache[0] != man:
1735 dcache[:] = [man, repo.manifest.readdelta(man)]
1735 dcache[:] = [man, repo.manifest.readdelta(man)]
1736 if fn in dcache[1]:
1736 if fn in dcache[1]:
1737 return ncache[fn].get(dcache[1][fn])
1737 return ncache[fn].get(dcache[1][fn])
1738 return None
1738 return None
1739
1739
1740 df = False
1740 df = False
1741 if opts["date"]:
1741 if opts["date"]:
1742 df = util.matchdate(opts["date"])
1742 df = util.matchdate(opts["date"])
1743
1743
1744 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1744 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1745 for st, rev, fns in changeiter:
1745 for st, rev, fns in changeiter:
1746 if st == 'add':
1746 if st == 'add':
1747 changenode = repo.changelog.node(rev)
1747 changenode = repo.changelog.node(rev)
1748 parents = [p for p in repo.changelog.parentrevs(rev)
1748 parents = [p for p in repo.changelog.parentrevs(rev)
1749 if p != nullrev]
1749 if p != nullrev]
1750 if opts['no_merges'] and len(parents) == 2:
1750 if opts['no_merges'] and len(parents) == 2:
1751 continue
1751 continue
1752 if opts['only_merges'] and len(parents) != 2:
1752 if opts['only_merges'] and len(parents) != 2:
1753 continue
1753 continue
1754
1754
1755 if df:
1755 if df:
1756 changes = get(rev)
1756 changes = get(rev)
1757 if not df(changes[2][0]):
1757 if not df(changes[2][0]):
1758 continue
1758 continue
1759
1759
1760 if opts['keyword']:
1760 if opts['keyword']:
1761 changes = get(rev)
1761 changes = get(rev)
1762 miss = 0
1762 miss = 0
1763 for k in [kw.lower() for kw in opts['keyword']]:
1763 for k in [kw.lower() for kw in opts['keyword']]:
1764 if not (k in changes[1].lower() or
1764 if not (k in changes[1].lower() or
1765 k in changes[4].lower() or
1765 k in changes[4].lower() or
1766 k in " ".join(changes[3]).lower()):
1766 k in " ".join(changes[3]).lower()):
1767 miss = 1
1767 miss = 1
1768 break
1768 break
1769 if miss:
1769 if miss:
1770 continue
1770 continue
1771
1771
1772 copies = []
1772 copies = []
1773 if opts.get('copies') and rev:
1773 if opts.get('copies') and rev:
1774 mf = get(rev)[0]
1774 mf = get(rev)[0]
1775 for fn in get(rev)[3]:
1775 for fn in get(rev)[3]:
1776 rename = getrenamed(fn, rev, mf)
1776 rename = getrenamed(fn, rev, mf)
1777 if rename:
1777 if rename:
1778 copies.append((fn, rename[0]))
1778 copies.append((fn, rename[0]))
1779 displayer.show(rev, changenode, copies=copies)
1779 displayer.show(rev, changenode, copies=copies)
1780 elif st == 'iter':
1780 elif st == 'iter':
1781 if count == limit: break
1781 if count == limit: break
1782 if displayer.flush(rev):
1782 if displayer.flush(rev):
1783 count += 1
1783 count += 1
1784
1784
1785 def manifest(ui, repo, rev=None):
1785 def manifest(ui, repo, rev=None):
1786 """output the current or given revision of the project manifest
1786 """output the current or given revision of the project manifest
1787
1787
1788 Print a list of version controlled files for the given revision.
1788 Print a list of version controlled files for the given revision.
1789 If no revision is given, the parent of the working directory is used,
1789 If no revision is given, the parent of the working directory is used,
1790 or tip if no revision is checked out.
1790 or tip if no revision is checked out.
1791
1791
1792 The manifest is the list of files being version controlled. If no revision
1792 The manifest is the list of files being version controlled. If no revision
1793 is given then the first parent of the working directory is used.
1793 is given then the first parent of the working directory is used.
1794
1794
1795 With -v flag, print file permissions. With --debug flag, print
1795 With -v flag, print file permissions. With --debug flag, print
1796 file revision hashes.
1796 file revision hashes.
1797 """
1797 """
1798
1798
1799 m = repo.changectx(rev).manifest()
1799 m = repo.changectx(rev).manifest()
1800 files = m.keys()
1800 files = m.keys()
1801 files.sort()
1801 files.sort()
1802
1802
1803 for f in files:
1803 for f in files:
1804 if ui.debugflag:
1804 if ui.debugflag:
1805 ui.write("%40s " % hex(m[f]))
1805 ui.write("%40s " % hex(m[f]))
1806 if ui.verbose:
1806 if ui.verbose:
1807 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1807 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1808 ui.write("%s\n" % f)
1808 ui.write("%s\n" % f)
1809
1809
1810 def merge(ui, repo, node=None, force=None, rev=None):
1810 def merge(ui, repo, node=None, force=None, rev=None):
1811 """merge working directory with another revision
1811 """merge working directory with another revision
1812
1812
1813 Merge the contents of the current working directory and the
1813 Merge the contents of the current working directory and the
1814 requested revision. Files that changed between either parent are
1814 requested revision. Files that changed between either parent are
1815 marked as changed for the next commit and a commit must be
1815 marked as changed for the next commit and a commit must be
1816 performed before any further updates are allowed.
1816 performed before any further updates are allowed.
1817
1817
1818 If no revision is specified, the working directory's parent is a
1818 If no revision is specified, the working directory's parent is a
1819 head revision, and the repository contains exactly one other head,
1819 head revision, and the repository contains exactly one other head,
1820 the other head is merged with by default. Otherwise, an explicit
1820 the other head is merged with by default. Otherwise, an explicit
1821 revision to merge with must be provided.
1821 revision to merge with must be provided.
1822 """
1822 """
1823
1823
1824 if rev and node:
1824 if rev and node:
1825 raise util.Abort(_("please specify just one revision"))
1825 raise util.Abort(_("please specify just one revision"))
1826
1826
1827 if not node:
1827 if not node:
1828 node = rev
1828 node = rev
1829
1829
1830 if not node:
1830 if not node:
1831 heads = repo.heads()
1831 heads = repo.heads()
1832 if len(heads) > 2:
1832 if len(heads) > 2:
1833 raise util.Abort(_('repo has %d heads - '
1833 raise util.Abort(_('repo has %d heads - '
1834 'please merge with an explicit rev') %
1834 'please merge with an explicit rev') %
1835 len(heads))
1835 len(heads))
1836 if len(heads) == 1:
1836 if len(heads) == 1:
1837 raise util.Abort(_('there is nothing to merge - '
1837 raise util.Abort(_('there is nothing to merge - '
1838 'use "hg update" instead'))
1838 'use "hg update" instead'))
1839 parent = repo.dirstate.parents()[0]
1839 parent = repo.dirstate.parents()[0]
1840 if parent not in heads:
1840 if parent not in heads:
1841 raise util.Abort(_('working dir not at a head rev - '
1841 raise util.Abort(_('working dir not at a head rev - '
1842 'use "hg update" or merge with an explicit rev'))
1842 'use "hg update" or merge with an explicit rev'))
1843 node = parent == heads[0] and heads[-1] or heads[0]
1843 node = parent == heads[0] and heads[-1] or heads[0]
1844 return hg.merge(repo, node, force=force)
1844 return hg.merge(repo, node, force=force)
1845
1845
1846 def outgoing(ui, repo, dest=None, **opts):
1846 def outgoing(ui, repo, dest=None, **opts):
1847 """show changesets not found in destination
1847 """show changesets not found in destination
1848
1848
1849 Show changesets not found in the specified destination repository or
1849 Show changesets not found in the specified destination repository or
1850 the default push location. These are the changesets that would be pushed
1850 the default push location. These are the changesets that would be pushed
1851 if a push was requested.
1851 if a push was requested.
1852
1852
1853 See pull for valid destination format details.
1853 See pull for valid destination format details.
1854 """
1854 """
1855 dest, revs = cmdutil.parseurl(
1855 dest, revs = cmdutil.parseurl(
1856 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1856 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1857 cmdutil.setremoteconfig(ui, opts)
1857 cmdutil.setremoteconfig(ui, opts)
1858 if revs:
1858 if revs:
1859 revs = [repo.lookup(rev) for rev in revs]
1859 revs = [repo.lookup(rev) for rev in revs]
1860
1860
1861 other = hg.repository(ui, dest)
1861 other = hg.repository(ui, dest)
1862 ui.status(_('comparing with %s\n') % dest)
1862 ui.status(_('comparing with %s\n') % dest)
1863 o = repo.findoutgoing(other, force=opts['force'])
1863 o = repo.findoutgoing(other, force=opts['force'])
1864 if not o:
1864 if not o:
1865 ui.status(_("no changes found\n"))
1865 ui.status(_("no changes found\n"))
1866 return 1
1866 return 1
1867 o = repo.changelog.nodesbetween(o, revs)[0]
1867 o = repo.changelog.nodesbetween(o, revs)[0]
1868 if opts['newest_first']:
1868 if opts['newest_first']:
1869 o.reverse()
1869 o.reverse()
1870 displayer = cmdutil.show_changeset(ui, repo, opts)
1870 displayer = cmdutil.show_changeset(ui, repo, opts)
1871 for n in o:
1871 for n in o:
1872 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1872 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1873 if opts['no_merges'] and len(parents) == 2:
1873 if opts['no_merges'] and len(parents) == 2:
1874 continue
1874 continue
1875 displayer.show(changenode=n)
1875 displayer.show(changenode=n)
1876
1876
1877 def parents(ui, repo, file_=None, **opts):
1877 def parents(ui, repo, file_=None, **opts):
1878 """show the parents of the working dir or revision
1878 """show the parents of the working dir or revision
1879
1879
1880 Print the working directory's parent revisions.
1880 Print the working directory's parent revisions.
1881 """
1881 """
1882 rev = opts.get('rev')
1882 rev = opts.get('rev')
1883 if rev:
1883 if rev:
1884 if file_:
1884 if file_:
1885 ctx = repo.filectx(file_, changeid=rev)
1885 ctx = repo.filectx(file_, changeid=rev)
1886 else:
1886 else:
1887 ctx = repo.changectx(rev)
1887 ctx = repo.changectx(rev)
1888 p = [cp.node() for cp in ctx.parents()]
1888 p = [cp.node() for cp in ctx.parents()]
1889 else:
1889 else:
1890 p = repo.dirstate.parents()
1890 p = repo.dirstate.parents()
1891
1891
1892 displayer = cmdutil.show_changeset(ui, repo, opts)
1892 displayer = cmdutil.show_changeset(ui, repo, opts)
1893 for n in p:
1893 for n in p:
1894 if n != nullid:
1894 if n != nullid:
1895 displayer.show(changenode=n)
1895 displayer.show(changenode=n)
1896
1896
1897 def paths(ui, repo, search=None):
1897 def paths(ui, repo, search=None):
1898 """show definition of symbolic path names
1898 """show definition of symbolic path names
1899
1899
1900 Show definition of symbolic path name NAME. If no name is given, show
1900 Show definition of symbolic path name NAME. If no name is given, show
1901 definition of available names.
1901 definition of available names.
1902
1902
1903 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1903 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1904 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1904 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1905 """
1905 """
1906 if search:
1906 if search:
1907 for name, path in ui.configitems("paths"):
1907 for name, path in ui.configitems("paths"):
1908 if name == search:
1908 if name == search:
1909 ui.write("%s\n" % path)
1909 ui.write("%s\n" % path)
1910 return
1910 return
1911 ui.warn(_("not found!\n"))
1911 ui.warn(_("not found!\n"))
1912 return 1
1912 return 1
1913 else:
1913 else:
1914 for name, path in ui.configitems("paths"):
1914 for name, path in ui.configitems("paths"):
1915 ui.write("%s = %s\n" % (name, path))
1915 ui.write("%s = %s\n" % (name, path))
1916
1916
1917 def postincoming(ui, repo, modheads, optupdate):
1917 def postincoming(ui, repo, modheads, optupdate):
1918 if modheads == 0:
1918 if modheads == 0:
1919 return
1919 return
1920 if optupdate:
1920 if optupdate:
1921 if modheads == 1:
1921 if modheads == 1:
1922 return hg.update(repo, repo.changelog.tip()) # update
1922 return hg.update(repo, repo.changelog.tip()) # update
1923 else:
1923 else:
1924 ui.status(_("not updating, since new heads added\n"))
1924 ui.status(_("not updating, since new heads added\n"))
1925 if modheads > 1:
1925 if modheads > 1:
1926 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1926 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1927 else:
1927 else:
1928 ui.status(_("(run 'hg update' to get a working copy)\n"))
1928 ui.status(_("(run 'hg update' to get a working copy)\n"))
1929
1929
1930 def pull(ui, repo, source="default", **opts):
1930 def pull(ui, repo, source="default", **opts):
1931 """pull changes from the specified source
1931 """pull changes from the specified source
1932
1932
1933 Pull changes from a remote repository to a local one.
1933 Pull changes from a remote repository to a local one.
1934
1934
1935 This finds all changes from the repository at the specified path
1935 This finds all changes from the repository at the specified path
1936 or URL and adds them to the local repository. By default, this
1936 or URL and adds them to the local repository. By default, this
1937 does not update the copy of the project in the working directory.
1937 does not update the copy of the project in the working directory.
1938
1938
1939 Valid URLs are of the form:
1939 Valid URLs are of the form:
1940
1940
1941 local/filesystem/path (or file://local/filesystem/path)
1941 local/filesystem/path (or file://local/filesystem/path)
1942 http://[user@]host[:port]/[path]
1942 http://[user@]host[:port]/[path]
1943 https://[user@]host[:port]/[path]
1943 https://[user@]host[:port]/[path]
1944 ssh://[user@]host[:port]/[path]
1944 ssh://[user@]host[:port]/[path]
1945 static-http://host[:port]/[path]
1945 static-http://host[:port]/[path]
1946
1946
1947 Paths in the local filesystem can either point to Mercurial
1947 Paths in the local filesystem can either point to Mercurial
1948 repositories or to bundle files (as created by 'hg bundle' or
1948 repositories or to bundle files (as created by 'hg bundle' or
1949 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1949 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1950 allows access to a Mercurial repository where you simply use a web
1950 allows access to a Mercurial repository where you simply use a web
1951 server to publish the .hg directory as static content.
1951 server to publish the .hg directory as static content.
1952
1952
1953 An optional identifier after # indicates a particular branch, tag,
1953 An optional identifier after # indicates a particular branch, tag,
1954 or changeset to pull.
1954 or changeset to pull.
1955
1955
1956 Some notes about using SSH with Mercurial:
1956 Some notes about using SSH with Mercurial:
1957 - SSH requires an accessible shell account on the destination machine
1957 - SSH requires an accessible shell account on the destination machine
1958 and a copy of hg in the remote path or specified with as remotecmd.
1958 and a copy of hg in the remote path or specified with as remotecmd.
1959 - path is relative to the remote user's home directory by default.
1959 - path is relative to the remote user's home directory by default.
1960 Use an extra slash at the start of a path to specify an absolute path:
1960 Use an extra slash at the start of a path to specify an absolute path:
1961 ssh://example.com//tmp/repository
1961 ssh://example.com//tmp/repository
1962 - Mercurial doesn't use its own compression via SSH; the right thing
1962 - Mercurial doesn't use its own compression via SSH; the right thing
1963 to do is to configure it in your ~/.ssh/config, e.g.:
1963 to do is to configure it in your ~/.ssh/config, e.g.:
1964 Host *.mylocalnetwork.example.com
1964 Host *.mylocalnetwork.example.com
1965 Compression no
1965 Compression no
1966 Host *
1966 Host *
1967 Compression yes
1967 Compression yes
1968 Alternatively specify "ssh -C" as your ssh command in your hgrc or
1968 Alternatively specify "ssh -C" as your ssh command in your hgrc or
1969 with the --ssh command line option.
1969 with the --ssh command line option.
1970 """
1970 """
1971 source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
1971 source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
1972 cmdutil.setremoteconfig(ui, opts)
1972 cmdutil.setremoteconfig(ui, opts)
1973
1973
1974 other = hg.repository(ui, source)
1974 other = hg.repository(ui, source)
1975 ui.status(_('pulling from %s\n') % (source))
1975 ui.status(_('pulling from %s\n') % (source))
1976 if revs:
1976 if revs:
1977 if 'lookup' in other.capabilities:
1977 if 'lookup' in other.capabilities:
1978 revs = [other.lookup(rev) for rev in revs]
1978 revs = [other.lookup(rev) for rev in revs]
1979 else:
1979 else:
1980 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1980 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1981 raise util.Abort(error)
1981 raise util.Abort(error)
1982
1982
1983 modheads = repo.pull(other, heads=revs, force=opts['force'])
1983 modheads = repo.pull(other, heads=revs, force=opts['force'])
1984 return postincoming(ui, repo, modheads, opts['update'])
1984 return postincoming(ui, repo, modheads, opts['update'])
1985
1985
1986 def push(ui, repo, dest=None, **opts):
1986 def push(ui, repo, dest=None, **opts):
1987 """push changes to the specified destination
1987 """push changes to the specified destination
1988
1988
1989 Push changes from the local repository to the given destination.
1989 Push changes from the local repository to the given destination.
1990
1990
1991 This is the symmetrical operation for pull. It helps to move
1991 This is the symmetrical operation for pull. It helps to move
1992 changes from the current repository to a different one. If the
1992 changes from the current repository to a different one. If the
1993 destination is local this is identical to a pull in that directory
1993 destination is local this is identical to a pull in that directory
1994 from the current one.
1994 from the current one.
1995
1995
1996 By default, push will refuse to run if it detects the result would
1996 By default, push will refuse to run if it detects the result would
1997 increase the number of remote heads. This generally indicates the
1997 increase the number of remote heads. This generally indicates the
1998 the client has forgotten to sync and merge before pushing.
1998 the client has forgotten to sync and merge before pushing.
1999
1999
2000 Valid URLs are of the form:
2000 Valid URLs are of the form:
2001
2001
2002 local/filesystem/path (or file://local/filesystem/path)
2002 local/filesystem/path (or file://local/filesystem/path)
2003 ssh://[user@]host[:port]/[path]
2003 ssh://[user@]host[:port]/[path]
2004 http://[user@]host[:port]/[path]
2004 http://[user@]host[:port]/[path]
2005 https://[user@]host[:port]/[path]
2005 https://[user@]host[:port]/[path]
2006
2006
2007 An optional identifier after # indicates a particular branch, tag,
2007 An optional identifier after # indicates a particular branch, tag,
2008 or changeset to push.
2008 or changeset to push.
2009
2009
2010 Look at the help text for the pull command for important details
2010 Look at the help text for the pull command for important details
2011 about ssh:// URLs.
2011 about ssh:// URLs.
2012
2012
2013 Pushing to http:// and https:// URLs is only possible, if this
2013 Pushing to http:// and https:// URLs is only possible, if this
2014 feature is explicitly enabled on the remote Mercurial server.
2014 feature is explicitly enabled on the remote Mercurial server.
2015 """
2015 """
2016 dest, revs = cmdutil.parseurl(
2016 dest, revs = cmdutil.parseurl(
2017 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2017 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2018 cmdutil.setremoteconfig(ui, opts)
2018 cmdutil.setremoteconfig(ui, opts)
2019
2019
2020 other = hg.repository(ui, dest)
2020 other = hg.repository(ui, dest)
2021 ui.status('pushing to %s\n' % (dest))
2021 ui.status('pushing to %s\n' % (dest))
2022 if revs:
2022 if revs:
2023 revs = [repo.lookup(rev) for rev in revs]
2023 revs = [repo.lookup(rev) for rev in revs]
2024 r = repo.push(other, opts['force'], revs=revs)
2024 r = repo.push(other, opts['force'], revs=revs)
2025 return r == 0
2025 return r == 0
2026
2026
2027 def rawcommit(ui, repo, *pats, **opts):
2027 def rawcommit(ui, repo, *pats, **opts):
2028 """raw commit interface (DEPRECATED)
2028 """raw commit interface (DEPRECATED)
2029
2029
2030 (DEPRECATED)
2030 (DEPRECATED)
2031 Lowlevel commit, for use in helper scripts.
2031 Lowlevel commit, for use in helper scripts.
2032
2032
2033 This command is not intended to be used by normal users, as it is
2033 This command is not intended to be used by normal users, as it is
2034 primarily useful for importing from other SCMs.
2034 primarily useful for importing from other SCMs.
2035
2035
2036 This command is now deprecated and will be removed in a future
2036 This command is now deprecated and will be removed in a future
2037 release, please use debugsetparents and commit instead.
2037 release, please use debugsetparents and commit instead.
2038 """
2038 """
2039
2039
2040 ui.warn(_("(the rawcommit command is deprecated)\n"))
2040 ui.warn(_("(the rawcommit command is deprecated)\n"))
2041
2041
2042 message = cmdutil.logmessage(opts)
2042 message = cmdutil.logmessage(opts)
2043
2043
2044 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2044 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2045 if opts['files']:
2045 if opts['files']:
2046 files += open(opts['files']).read().splitlines()
2046 files += open(opts['files']).read().splitlines()
2047
2047
2048 parents = [repo.lookup(p) for p in opts['parent']]
2048 parents = [repo.lookup(p) for p in opts['parent']]
2049
2049
2050 try:
2050 try:
2051 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2051 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2052 except ValueError, inst:
2052 except ValueError, inst:
2053 raise util.Abort(str(inst))
2053 raise util.Abort(str(inst))
2054
2054
2055 def recover(ui, repo):
2055 def recover(ui, repo):
2056 """roll back an interrupted transaction
2056 """roll back an interrupted transaction
2057
2057
2058 Recover from an interrupted commit or pull.
2058 Recover from an interrupted commit or pull.
2059
2059
2060 This command tries to fix the repository status after an interrupted
2060 This command tries to fix the repository status after an interrupted
2061 operation. It should only be necessary when Mercurial suggests it.
2061 operation. It should only be necessary when Mercurial suggests it.
2062 """
2062 """
2063 if repo.recover():
2063 if repo.recover():
2064 return hg.verify(repo)
2064 return hg.verify(repo)
2065 return 1
2065 return 1
2066
2066
2067 def remove(ui, repo, *pats, **opts):
2067 def remove(ui, repo, *pats, **opts):
2068 """remove the specified files on the next commit
2068 """remove the specified files on the next commit
2069
2069
2070 Schedule the indicated files for removal from the repository.
2070 Schedule the indicated files for removal from the repository.
2071
2071
2072 This only removes files from the current branch, not from the
2072 This only removes files from the current branch, not from the
2073 entire project history. If the files still exist in the working
2073 entire project history. If the files still exist in the working
2074 directory, they will be deleted from it. If invoked with --after,
2074 directory, they will be deleted from it. If invoked with --after,
2075 files are marked as removed, but not actually unlinked unless --force
2075 files are marked as removed, but not actually unlinked unless --force
2076 is also given. Without exact file names, --after will only mark
2076 is also given. Without exact file names, --after will only mark
2077 files as removed if they are no longer in the working directory.
2077 files as removed if they are no longer in the working directory.
2078
2078
2079 This command schedules the files to be removed at the next commit.
2079 This command schedules the files to be removed at the next commit.
2080 To undo a remove before that, see hg revert.
2080 To undo a remove before that, see hg revert.
2081
2081
2082 Modified files and added files are not removed by default. To
2082 Modified files and added files are not removed by default. To
2083 remove them, use the -f/--force option.
2083 remove them, use the -f/--force option.
2084 """
2084 """
2085 names = []
2085 names = []
2086 if not opts['after'] and not pats:
2086 if not opts['after'] and not pats:
2087 raise util.Abort(_('no files specified'))
2087 raise util.Abort(_('no files specified'))
2088 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2088 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2089 exact = dict.fromkeys(files)
2089 exact = dict.fromkeys(files)
2090 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2090 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2091 modified, added, removed, deleted, unknown = mardu
2091 modified, added, removed, deleted, unknown = mardu
2092 remove, forget = [], []
2092 remove, forget = [], []
2093 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2093 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2094 reason = None
2094 reason = None
2095 if abs in modified and not opts['force']:
2095 if abs in modified and not opts['force']:
2096 reason = _('is modified (use -f to force removal)')
2096 reason = _('is modified (use -f to force removal)')
2097 elif abs in added:
2097 elif abs in added:
2098 if opts['force']:
2098 if opts['force']:
2099 forget.append(abs)
2099 forget.append(abs)
2100 continue
2100 continue
2101 reason = _('has been marked for add (use -f to force removal)')
2101 reason = _('has been marked for add (use -f to force removal)')
2102 elif abs in unknown:
2102 elif repo.dirstate.state(abs) == '?':
2103 reason = _('is not managed')
2103 reason = _('is not managed')
2104 elif opts['after'] and not exact and abs not in deleted:
2104 elif opts['after'] and not exact and abs not in deleted:
2105 continue
2105 continue
2106 elif abs in removed:
2106 elif abs in removed:
2107 continue
2107 continue
2108 if reason:
2108 if reason:
2109 if exact:
2109 if exact:
2110 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2110 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2111 else:
2111 else:
2112 if ui.verbose or not exact:
2112 if ui.verbose or not exact:
2113 ui.status(_('removing %s\n') % rel)
2113 ui.status(_('removing %s\n') % rel)
2114 remove.append(abs)
2114 remove.append(abs)
2115 repo.forget(forget)
2115 repo.forget(forget)
2116 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2116 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2117
2117
2118 def rename(ui, repo, *pats, **opts):
2118 def rename(ui, repo, *pats, **opts):
2119 """rename files; equivalent of copy + remove
2119 """rename files; equivalent of copy + remove
2120
2120
2121 Mark dest as copies of sources; mark sources for deletion. If
2121 Mark dest as copies of sources; mark sources for deletion. If
2122 dest is a directory, copies are put in that directory. If dest is
2122 dest is a directory, copies are put in that directory. If dest is
2123 a file, there can only be one source.
2123 a file, there can only be one source.
2124
2124
2125 By default, this command copies the contents of files as they
2125 By default, this command copies the contents of files as they
2126 stand in the working directory. If invoked with --after, the
2126 stand in the working directory. If invoked with --after, the
2127 operation is recorded, but no copying is performed.
2127 operation is recorded, but no copying is performed.
2128
2128
2129 This command takes effect in the next commit. To undo a rename
2129 This command takes effect in the next commit. To undo a rename
2130 before that, see hg revert.
2130 before that, see hg revert.
2131 """
2131 """
2132 wlock = repo.wlock(0)
2132 wlock = repo.wlock(0)
2133 errs, copied = docopy(ui, repo, pats, opts, wlock)
2133 errs, copied = docopy(ui, repo, pats, opts, wlock)
2134 names = []
2134 names = []
2135 for abs, rel, exact in copied:
2135 for abs, rel, exact in copied:
2136 if ui.verbose or not exact:
2136 if ui.verbose or not exact:
2137 ui.status(_('removing %s\n') % rel)
2137 ui.status(_('removing %s\n') % rel)
2138 names.append(abs)
2138 names.append(abs)
2139 if not opts.get('dry_run'):
2139 if not opts.get('dry_run'):
2140 repo.remove(names, True, wlock=wlock)
2140 repo.remove(names, True, wlock=wlock)
2141 return errs
2141 return errs
2142
2142
2143 def revert(ui, repo, *pats, **opts):
2143 def revert(ui, repo, *pats, **opts):
2144 """revert files or dirs to their states as of some revision
2144 """revert files or dirs to their states as of some revision
2145
2145
2146 With no revision specified, revert the named files or directories
2146 With no revision specified, revert the named files or directories
2147 to the contents they had in the parent of the working directory.
2147 to the contents they had in the parent of the working directory.
2148 This restores the contents of the affected files to an unmodified
2148 This restores the contents of the affected files to an unmodified
2149 state and unschedules adds, removes, copies, and renames. If the
2149 state and unschedules adds, removes, copies, and renames. If the
2150 working directory has two parents, you must explicitly specify the
2150 working directory has two parents, you must explicitly specify the
2151 revision to revert to.
2151 revision to revert to.
2152
2152
2153 Modified files are saved with a .orig suffix before reverting.
2153 Modified files are saved with a .orig suffix before reverting.
2154 To disable these backups, use --no-backup.
2154 To disable these backups, use --no-backup.
2155
2155
2156 Using the -r option, revert the given files or directories to their
2156 Using the -r option, revert the given files or directories to their
2157 contents as of a specific revision. This can be helpful to "roll
2157 contents as of a specific revision. This can be helpful to "roll
2158 back" some or all of a change that should not have been committed.
2158 back" some or all of a change that should not have been committed.
2159
2159
2160 Revert modifies the working directory. It does not commit any
2160 Revert modifies the working directory. It does not commit any
2161 changes, or change the parent of the working directory. If you
2161 changes, or change the parent of the working directory. If you
2162 revert to a revision other than the parent of the working
2162 revert to a revision other than the parent of the working
2163 directory, the reverted files will thus appear modified
2163 directory, the reverted files will thus appear modified
2164 afterwards.
2164 afterwards.
2165
2165
2166 If a file has been deleted, it is recreated. If the executable
2166 If a file has been deleted, it is recreated. If the executable
2167 mode of a file was changed, it is reset.
2167 mode of a file was changed, it is reset.
2168
2168
2169 If names are given, all files matching the names are reverted.
2169 If names are given, all files matching the names are reverted.
2170
2170
2171 If no arguments are given, no files are reverted.
2171 If no arguments are given, no files are reverted.
2172 """
2172 """
2173
2173
2174 if opts["date"]:
2174 if opts["date"]:
2175 if opts["rev"]:
2175 if opts["rev"]:
2176 raise util.Abort(_("you can't specify a revision and a date"))
2176 raise util.Abort(_("you can't specify a revision and a date"))
2177 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2177 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2178
2178
2179 if not pats and not opts['all']:
2179 if not pats and not opts['all']:
2180 raise util.Abort(_('no files or directories specified; '
2180 raise util.Abort(_('no files or directories specified; '
2181 'use --all to revert the whole repo'))
2181 'use --all to revert the whole repo'))
2182
2182
2183 parent, p2 = repo.dirstate.parents()
2183 parent, p2 = repo.dirstate.parents()
2184 if not opts['rev'] and p2 != nullid:
2184 if not opts['rev'] and p2 != nullid:
2185 raise util.Abort(_('uncommitted merge - please provide a '
2185 raise util.Abort(_('uncommitted merge - please provide a '
2186 'specific revision'))
2186 'specific revision'))
2187 ctx = repo.changectx(opts['rev'])
2187 ctx = repo.changectx(opts['rev'])
2188 node = ctx.node()
2188 node = ctx.node()
2189 mf = ctx.manifest()
2189 mf = ctx.manifest()
2190 if node == parent:
2190 if node == parent:
2191 pmf = mf
2191 pmf = mf
2192 else:
2192 else:
2193 pmf = None
2193 pmf = None
2194
2194
2195 wlock = repo.wlock()
2195 wlock = repo.wlock()
2196
2196
2197 # need all matching names in dirstate and manifest of target rev,
2197 # need all matching names in dirstate and manifest of target rev,
2198 # so have to walk both. do not print errors if files exist in one
2198 # so have to walk both. do not print errors if files exist in one
2199 # but not other.
2199 # but not other.
2200
2200
2201 names = {}
2201 names = {}
2202 target_only = {}
2202 target_only = {}
2203
2203
2204 # walk dirstate.
2204 # walk dirstate.
2205
2205
2206 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2206 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2207 badmatch=mf.has_key):
2207 badmatch=mf.has_key):
2208 names[abs] = (rel, exact)
2208 names[abs] = (rel, exact)
2209 if src == 'b':
2209 if src == 'b':
2210 target_only[abs] = True
2210 target_only[abs] = True
2211
2211
2212 # walk target manifest.
2212 # walk target manifest.
2213
2213
2214 def badmatch(path):
2214 def badmatch(path):
2215 if path in names:
2215 if path in names:
2216 return True
2216 return True
2217 path_ = path + '/'
2217 path_ = path + '/'
2218 for f in names:
2218 for f in names:
2219 if f.startswith(path_):
2219 if f.startswith(path_):
2220 return True
2220 return True
2221 return False
2221 return False
2222
2222
2223 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2223 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2224 badmatch=badmatch):
2224 badmatch=badmatch):
2225 if abs in names or src == 'b':
2225 if abs in names or src == 'b':
2226 continue
2226 continue
2227 names[abs] = (rel, exact)
2227 names[abs] = (rel, exact)
2228 target_only[abs] = True
2228 target_only[abs] = True
2229
2229
2230 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2230 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2231 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2231 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2232
2232
2233 revert = ([], _('reverting %s\n'))
2233 revert = ([], _('reverting %s\n'))
2234 add = ([], _('adding %s\n'))
2234 add = ([], _('adding %s\n'))
2235 remove = ([], _('removing %s\n'))
2235 remove = ([], _('removing %s\n'))
2236 forget = ([], _('forgetting %s\n'))
2236 forget = ([], _('forgetting %s\n'))
2237 undelete = ([], _('undeleting %s\n'))
2237 undelete = ([], _('undeleting %s\n'))
2238 update = {}
2238 update = {}
2239
2239
2240 disptable = (
2240 disptable = (
2241 # dispatch table:
2241 # dispatch table:
2242 # file state
2242 # file state
2243 # action if in target manifest
2243 # action if in target manifest
2244 # action if not in target manifest
2244 # action if not in target manifest
2245 # make backup if in target manifest
2245 # make backup if in target manifest
2246 # make backup if not in target manifest
2246 # make backup if not in target manifest
2247 (modified, revert, remove, True, True),
2247 (modified, revert, remove, True, True),
2248 (added, revert, forget, True, False),
2248 (added, revert, forget, True, False),
2249 (removed, undelete, None, False, False),
2249 (removed, undelete, None, False, False),
2250 (deleted, revert, remove, False, False),
2250 (deleted, revert, remove, False, False),
2251 (unknown, add, None, True, False),
2251 (unknown, add, None, True, False),
2252 (target_only, add, None, False, False),
2252 (target_only, add, None, False, False),
2253 )
2253 )
2254
2254
2255 entries = names.items()
2255 entries = names.items()
2256 entries.sort()
2256 entries.sort()
2257
2257
2258 for abs, (rel, exact) in entries:
2258 for abs, (rel, exact) in entries:
2259 mfentry = mf.get(abs)
2259 mfentry = mf.get(abs)
2260 target = repo.wjoin(abs)
2260 target = repo.wjoin(abs)
2261 def handle(xlist, dobackup):
2261 def handle(xlist, dobackup):
2262 xlist[0].append(abs)
2262 xlist[0].append(abs)
2263 update[abs] = 1
2263 update[abs] = 1
2264 if (dobackup and not opts['no_backup'] and
2264 if dobackup and not opts['no_backup'] and util.lexists(target):
2265 (os.path.islink(target) or os.path.exists(target))):
2266 bakname = "%s.orig" % rel
2265 bakname = "%s.orig" % rel
2267 ui.note(_('saving current version of %s as %s\n') %
2266 ui.note(_('saving current version of %s as %s\n') %
2268 (rel, bakname))
2267 (rel, bakname))
2269 if not opts.get('dry_run'):
2268 if not opts.get('dry_run'):
2270 util.copyfile(target, bakname)
2269 util.copyfile(target, bakname)
2271 if ui.verbose or not exact:
2270 if ui.verbose or not exact:
2272 ui.status(xlist[1] % rel)
2271 ui.status(xlist[1] % rel)
2273 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2272 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2274 if abs not in table: continue
2273 if abs not in table: continue
2275 # file has changed in dirstate
2274 # file has changed in dirstate
2276 if mfentry:
2275 if mfentry:
2277 handle(hitlist, backuphit)
2276 handle(hitlist, backuphit)
2278 elif misslist is not None:
2277 elif misslist is not None:
2279 handle(misslist, backupmiss)
2278 handle(misslist, backupmiss)
2280 else:
2279 else:
2281 if exact: ui.warn(_('file not managed: %s\n') % rel)
2280 if exact: ui.warn(_('file not managed: %s\n') % rel)
2282 break
2281 break
2283 else:
2282 else:
2284 # file has not changed in dirstate
2283 # file has not changed in dirstate
2285 if node == parent:
2284 if node == parent:
2286 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2285 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2287 continue
2286 continue
2288 if pmf is None:
2287 if pmf is None:
2289 # only need parent manifest in this unlikely case,
2288 # only need parent manifest in this unlikely case,
2290 # so do not read by default
2289 # so do not read by default
2291 pmf = repo.changectx(parent).manifest()
2290 pmf = repo.changectx(parent).manifest()
2292 if abs in pmf:
2291 if abs in pmf:
2293 if mfentry:
2292 if mfentry:
2294 # if version of file is same in parent and target
2293 # if version of file is same in parent and target
2295 # manifests, do nothing
2294 # manifests, do nothing
2296 if pmf[abs] != mfentry:
2295 if pmf[abs] != mfentry:
2297 handle(revert, False)
2296 handle(revert, False)
2298 else:
2297 else:
2299 handle(remove, False)
2298 handle(remove, False)
2300
2299
2301 if not opts.get('dry_run'):
2300 if not opts.get('dry_run'):
2302 repo.dirstate.forget(forget[0])
2301 repo.dirstate.forget(forget[0])
2303 r = hg.revert(repo, node, update.has_key, wlock)
2302 r = hg.revert(repo, node, update.has_key, wlock)
2304 repo.dirstate.update(add[0], 'a')
2303 repo.dirstate.update(add[0], 'a')
2305 repo.dirstate.update(undelete[0], 'n')
2304 repo.dirstate.update(undelete[0], 'n')
2306 repo.dirstate.update(remove[0], 'r')
2305 repo.dirstate.update(remove[0], 'r')
2307 return r
2306 return r
2308
2307
2309 def rollback(ui, repo):
2308 def rollback(ui, repo):
2310 """roll back the last transaction in this repository
2309 """roll back the last transaction in this repository
2311
2310
2312 Roll back the last transaction in this repository, restoring the
2311 Roll back the last transaction in this repository, restoring the
2313 project to its state prior to the transaction.
2312 project to its state prior to the transaction.
2314
2313
2315 Transactions are used to encapsulate the effects of all commands
2314 Transactions are used to encapsulate the effects of all commands
2316 that create new changesets or propagate existing changesets into a
2315 that create new changesets or propagate existing changesets into a
2317 repository. For example, the following commands are transactional,
2316 repository. For example, the following commands are transactional,
2318 and their effects can be rolled back:
2317 and their effects can be rolled back:
2319
2318
2320 commit
2319 commit
2321 import
2320 import
2322 pull
2321 pull
2323 push (with this repository as destination)
2322 push (with this repository as destination)
2324 unbundle
2323 unbundle
2325
2324
2326 This command should be used with care. There is only one level of
2325 This command should be used with care. There is only one level of
2327 rollback, and there is no way to undo a rollback.
2326 rollback, and there is no way to undo a rollback.
2328
2327
2329 This command is not intended for use on public repositories. Once
2328 This command is not intended for use on public repositories. Once
2330 changes are visible for pull by other users, rolling a transaction
2329 changes are visible for pull by other users, rolling a transaction
2331 back locally is ineffective (someone else may already have pulled
2330 back locally is ineffective (someone else may already have pulled
2332 the changes). Furthermore, a race is possible with readers of the
2331 the changes). Furthermore, a race is possible with readers of the
2333 repository; for example an in-progress pull from the repository
2332 repository; for example an in-progress pull from the repository
2334 may fail if a rollback is performed.
2333 may fail if a rollback is performed.
2335 """
2334 """
2336 repo.rollback()
2335 repo.rollback()
2337
2336
2338 def root(ui, repo):
2337 def root(ui, repo):
2339 """print the root (top) of the current working dir
2338 """print the root (top) of the current working dir
2340
2339
2341 Print the root directory of the current repository.
2340 Print the root directory of the current repository.
2342 """
2341 """
2343 ui.write(repo.root + "\n")
2342 ui.write(repo.root + "\n")
2344
2343
2345 def serve(ui, repo, **opts):
2344 def serve(ui, repo, **opts):
2346 """export the repository via HTTP
2345 """export the repository via HTTP
2347
2346
2348 Start a local HTTP repository browser and pull server.
2347 Start a local HTTP repository browser and pull server.
2349
2348
2350 By default, the server logs accesses to stdout and errors to
2349 By default, the server logs accesses to stdout and errors to
2351 stderr. Use the "-A" and "-E" options to log to files.
2350 stderr. Use the "-A" and "-E" options to log to files.
2352 """
2351 """
2353
2352
2354 if opts["stdio"]:
2353 if opts["stdio"]:
2355 if repo is None:
2354 if repo is None:
2356 raise hg.RepoError(_("There is no Mercurial repository here"
2355 raise hg.RepoError(_("There is no Mercurial repository here"
2357 " (.hg not found)"))
2356 " (.hg not found)"))
2358 s = sshserver.sshserver(ui, repo)
2357 s = sshserver.sshserver(ui, repo)
2359 s.serve_forever()
2358 s.serve_forever()
2360
2359
2361 parentui = ui.parentui or ui
2360 parentui = ui.parentui or ui
2362 optlist = ("name templates style address port ipv6"
2361 optlist = ("name templates style address port ipv6"
2363 " accesslog errorlog webdir_conf")
2362 " accesslog errorlog webdir_conf")
2364 for o in optlist.split():
2363 for o in optlist.split():
2365 if opts[o]:
2364 if opts[o]:
2366 parentui.setconfig("web", o, str(opts[o]))
2365 parentui.setconfig("web", o, str(opts[o]))
2367
2366
2368 if repo is None and not ui.config("web", "webdir_conf"):
2367 if repo is None and not ui.config("web", "webdir_conf"):
2369 raise hg.RepoError(_("There is no Mercurial repository here"
2368 raise hg.RepoError(_("There is no Mercurial repository here"
2370 " (.hg not found)"))
2369 " (.hg not found)"))
2371
2370
2372 class service:
2371 class service:
2373 def init(self):
2372 def init(self):
2374 try:
2373 try:
2375 self.httpd = hgweb.server.create_server(parentui, repo)
2374 self.httpd = hgweb.server.create_server(parentui, repo)
2376 except socket.error, inst:
2375 except socket.error, inst:
2377 raise util.Abort(_('cannot start server: ') + inst.args[1])
2376 raise util.Abort(_('cannot start server: ') + inst.args[1])
2378
2377
2379 if not ui.verbose: return
2378 if not ui.verbose: return
2380
2379
2381 if self.httpd.port != 80:
2380 if self.httpd.port != 80:
2382 ui.status(_('listening at http://%s:%d/\n') %
2381 ui.status(_('listening at http://%s:%d/\n') %
2383 (self.httpd.addr, self.httpd.port))
2382 (self.httpd.addr, self.httpd.port))
2384 else:
2383 else:
2385 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2384 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2386
2385
2387 def run(self):
2386 def run(self):
2388 self.httpd.serve_forever()
2387 self.httpd.serve_forever()
2389
2388
2390 service = service()
2389 service = service()
2391
2390
2392 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2391 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2393
2392
2394 def status(ui, repo, *pats, **opts):
2393 def status(ui, repo, *pats, **opts):
2395 """show changed files in the working directory
2394 """show changed files in the working directory
2396
2395
2397 Show status of files in the repository. If names are given, only
2396 Show status of files in the repository. If names are given, only
2398 files that match are shown. Files that are clean or ignored, are
2397 files that match are shown. Files that are clean or ignored, are
2399 not listed unless -c (clean), -i (ignored) or -A is given.
2398 not listed unless -c (clean), -i (ignored) or -A is given.
2400
2399
2401 NOTE: status may appear to disagree with diff if permissions have
2400 NOTE: status may appear to disagree with diff if permissions have
2402 changed or a merge has occurred. The standard diff format does not
2401 changed or a merge has occurred. The standard diff format does not
2403 report permission changes and diff only reports changes relative
2402 report permission changes and diff only reports changes relative
2404 to one merge parent.
2403 to one merge parent.
2405
2404
2406 If one revision is given, it is used as the base revision.
2405 If one revision is given, it is used as the base revision.
2407 If two revisions are given, the difference between them is shown.
2406 If two revisions are given, the difference between them is shown.
2408
2407
2409 The codes used to show the status of files are:
2408 The codes used to show the status of files are:
2410 M = modified
2409 M = modified
2411 A = added
2410 A = added
2412 R = removed
2411 R = removed
2413 C = clean
2412 C = clean
2414 ! = deleted, but still tracked
2413 ! = deleted, but still tracked
2415 ? = not tracked
2414 ? = not tracked
2416 I = ignored (not shown by default)
2415 I = ignored (not shown by default)
2417 = the previous added file was copied from here
2416 = the previous added file was copied from here
2418 """
2417 """
2419
2418
2420 all = opts['all']
2419 all = opts['all']
2421 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2420 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2422
2421
2423 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2422 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2424 cwd = (pats and repo.getcwd()) or ''
2423 cwd = (pats and repo.getcwd()) or ''
2425 modified, added, removed, deleted, unknown, ignored, clean = [
2424 modified, added, removed, deleted, unknown, ignored, clean = [
2426 n for n in repo.status(node1=node1, node2=node2, files=files,
2425 n for n in repo.status(node1=node1, node2=node2, files=files,
2427 match=matchfn,
2426 match=matchfn,
2428 list_ignored=all or opts['ignored'],
2427 list_ignored=all or opts['ignored'],
2429 list_clean=all or opts['clean'])]
2428 list_clean=all or opts['clean'])]
2430
2429
2431 changetypes = (('modified', 'M', modified),
2430 changetypes = (('modified', 'M', modified),
2432 ('added', 'A', added),
2431 ('added', 'A', added),
2433 ('removed', 'R', removed),
2432 ('removed', 'R', removed),
2434 ('deleted', '!', deleted),
2433 ('deleted', '!', deleted),
2435 ('unknown', '?', unknown),
2434 ('unknown', '?', unknown),
2436 ('ignored', 'I', ignored))
2435 ('ignored', 'I', ignored))
2437
2436
2438 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2437 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2439
2438
2440 end = opts['print0'] and '\0' or '\n'
2439 end = opts['print0'] and '\0' or '\n'
2441
2440
2442 for opt, char, changes in ([ct for ct in explicit_changetypes
2441 for opt, char, changes in ([ct for ct in explicit_changetypes
2443 if all or opts[ct[0]]]
2442 if all or opts[ct[0]]]
2444 or changetypes):
2443 or changetypes):
2445 if opts['no_status']:
2444 if opts['no_status']:
2446 format = "%%s%s" % end
2445 format = "%%s%s" % end
2447 else:
2446 else:
2448 format = "%s %%s%s" % (char, end)
2447 format = "%s %%s%s" % (char, end)
2449
2448
2450 for f in changes:
2449 for f in changes:
2451 ui.write(format % repo.pathto(f, cwd))
2450 ui.write(format % repo.pathto(f, cwd))
2452 if ((all or opts.get('copies')) and not opts.get('no_status')):
2451 if ((all or opts.get('copies')) and not opts.get('no_status')):
2453 copied = repo.dirstate.copied(f)
2452 copied = repo.dirstate.copied(f)
2454 if copied:
2453 if copied:
2455 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2454 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2456
2455
2457 def tag(ui, repo, name, rev_=None, **opts):
2456 def tag(ui, repo, name, rev_=None, **opts):
2458 """add a tag for the current or given revision
2457 """add a tag for the current or given revision
2459
2458
2460 Name a particular revision using <name>.
2459 Name a particular revision using <name>.
2461
2460
2462 Tags are used to name particular revisions of the repository and are
2461 Tags are used to name particular revisions of the repository and are
2463 very useful to compare different revision, to go back to significant
2462 very useful to compare different revision, to go back to significant
2464 earlier versions or to mark branch points as releases, etc.
2463 earlier versions or to mark branch points as releases, etc.
2465
2464
2466 If no revision is given, the parent of the working directory is used,
2465 If no revision is given, the parent of the working directory is used,
2467 or tip if no revision is checked out.
2466 or tip if no revision is checked out.
2468
2467
2469 To facilitate version control, distribution, and merging of tags,
2468 To facilitate version control, distribution, and merging of tags,
2470 they are stored as a file named ".hgtags" which is managed
2469 they are stored as a file named ".hgtags" which is managed
2471 similarly to other project files and can be hand-edited if
2470 similarly to other project files and can be hand-edited if
2472 necessary. The file '.hg/localtags' is used for local tags (not
2471 necessary. The file '.hg/localtags' is used for local tags (not
2473 shared among repositories).
2472 shared among repositories).
2474 """
2473 """
2475 if name in ['tip', '.', 'null']:
2474 if name in ['tip', '.', 'null']:
2476 raise util.Abort(_("the name '%s' is reserved") % name)
2475 raise util.Abort(_("the name '%s' is reserved") % name)
2477 if rev_ is not None:
2476 if rev_ is not None:
2478 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2477 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2479 "please use 'hg tag [-r REV] NAME' instead\n"))
2478 "please use 'hg tag [-r REV] NAME' instead\n"))
2480 if opts['rev']:
2479 if opts['rev']:
2481 raise util.Abort(_("use only one form to specify the revision"))
2480 raise util.Abort(_("use only one form to specify the revision"))
2482 if opts['rev'] and opts['remove']:
2481 if opts['rev'] and opts['remove']:
2483 raise util.Abort(_("--rev and --remove are incompatible"))
2482 raise util.Abort(_("--rev and --remove are incompatible"))
2484 if opts['rev']:
2483 if opts['rev']:
2485 rev_ = opts['rev']
2484 rev_ = opts['rev']
2486 message = opts['message']
2485 message = opts['message']
2487 if opts['remove']:
2486 if opts['remove']:
2488 rev_ = nullid
2487 rev_ = nullid
2489 if not message:
2488 if not message:
2490 message = _('Removed tag %s') % name
2489 message = _('Removed tag %s') % name
2491 elif name in repo.tags() and not opts['force']:
2490 elif name in repo.tags() and not opts['force']:
2492 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2491 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2493 % name)
2492 % name)
2494 if not rev_ and repo.dirstate.parents()[1] != nullid:
2493 if not rev_ and repo.dirstate.parents()[1] != nullid:
2495 raise util.Abort(_('uncommitted merge - please provide a '
2494 raise util.Abort(_('uncommitted merge - please provide a '
2496 'specific revision'))
2495 'specific revision'))
2497 r = repo.changectx(rev_).node()
2496 r = repo.changectx(rev_).node()
2498
2497
2499 if not message:
2498 if not message:
2500 message = _('Added tag %s for changeset %s') % (name, short(r))
2499 message = _('Added tag %s for changeset %s') % (name, short(r))
2501
2500
2502 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2501 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2503
2502
2504 def tags(ui, repo):
2503 def tags(ui, repo):
2505 """list repository tags
2504 """list repository tags
2506
2505
2507 List the repository tags.
2506 List the repository tags.
2508
2507
2509 This lists both regular and local tags.
2508 This lists both regular and local tags.
2510 """
2509 """
2511
2510
2512 l = repo.tagslist()
2511 l = repo.tagslist()
2513 l.reverse()
2512 l.reverse()
2514 hexfunc = ui.debugflag and hex or short
2513 hexfunc = ui.debugflag and hex or short
2515 for t, n in l:
2514 for t, n in l:
2516 try:
2515 try:
2517 hn = hexfunc(n)
2516 hn = hexfunc(n)
2518 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2517 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2519 except revlog.LookupError:
2518 except revlog.LookupError:
2520 r = " ?:%s" % hn
2519 r = " ?:%s" % hn
2521 if ui.quiet:
2520 if ui.quiet:
2522 ui.write("%s\n" % t)
2521 ui.write("%s\n" % t)
2523 else:
2522 else:
2524 spaces = " " * (30 - util.locallen(t))
2523 spaces = " " * (30 - util.locallen(t))
2525 ui.write("%s%s %s\n" % (t, spaces, r))
2524 ui.write("%s%s %s\n" % (t, spaces, r))
2526
2525
2527 def tip(ui, repo, **opts):
2526 def tip(ui, repo, **opts):
2528 """show the tip revision
2527 """show the tip revision
2529
2528
2530 Show the tip revision.
2529 Show the tip revision.
2531 """
2530 """
2532 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2531 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2533
2532
2534 def unbundle(ui, repo, fname, **opts):
2533 def unbundle(ui, repo, fname, **opts):
2535 """apply a changegroup file
2534 """apply a changegroup file
2536
2535
2537 Apply a compressed changegroup file generated by the bundle
2536 Apply a compressed changegroup file generated by the bundle
2538 command.
2537 command.
2539 """
2538 """
2540 if os.path.exists(fname):
2539 if os.path.exists(fname):
2541 f = open(fname, "rb")
2540 f = open(fname, "rb")
2542 else:
2541 else:
2543 f = urllib.urlopen(fname)
2542 f = urllib.urlopen(fname)
2544 gen = changegroup.readbundle(f, fname)
2543 gen = changegroup.readbundle(f, fname)
2545 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2544 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2546 return postincoming(ui, repo, modheads, opts['update'])
2545 return postincoming(ui, repo, modheads, opts['update'])
2547
2546
2548 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2547 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2549 """update working directory
2548 """update working directory
2550
2549
2551 Update the working directory to the specified revision, or the
2550 Update the working directory to the specified revision, or the
2552 tip of the current branch if none is specified.
2551 tip of the current branch if none is specified.
2553
2552
2554 If there are no outstanding changes in the working directory and
2553 If there are no outstanding changes in the working directory and
2555 there is a linear relationship between the current version and the
2554 there is a linear relationship between the current version and the
2556 requested version, the result is the requested version.
2555 requested version, the result is the requested version.
2557
2556
2558 To merge the working directory with another revision, use the
2557 To merge the working directory with another revision, use the
2559 merge command.
2558 merge command.
2560
2559
2561 By default, update will refuse to run if doing so would require
2560 By default, update will refuse to run if doing so would require
2562 discarding local changes.
2561 discarding local changes.
2563 """
2562 """
2564 if rev and node:
2563 if rev and node:
2565 raise util.Abort(_("please specify just one revision"))
2564 raise util.Abort(_("please specify just one revision"))
2566
2565
2567 if not rev:
2566 if not rev:
2568 rev = node
2567 rev = node
2569
2568
2570 if date:
2569 if date:
2571 if rev:
2570 if rev:
2572 raise util.Abort(_("you can't specify a revision and a date"))
2571 raise util.Abort(_("you can't specify a revision and a date"))
2573 rev = cmdutil.finddate(ui, repo, date)
2572 rev = cmdutil.finddate(ui, repo, date)
2574
2573
2575 if clean:
2574 if clean:
2576 return hg.clean(repo, rev)
2575 return hg.clean(repo, rev)
2577 else:
2576 else:
2578 return hg.update(repo, rev)
2577 return hg.update(repo, rev)
2579
2578
2580 def verify(ui, repo):
2579 def verify(ui, repo):
2581 """verify the integrity of the repository
2580 """verify the integrity of the repository
2582
2581
2583 Verify the integrity of the current repository.
2582 Verify the integrity of the current repository.
2584
2583
2585 This will perform an extensive check of the repository's
2584 This will perform an extensive check of the repository's
2586 integrity, validating the hashes and checksums of each entry in
2585 integrity, validating the hashes and checksums of each entry in
2587 the changelog, manifest, and tracked files, as well as the
2586 the changelog, manifest, and tracked files, as well as the
2588 integrity of their crosslinks and indices.
2587 integrity of their crosslinks and indices.
2589 """
2588 """
2590 return hg.verify(repo)
2589 return hg.verify(repo)
2591
2590
2592 def version_(ui):
2591 def version_(ui):
2593 """output version and copyright information"""
2592 """output version and copyright information"""
2594 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2593 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2595 % version.get_version())
2594 % version.get_version())
2596 ui.status(_(
2595 ui.status(_(
2597 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
2596 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
2598 "This is free software; see the source for copying conditions. "
2597 "This is free software; see the source for copying conditions. "
2599 "There is NO\nwarranty; "
2598 "There is NO\nwarranty; "
2600 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2599 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2601 ))
2600 ))
2602
2601
2603 # Command options and aliases are listed here, alphabetically
2602 # Command options and aliases are listed here, alphabetically
2604
2603
2605 globalopts = [
2604 globalopts = [
2606 ('R', 'repository', '',
2605 ('R', 'repository', '',
2607 _('repository root directory or symbolic path name')),
2606 _('repository root directory or symbolic path name')),
2608 ('', 'cwd', '', _('change working directory')),
2607 ('', 'cwd', '', _('change working directory')),
2609 ('y', 'noninteractive', None,
2608 ('y', 'noninteractive', None,
2610 _('do not prompt, assume \'yes\' for any required answers')),
2609 _('do not prompt, assume \'yes\' for any required answers')),
2611 ('q', 'quiet', None, _('suppress output')),
2610 ('q', 'quiet', None, _('suppress output')),
2612 ('v', 'verbose', None, _('enable additional output')),
2611 ('v', 'verbose', None, _('enable additional output')),
2613 ('', 'config', [], _('set/override config option')),
2612 ('', 'config', [], _('set/override config option')),
2614 ('', 'debug', None, _('enable debugging output')),
2613 ('', 'debug', None, _('enable debugging output')),
2615 ('', 'debugger', None, _('start debugger')),
2614 ('', 'debugger', None, _('start debugger')),
2616 ('', 'encoding', util._encoding, _('set the charset encoding')),
2615 ('', 'encoding', util._encoding, _('set the charset encoding')),
2617 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2616 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2618 ('', 'lsprof', None, _('print improved command execution profile')),
2617 ('', 'lsprof', None, _('print improved command execution profile')),
2619 ('', 'traceback', None, _('print traceback on exception')),
2618 ('', 'traceback', None, _('print traceback on exception')),
2620 ('', 'time', None, _('time how long the command takes')),
2619 ('', 'time', None, _('time how long the command takes')),
2621 ('', 'profile', None, _('print command execution profile')),
2620 ('', 'profile', None, _('print command execution profile')),
2622 ('', 'version', None, _('output version information and exit')),
2621 ('', 'version', None, _('output version information and exit')),
2623 ('h', 'help', None, _('display help and exit')),
2622 ('h', 'help', None, _('display help and exit')),
2624 ]
2623 ]
2625
2624
2626 dryrunopts = [('n', 'dry-run', None,
2625 dryrunopts = [('n', 'dry-run', None,
2627 _('do not perform actions, just print output'))]
2626 _('do not perform actions, just print output'))]
2628
2627
2629 remoteopts = [
2628 remoteopts = [
2630 ('e', 'ssh', '', _('specify ssh command to use')),
2629 ('e', 'ssh', '', _('specify ssh command to use')),
2631 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2630 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2632 ]
2631 ]
2633
2632
2634 walkopts = [
2633 walkopts = [
2635 ('I', 'include', [], _('include names matching the given patterns')),
2634 ('I', 'include', [], _('include names matching the given patterns')),
2636 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2635 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2637 ]
2636 ]
2638
2637
2639 commitopts = [
2638 commitopts = [
2640 ('m', 'message', '', _('use <text> as commit message')),
2639 ('m', 'message', '', _('use <text> as commit message')),
2641 ('l', 'logfile', '', _('read commit message from <file>')),
2640 ('l', 'logfile', '', _('read commit message from <file>')),
2642 ]
2641 ]
2643
2642
2644 table = {
2643 table = {
2645 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2644 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2646 "addremove":
2645 "addremove":
2647 (addremove,
2646 (addremove,
2648 [('s', 'similarity', '',
2647 [('s', 'similarity', '',
2649 _('guess renamed files by similarity (0<=s<=100)')),
2648 _('guess renamed files by similarity (0<=s<=100)')),
2650 ] + walkopts + dryrunopts,
2649 ] + walkopts + dryrunopts,
2651 _('hg addremove [OPTION]... [FILE]...')),
2650 _('hg addremove [OPTION]... [FILE]...')),
2652 "^annotate":
2651 "^annotate":
2653 (annotate,
2652 (annotate,
2654 [('r', 'rev', '', _('annotate the specified revision')),
2653 [('r', 'rev', '', _('annotate the specified revision')),
2655 ('f', 'follow', None, _('follow file copies and renames')),
2654 ('f', 'follow', None, _('follow file copies and renames')),
2656 ('a', 'text', None, _('treat all files as text')),
2655 ('a', 'text', None, _('treat all files as text')),
2657 ('u', 'user', None, _('list the author')),
2656 ('u', 'user', None, _('list the author')),
2658 ('d', 'date', None, _('list the date')),
2657 ('d', 'date', None, _('list the date')),
2659 ('n', 'number', None, _('list the revision number (default)')),
2658 ('n', 'number', None, _('list the revision number (default)')),
2660 ('c', 'changeset', None, _('list the changeset')),
2659 ('c', 'changeset', None, _('list the changeset')),
2661 ] + walkopts,
2660 ] + walkopts,
2662 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] FILE...')),
2661 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] FILE...')),
2663 "archive":
2662 "archive":
2664 (archive,
2663 (archive,
2665 [('', 'no-decode', None, _('do not pass files through decoders')),
2664 [('', 'no-decode', None, _('do not pass files through decoders')),
2666 ('p', 'prefix', '', _('directory prefix for files in archive')),
2665 ('p', 'prefix', '', _('directory prefix for files in archive')),
2667 ('r', 'rev', '', _('revision to distribute')),
2666 ('r', 'rev', '', _('revision to distribute')),
2668 ('t', 'type', '', _('type of distribution to create')),
2667 ('t', 'type', '', _('type of distribution to create')),
2669 ] + walkopts,
2668 ] + walkopts,
2670 _('hg archive [OPTION]... DEST')),
2669 _('hg archive [OPTION]... DEST')),
2671 "backout":
2670 "backout":
2672 (backout,
2671 (backout,
2673 [('', 'merge', None,
2672 [('', 'merge', None,
2674 _('merge with old dirstate parent after backout')),
2673 _('merge with old dirstate parent after backout')),
2675 ('d', 'date', '', _('record datecode as commit date')),
2674 ('d', 'date', '', _('record datecode as commit date')),
2676 ('', 'parent', '', _('parent to choose when backing out merge')),
2675 ('', 'parent', '', _('parent to choose when backing out merge')),
2677 ('u', 'user', '', _('record user as committer')),
2676 ('u', 'user', '', _('record user as committer')),
2678 ('r', 'rev', '', _('revision to backout')),
2677 ('r', 'rev', '', _('revision to backout')),
2679 ] + walkopts + commitopts,
2678 ] + walkopts + commitopts,
2680 _('hg backout [OPTION]... [-r] REV')),
2679 _('hg backout [OPTION]... [-r] REV')),
2681 "branch": (branch,
2680 "branch": (branch,
2682 [('f', 'force', None,
2681 [('f', 'force', None,
2683 _('set branch name even if it shadows an existing branch'))],
2682 _('set branch name even if it shadows an existing branch'))],
2684 _('hg branch [NAME]')),
2683 _('hg branch [NAME]')),
2685 "branches": (branches, [], _('hg branches')),
2684 "branches": (branches, [], _('hg branches')),
2686 "bundle":
2685 "bundle":
2687 (bundle,
2686 (bundle,
2688 [('f', 'force', None,
2687 [('f', 'force', None,
2689 _('run even when remote repository is unrelated')),
2688 _('run even when remote repository is unrelated')),
2690 ('r', 'rev', [],
2689 ('r', 'rev', [],
2691 _('a changeset you would like to bundle')),
2690 _('a changeset you would like to bundle')),
2692 ('', 'base', [],
2691 ('', 'base', [],
2693 _('a base changeset to specify instead of a destination')),
2692 _('a base changeset to specify instead of a destination')),
2694 ] + remoteopts,
2693 ] + remoteopts,
2695 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2694 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2696 "cat":
2695 "cat":
2697 (cat,
2696 (cat,
2698 [('o', 'output', '', _('print output to file with formatted name')),
2697 [('o', 'output', '', _('print output to file with formatted name')),
2699 ('r', 'rev', '', _('print the given revision')),
2698 ('r', 'rev', '', _('print the given revision')),
2700 ] + walkopts,
2699 ] + walkopts,
2701 _('hg cat [OPTION]... FILE...')),
2700 _('hg cat [OPTION]... FILE...')),
2702 "^clone":
2701 "^clone":
2703 (clone,
2702 (clone,
2704 [('U', 'noupdate', None, _('do not update the new working directory')),
2703 [('U', 'noupdate', None, _('do not update the new working directory')),
2705 ('r', 'rev', [],
2704 ('r', 'rev', [],
2706 _('a changeset you would like to have after cloning')),
2705 _('a changeset you would like to have after cloning')),
2707 ('', 'pull', None, _('use pull protocol to copy metadata')),
2706 ('', 'pull', None, _('use pull protocol to copy metadata')),
2708 ('', 'uncompressed', None,
2707 ('', 'uncompressed', None,
2709 _('use uncompressed transfer (fast over LAN)')),
2708 _('use uncompressed transfer (fast over LAN)')),
2710 ] + remoteopts,
2709 ] + remoteopts,
2711 _('hg clone [OPTION]... SOURCE [DEST]')),
2710 _('hg clone [OPTION]... SOURCE [DEST]')),
2712 "^commit|ci":
2711 "^commit|ci":
2713 (commit,
2712 (commit,
2714 [('A', 'addremove', None,
2713 [('A', 'addremove', None,
2715 _('mark new/missing files as added/removed before committing')),
2714 _('mark new/missing files as added/removed before committing')),
2716 ('d', 'date', '', _('record datecode as commit date')),
2715 ('d', 'date', '', _('record datecode as commit date')),
2717 ('u', 'user', '', _('record user as commiter')),
2716 ('u', 'user', '', _('record user as commiter')),
2718 ] + walkopts + commitopts,
2717 ] + walkopts + commitopts,
2719 _('hg commit [OPTION]... [FILE]...')),
2718 _('hg commit [OPTION]... [FILE]...')),
2720 "copy|cp":
2719 "copy|cp":
2721 (copy,
2720 (copy,
2722 [('A', 'after', None, _('record a copy that has already occurred')),
2721 [('A', 'after', None, _('record a copy that has already occurred')),
2723 ('f', 'force', None,
2722 ('f', 'force', None,
2724 _('forcibly copy over an existing managed file')),
2723 _('forcibly copy over an existing managed file')),
2725 ] + walkopts + dryrunopts,
2724 ] + walkopts + dryrunopts,
2726 _('hg copy [OPTION]... [SOURCE]... DEST')),
2725 _('hg copy [OPTION]... [SOURCE]... DEST')),
2727 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2726 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2728 "debugcomplete":
2727 "debugcomplete":
2729 (debugcomplete,
2728 (debugcomplete,
2730 [('o', 'options', None, _('show the command options'))],
2729 [('o', 'options', None, _('show the command options'))],
2731 _('debugcomplete [-o] CMD')),
2730 _('debugcomplete [-o] CMD')),
2732 "debuginstall": (debuginstall, [], _('debuginstall')),
2731 "debuginstall": (debuginstall, [], _('debuginstall')),
2733 "debugrebuildstate":
2732 "debugrebuildstate":
2734 (debugrebuildstate,
2733 (debugrebuildstate,
2735 [('r', 'rev', '', _('revision to rebuild to'))],
2734 [('r', 'rev', '', _('revision to rebuild to'))],
2736 _('debugrebuildstate [-r REV] [REV]')),
2735 _('debugrebuildstate [-r REV] [REV]')),
2737 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2736 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2738 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2737 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2739 "debugstate": (debugstate, [], _('debugstate')),
2738 "debugstate": (debugstate, [], _('debugstate')),
2740 "debugdate":
2739 "debugdate":
2741 (debugdate,
2740 (debugdate,
2742 [('e', 'extended', None, _('try extended date formats'))],
2741 [('e', 'extended', None, _('try extended date formats'))],
2743 _('debugdate [-e] DATE [RANGE]')),
2742 _('debugdate [-e] DATE [RANGE]')),
2744 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2743 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2745 "debugindex": (debugindex, [], _('debugindex FILE')),
2744 "debugindex": (debugindex, [], _('debugindex FILE')),
2746 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2745 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2747 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2746 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2748 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2747 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2749 "^diff":
2748 "^diff":
2750 (diff,
2749 (diff,
2751 [('r', 'rev', [], _('revision')),
2750 [('r', 'rev', [], _('revision')),
2752 ('a', 'text', None, _('treat all files as text')),
2751 ('a', 'text', None, _('treat all files as text')),
2753 ('p', 'show-function', None,
2752 ('p', 'show-function', None,
2754 _('show which function each change is in')),
2753 _('show which function each change is in')),
2755 ('g', 'git', None, _('use git extended diff format')),
2754 ('g', 'git', None, _('use git extended diff format')),
2756 ('', 'nodates', None, _("don't include dates in diff headers")),
2755 ('', 'nodates', None, _("don't include dates in diff headers")),
2757 ('w', 'ignore-all-space', None,
2756 ('w', 'ignore-all-space', None,
2758 _('ignore white space when comparing lines')),
2757 _('ignore white space when comparing lines')),
2759 ('b', 'ignore-space-change', None,
2758 ('b', 'ignore-space-change', None,
2760 _('ignore changes in the amount of white space')),
2759 _('ignore changes in the amount of white space')),
2761 ('B', 'ignore-blank-lines', None,
2760 ('B', 'ignore-blank-lines', None,
2762 _('ignore changes whose lines are all blank')),
2761 _('ignore changes whose lines are all blank')),
2763 ] + walkopts,
2762 ] + walkopts,
2764 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2763 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2765 "^export":
2764 "^export":
2766 (export,
2765 (export,
2767 [('o', 'output', '', _('print output to file with formatted name')),
2766 [('o', 'output', '', _('print output to file with formatted name')),
2768 ('a', 'text', None, _('treat all files as text')),
2767 ('a', 'text', None, _('treat all files as text')),
2769 ('g', 'git', None, _('use git extended diff format')),
2768 ('g', 'git', None, _('use git extended diff format')),
2770 ('', 'nodates', None, _("don't include dates in diff headers")),
2769 ('', 'nodates', None, _("don't include dates in diff headers")),
2771 ('', 'switch-parent', None, _('diff against the second parent'))],
2770 ('', 'switch-parent', None, _('diff against the second parent'))],
2772 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2771 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2773 "grep":
2772 "grep":
2774 (grep,
2773 (grep,
2775 [('0', 'print0', None, _('end fields with NUL')),
2774 [('0', 'print0', None, _('end fields with NUL')),
2776 ('', 'all', None, _('print all revisions that match')),
2775 ('', 'all', None, _('print all revisions that match')),
2777 ('f', 'follow', None,
2776 ('f', 'follow', None,
2778 _('follow changeset history, or file history across copies and renames')),
2777 _('follow changeset history, or file history across copies and renames')),
2779 ('i', 'ignore-case', None, _('ignore case when matching')),
2778 ('i', 'ignore-case', None, _('ignore case when matching')),
2780 ('l', 'files-with-matches', None,
2779 ('l', 'files-with-matches', None,
2781 _('print only filenames and revs that match')),
2780 _('print only filenames and revs that match')),
2782 ('n', 'line-number', None, _('print matching line numbers')),
2781 ('n', 'line-number', None, _('print matching line numbers')),
2783 ('r', 'rev', [], _('search in given revision range')),
2782 ('r', 'rev', [], _('search in given revision range')),
2784 ('u', 'user', None, _('print user who committed change')),
2783 ('u', 'user', None, _('print user who committed change')),
2785 ] + walkopts,
2784 ] + walkopts,
2786 _('hg grep [OPTION]... PATTERN [FILE]...')),
2785 _('hg grep [OPTION]... PATTERN [FILE]...')),
2787 "heads":
2786 "heads":
2788 (heads,
2787 (heads,
2789 [('', 'style', '', _('display using template map file')),
2788 [('', 'style', '', _('display using template map file')),
2790 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2789 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2791 ('', 'template', '', _('display with template'))],
2790 ('', 'template', '', _('display with template'))],
2792 _('hg heads [-r REV]')),
2791 _('hg heads [-r REV]')),
2793 "help": (help_, [], _('hg help [COMMAND]')),
2792 "help": (help_, [], _('hg help [COMMAND]')),
2794 "identify|id": (identify, [], _('hg identify')),
2793 "identify|id": (identify, [], _('hg identify')),
2795 "import|patch":
2794 "import|patch":
2796 (import_,
2795 (import_,
2797 [('p', 'strip', 1,
2796 [('p', 'strip', 1,
2798 _('directory strip option for patch. This has the same\n'
2797 _('directory strip option for patch. This has the same\n'
2799 'meaning as the corresponding patch option')),
2798 'meaning as the corresponding patch option')),
2800 ('b', 'base', '', _('base path')),
2799 ('b', 'base', '', _('base path')),
2801 ('f', 'force', None,
2800 ('f', 'force', None,
2802 _('skip check for outstanding uncommitted changes')),
2801 _('skip check for outstanding uncommitted changes')),
2803 ('', 'exact', None,
2802 ('', 'exact', None,
2804 _('apply patch to the nodes from which it was generated'))] + commitopts,
2803 _('apply patch to the nodes from which it was generated'))] + commitopts,
2805 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2804 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2806 "incoming|in": (incoming,
2805 "incoming|in": (incoming,
2807 [('M', 'no-merges', None, _('do not show merges')),
2806 [('M', 'no-merges', None, _('do not show merges')),
2808 ('f', 'force', None,
2807 ('f', 'force', None,
2809 _('run even when remote repository is unrelated')),
2808 _('run even when remote repository is unrelated')),
2810 ('', 'style', '', _('display using template map file')),
2809 ('', 'style', '', _('display using template map file')),
2811 ('n', 'newest-first', None, _('show newest record first')),
2810 ('n', 'newest-first', None, _('show newest record first')),
2812 ('', 'bundle', '', _('file to store the bundles into')),
2811 ('', 'bundle', '', _('file to store the bundles into')),
2813 ('p', 'patch', None, _('show patch')),
2812 ('p', 'patch', None, _('show patch')),
2814 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2813 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2815 ('', 'template', '', _('display with template')),
2814 ('', 'template', '', _('display with template')),
2816 ] + remoteopts,
2815 ] + remoteopts,
2817 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2816 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2818 ' [--bundle FILENAME] [SOURCE]')),
2817 ' [--bundle FILENAME] [SOURCE]')),
2819 "^init":
2818 "^init":
2820 (init,
2819 (init,
2821 remoteopts,
2820 remoteopts,
2822 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2821 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2823 "locate":
2822 "locate":
2824 (locate,
2823 (locate,
2825 [('r', 'rev', '', _('search the repository as it stood at rev')),
2824 [('r', 'rev', '', _('search the repository as it stood at rev')),
2826 ('0', 'print0', None,
2825 ('0', 'print0', None,
2827 _('end filenames with NUL, for use with xargs')),
2826 _('end filenames with NUL, for use with xargs')),
2828 ('f', 'fullpath', None,
2827 ('f', 'fullpath', None,
2829 _('print complete paths from the filesystem root')),
2828 _('print complete paths from the filesystem root')),
2830 ] + walkopts,
2829 ] + walkopts,
2831 _('hg locate [OPTION]... [PATTERN]...')),
2830 _('hg locate [OPTION]... [PATTERN]...')),
2832 "^log|history":
2831 "^log|history":
2833 (log,
2832 (log,
2834 [('f', 'follow', None,
2833 [('f', 'follow', None,
2835 _('follow changeset history, or file history across copies and renames')),
2834 _('follow changeset history, or file history across copies and renames')),
2836 ('', 'follow-first', None,
2835 ('', 'follow-first', None,
2837 _('only follow the first parent of merge changesets')),
2836 _('only follow the first parent of merge changesets')),
2838 ('d', 'date', '', _('show revs matching date spec')),
2837 ('d', 'date', '', _('show revs matching date spec')),
2839 ('C', 'copies', None, _('show copied files')),
2838 ('C', 'copies', None, _('show copied files')),
2840 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
2839 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
2841 ('l', 'limit', '', _('limit number of changes displayed')),
2840 ('l', 'limit', '', _('limit number of changes displayed')),
2842 ('r', 'rev', [], _('show the specified revision or range')),
2841 ('r', 'rev', [], _('show the specified revision or range')),
2843 ('', 'removed', None, _('include revs where files were removed')),
2842 ('', 'removed', None, _('include revs where files were removed')),
2844 ('M', 'no-merges', None, _('do not show merges')),
2843 ('M', 'no-merges', None, _('do not show merges')),
2845 ('', 'style', '', _('display using template map file')),
2844 ('', 'style', '', _('display using template map file')),
2846 ('m', 'only-merges', None, _('show only merges')),
2845 ('m', 'only-merges', None, _('show only merges')),
2847 ('p', 'patch', None, _('show patch')),
2846 ('p', 'patch', None, _('show patch')),
2848 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
2847 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
2849 ('', 'template', '', _('display with template')),
2848 ('', 'template', '', _('display with template')),
2850 ] + walkopts,
2849 ] + walkopts,
2851 _('hg log [OPTION]... [FILE]')),
2850 _('hg log [OPTION]... [FILE]')),
2852 "manifest": (manifest, [], _('hg manifest [REV]')),
2851 "manifest": (manifest, [], _('hg manifest [REV]')),
2853 "^merge":
2852 "^merge":
2854 (merge,
2853 (merge,
2855 [('f', 'force', None, _('force a merge with outstanding changes')),
2854 [('f', 'force', None, _('force a merge with outstanding changes')),
2856 ('r', 'rev', '', _('revision to merge')),
2855 ('r', 'rev', '', _('revision to merge')),
2857 ],
2856 ],
2858 _('hg merge [-f] [[-r] REV]')),
2857 _('hg merge [-f] [[-r] REV]')),
2859 "outgoing|out": (outgoing,
2858 "outgoing|out": (outgoing,
2860 [('M', 'no-merges', None, _('do not show merges')),
2859 [('M', 'no-merges', None, _('do not show merges')),
2861 ('f', 'force', None,
2860 ('f', 'force', None,
2862 _('run even when remote repository is unrelated')),
2861 _('run even when remote repository is unrelated')),
2863 ('p', 'patch', None, _('show patch')),
2862 ('p', 'patch', None, _('show patch')),
2864 ('', 'style', '', _('display using template map file')),
2863 ('', 'style', '', _('display using template map file')),
2865 ('r', 'rev', [], _('a specific revision you would like to push')),
2864 ('r', 'rev', [], _('a specific revision you would like to push')),
2866 ('n', 'newest-first', None, _('show newest record first')),
2865 ('n', 'newest-first', None, _('show newest record first')),
2867 ('', 'template', '', _('display with template')),
2866 ('', 'template', '', _('display with template')),
2868 ] + remoteopts,
2867 ] + remoteopts,
2869 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
2868 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
2870 "^parents":
2869 "^parents":
2871 (parents,
2870 (parents,
2872 [('r', 'rev', '', _('show parents from the specified rev')),
2871 [('r', 'rev', '', _('show parents from the specified rev')),
2873 ('', 'style', '', _('display using template map file')),
2872 ('', 'style', '', _('display using template map file')),
2874 ('', 'template', '', _('display with template'))],
2873 ('', 'template', '', _('display with template'))],
2875 _('hg parents [-r REV] [FILE]')),
2874 _('hg parents [-r REV] [FILE]')),
2876 "paths": (paths, [], _('hg paths [NAME]')),
2875 "paths": (paths, [], _('hg paths [NAME]')),
2877 "^pull":
2876 "^pull":
2878 (pull,
2877 (pull,
2879 [('u', 'update', None,
2878 [('u', 'update', None,
2880 _('update to new tip if changesets were pulled')),
2879 _('update to new tip if changesets were pulled')),
2881 ('f', 'force', None,
2880 ('f', 'force', None,
2882 _('run even when remote repository is unrelated')),
2881 _('run even when remote repository is unrelated')),
2883 ('r', 'rev', [],
2882 ('r', 'rev', [],
2884 _('a specific revision up to which you would like to pull')),
2883 _('a specific revision up to which you would like to pull')),
2885 ] + remoteopts,
2884 ] + remoteopts,
2886 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
2885 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
2887 "^push":
2886 "^push":
2888 (push,
2887 (push,
2889 [('f', 'force', None, _('force push')),
2888 [('f', 'force', None, _('force push')),
2890 ('r', 'rev', [], _('a specific revision you would like to push')),
2889 ('r', 'rev', [], _('a specific revision you would like to push')),
2891 ] + remoteopts,
2890 ] + remoteopts,
2892 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
2891 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
2893 "debugrawcommit|rawcommit":
2892 "debugrawcommit|rawcommit":
2894 (rawcommit,
2893 (rawcommit,
2895 [('p', 'parent', [], _('parent')),
2894 [('p', 'parent', [], _('parent')),
2896 ('d', 'date', '', _('date code')),
2895 ('d', 'date', '', _('date code')),
2897 ('u', 'user', '', _('user')),
2896 ('u', 'user', '', _('user')),
2898 ('F', 'files', '', _('file list'))
2897 ('F', 'files', '', _('file list'))
2899 ] + commitopts,
2898 ] + commitopts,
2900 _('hg debugrawcommit [OPTION]... [FILE]...')),
2899 _('hg debugrawcommit [OPTION]... [FILE]...')),
2901 "recover": (recover, [], _('hg recover')),
2900 "recover": (recover, [], _('hg recover')),
2902 "^remove|rm":
2901 "^remove|rm":
2903 (remove,
2902 (remove,
2904 [('A', 'after', None, _('record remove that has already occurred')),
2903 [('A', 'after', None, _('record remove that has already occurred')),
2905 ('f', 'force', None, _('remove file even if modified')),
2904 ('f', 'force', None, _('remove file even if modified')),
2906 ] + walkopts,
2905 ] + walkopts,
2907 _('hg remove [OPTION]... FILE...')),
2906 _('hg remove [OPTION]... FILE...')),
2908 "rename|mv":
2907 "rename|mv":
2909 (rename,
2908 (rename,
2910 [('A', 'after', None, _('record a rename that has already occurred')),
2909 [('A', 'after', None, _('record a rename that has already occurred')),
2911 ('f', 'force', None,
2910 ('f', 'force', None,
2912 _('forcibly copy over an existing managed file')),
2911 _('forcibly copy over an existing managed file')),
2913 ] + walkopts + dryrunopts,
2912 ] + walkopts + dryrunopts,
2914 _('hg rename [OPTION]... SOURCE... DEST')),
2913 _('hg rename [OPTION]... SOURCE... DEST')),
2915 "^revert":
2914 "^revert":
2916 (revert,
2915 (revert,
2917 [('a', 'all', None, _('revert all changes when no arguments given')),
2916 [('a', 'all', None, _('revert all changes when no arguments given')),
2918 ('d', 'date', '', _('tipmost revision matching date')),
2917 ('d', 'date', '', _('tipmost revision matching date')),
2919 ('r', 'rev', '', _('revision to revert to')),
2918 ('r', 'rev', '', _('revision to revert to')),
2920 ('', 'no-backup', None, _('do not save backup copies of files')),
2919 ('', 'no-backup', None, _('do not save backup copies of files')),
2921 ] + walkopts + dryrunopts,
2920 ] + walkopts + dryrunopts,
2922 _('hg revert [OPTION]... [-r REV] [NAME]...')),
2921 _('hg revert [OPTION]... [-r REV] [NAME]...')),
2923 "rollback": (rollback, [], _('hg rollback')),
2922 "rollback": (rollback, [], _('hg rollback')),
2924 "root": (root, [], _('hg root')),
2923 "root": (root, [], _('hg root')),
2925 "showconfig|debugconfig":
2924 "showconfig|debugconfig":
2926 (showconfig,
2925 (showconfig,
2927 [('u', 'untrusted', None, _('show untrusted configuration options'))],
2926 [('u', 'untrusted', None, _('show untrusted configuration options'))],
2928 _('showconfig [-u] [NAME]...')),
2927 _('showconfig [-u] [NAME]...')),
2929 "^serve":
2928 "^serve":
2930 (serve,
2929 (serve,
2931 [('A', 'accesslog', '', _('name of access log file to write to')),
2930 [('A', 'accesslog', '', _('name of access log file to write to')),
2932 ('d', 'daemon', None, _('run server in background')),
2931 ('d', 'daemon', None, _('run server in background')),
2933 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
2932 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
2934 ('E', 'errorlog', '', _('name of error log file to write to')),
2933 ('E', 'errorlog', '', _('name of error log file to write to')),
2935 ('p', 'port', 0, _('port to use (default: 8000)')),
2934 ('p', 'port', 0, _('port to use (default: 8000)')),
2936 ('a', 'address', '', _('address to use')),
2935 ('a', 'address', '', _('address to use')),
2937 ('n', 'name', '',
2936 ('n', 'name', '',
2938 _('name to show in web pages (default: working dir)')),
2937 _('name to show in web pages (default: working dir)')),
2939 ('', 'webdir-conf', '', _('name of the webdir config file'
2938 ('', 'webdir-conf', '', _('name of the webdir config file'
2940 ' (serve more than one repo)')),
2939 ' (serve more than one repo)')),
2941 ('', 'pid-file', '', _('name of file to write process ID to')),
2940 ('', 'pid-file', '', _('name of file to write process ID to')),
2942 ('', 'stdio', None, _('for remote clients')),
2941 ('', 'stdio', None, _('for remote clients')),
2943 ('t', 'templates', '', _('web templates to use')),
2942 ('t', 'templates', '', _('web templates to use')),
2944 ('', 'style', '', _('template style to use')),
2943 ('', 'style', '', _('template style to use')),
2945 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2944 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2946 _('hg serve [OPTION]...')),
2945 _('hg serve [OPTION]...')),
2947 "^status|st":
2946 "^status|st":
2948 (status,
2947 (status,
2949 [('A', 'all', None, _('show status of all files')),
2948 [('A', 'all', None, _('show status of all files')),
2950 ('m', 'modified', None, _('show only modified files')),
2949 ('m', 'modified', None, _('show only modified files')),
2951 ('a', 'added', None, _('show only added files')),
2950 ('a', 'added', None, _('show only added files')),
2952 ('r', 'removed', None, _('show only removed files')),
2951 ('r', 'removed', None, _('show only removed files')),
2953 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2952 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2954 ('c', 'clean', None, _('show only files without changes')),
2953 ('c', 'clean', None, _('show only files without changes')),
2955 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2954 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2956 ('i', 'ignored', None, _('show only ignored files')),
2955 ('i', 'ignored', None, _('show only ignored files')),
2957 ('n', 'no-status', None, _('hide status prefix')),
2956 ('n', 'no-status', None, _('hide status prefix')),
2958 ('C', 'copies', None, _('show source of copied files')),
2957 ('C', 'copies', None, _('show source of copied files')),
2959 ('0', 'print0', None,
2958 ('0', 'print0', None,
2960 _('end filenames with NUL, for use with xargs')),
2959 _('end filenames with NUL, for use with xargs')),
2961 ('', 'rev', [], _('show difference from revision')),
2960 ('', 'rev', [], _('show difference from revision')),
2962 ] + walkopts,
2961 ] + walkopts,
2963 _('hg status [OPTION]... [FILE]...')),
2962 _('hg status [OPTION]... [FILE]...')),
2964 "tag":
2963 "tag":
2965 (tag,
2964 (tag,
2966 [('f', 'force', None, _('replace existing tag')),
2965 [('f', 'force', None, _('replace existing tag')),
2967 ('l', 'local', None, _('make the tag local')),
2966 ('l', 'local', None, _('make the tag local')),
2968 ('m', 'message', '', _('message for tag commit log entry')),
2967 ('m', 'message', '', _('message for tag commit log entry')),
2969 ('d', 'date', '', _('record datecode as commit date')),
2968 ('d', 'date', '', _('record datecode as commit date')),
2970 ('u', 'user', '', _('record user as commiter')),
2969 ('u', 'user', '', _('record user as commiter')),
2971 ('r', 'rev', '', _('revision to tag')),
2970 ('r', 'rev', '', _('revision to tag')),
2972 ('', 'remove', None, _('remove a tag'))],
2971 ('', 'remove', None, _('remove a tag'))],
2973 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
2972 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
2974 "tags": (tags, [], _('hg tags')),
2973 "tags": (tags, [], _('hg tags')),
2975 "tip":
2974 "tip":
2976 (tip,
2975 (tip,
2977 [('', 'style', '', _('display using template map file')),
2976 [('', 'style', '', _('display using template map file')),
2978 ('p', 'patch', None, _('show patch')),
2977 ('p', 'patch', None, _('show patch')),
2979 ('', 'template', '', _('display with template'))],
2978 ('', 'template', '', _('display with template'))],
2980 _('hg tip [-p]')),
2979 _('hg tip [-p]')),
2981 "unbundle":
2980 "unbundle":
2982 (unbundle,
2981 (unbundle,
2983 [('u', 'update', None,
2982 [('u', 'update', None,
2984 _('update to new tip if changesets were unbundled'))],
2983 _('update to new tip if changesets were unbundled'))],
2985 _('hg unbundle [-u] FILE')),
2984 _('hg unbundle [-u] FILE')),
2986 "^update|up|checkout|co":
2985 "^update|up|checkout|co":
2987 (update,
2986 (update,
2988 [('C', 'clean', None, _('overwrite locally modified files')),
2987 [('C', 'clean', None, _('overwrite locally modified files')),
2989 ('d', 'date', '', _('tipmost revision matching date')),
2988 ('d', 'date', '', _('tipmost revision matching date')),
2990 ('r', 'rev', '', _('revision'))],
2989 ('r', 'rev', '', _('revision'))],
2991 _('hg update [-C] [-d DATE] [[-r] REV]')),
2990 _('hg update [-C] [-d DATE] [[-r] REV]')),
2992 "verify": (verify, [], _('hg verify')),
2991 "verify": (verify, [], _('hg verify')),
2993 "version": (version_, [], _('hg version')),
2992 "version": (version_, [], _('hg version')),
2994 }
2993 }
2995
2994
2996 norepo = ("clone init version help debugancestor debugcomplete debugdata"
2995 norepo = ("clone init version help debugancestor debugcomplete debugdata"
2997 " debugindex debugindexdot debugdate debuginstall")
2996 " debugindex debugindexdot debugdate debuginstall")
2998 optionalrepo = ("paths serve showconfig")
2997 optionalrepo = ("paths serve showconfig")
2999
2998
3000 def dispatch(args):
2999 def dispatch(args):
3001 try:
3000 try:
3002 u = ui.ui(traceback='--traceback' in args)
3001 u = ui.ui(traceback='--traceback' in args)
3003 except util.Abort, inst:
3002 except util.Abort, inst:
3004 sys.stderr.write(_("abort: %s\n") % inst)
3003 sys.stderr.write(_("abort: %s\n") % inst)
3005 return -1
3004 return -1
3006 return cmdutil.runcatch(u, args)
3005 return cmdutil.runcatch(u, args)
3007
3006
3008 def run():
3007 def run():
3009 sys.exit(dispatch(sys.argv[1:]))
3008 sys.exit(dispatch(sys.argv[1:]))
@@ -1,1958 +1,1960 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import repo, changegroup
10 import repo, changegroup
11 import changelog, dirstate, filelog, manifest, context
11 import changelog, dirstate, filelog, manifest, context
12 import re, lock, transaction, tempfile, stat, mdiff, errno, ui
12 import re, lock, transaction, tempfile, stat, mdiff, errno, ui
13 import os, revlog, time, util
13 import os, revlog, time, util
14
14
15 class localrepository(repo.repository):
15 class localrepository(repo.repository):
16 capabilities = ('lookup', 'changegroupsubset')
16 capabilities = ('lookup', 'changegroupsubset')
17 supported = ('revlogv1', 'store')
17 supported = ('revlogv1', 'store')
18
18
19 def __del__(self):
19 def __del__(self):
20 self.transhandle = None
20 self.transhandle = None
21 def __init__(self, parentui, path=None, create=0):
21 def __init__(self, parentui, path=None, create=0):
22 repo.repository.__init__(self)
22 repo.repository.__init__(self)
23 self.path = path
23 self.path = path
24 self.root = os.path.realpath(path)
24 self.root = os.path.realpath(path)
25 self.path = os.path.join(self.root, ".hg")
25 self.path = os.path.join(self.root, ".hg")
26 self.origroot = path
26 self.origroot = path
27 self.opener = util.opener(self.path)
27 self.opener = util.opener(self.path)
28 self.wopener = util.opener(self.root)
28 self.wopener = util.opener(self.root)
29
29
30 if not os.path.isdir(self.path):
30 if not os.path.isdir(self.path):
31 if create:
31 if create:
32 if not os.path.exists(path):
32 if not os.path.exists(path):
33 os.mkdir(path)
33 os.mkdir(path)
34 os.mkdir(self.path)
34 os.mkdir(self.path)
35 requirements = ["revlogv1"]
35 requirements = ["revlogv1"]
36 if parentui.configbool('format', 'usestore', True):
36 if parentui.configbool('format', 'usestore', True):
37 os.mkdir(os.path.join(self.path, "store"))
37 os.mkdir(os.path.join(self.path, "store"))
38 requirements.append("store")
38 requirements.append("store")
39 # create an invalid changelog
39 # create an invalid changelog
40 self.opener("00changelog.i", "a").write(
40 self.opener("00changelog.i", "a").write(
41 '\0\0\0\2' # represents revlogv2
41 '\0\0\0\2' # represents revlogv2
42 ' dummy changelog to prevent using the old repo layout'
42 ' dummy changelog to prevent using the old repo layout'
43 )
43 )
44 reqfile = self.opener("requires", "w")
44 reqfile = self.opener("requires", "w")
45 for r in requirements:
45 for r in requirements:
46 reqfile.write("%s\n" % r)
46 reqfile.write("%s\n" % r)
47 reqfile.close()
47 reqfile.close()
48 else:
48 else:
49 raise repo.RepoError(_("repository %s not found") % path)
49 raise repo.RepoError(_("repository %s not found") % path)
50 elif create:
50 elif create:
51 raise repo.RepoError(_("repository %s already exists") % path)
51 raise repo.RepoError(_("repository %s already exists") % path)
52 else:
52 else:
53 # find requirements
53 # find requirements
54 try:
54 try:
55 requirements = self.opener("requires").read().splitlines()
55 requirements = self.opener("requires").read().splitlines()
56 except IOError, inst:
56 except IOError, inst:
57 if inst.errno != errno.ENOENT:
57 if inst.errno != errno.ENOENT:
58 raise
58 raise
59 requirements = []
59 requirements = []
60 # check them
60 # check them
61 for r in requirements:
61 for r in requirements:
62 if r not in self.supported:
62 if r not in self.supported:
63 raise repo.RepoError(_("requirement '%s' not supported") % r)
63 raise repo.RepoError(_("requirement '%s' not supported") % r)
64
64
65 # setup store
65 # setup store
66 if "store" in requirements:
66 if "store" in requirements:
67 self.encodefn = util.encodefilename
67 self.encodefn = util.encodefilename
68 self.decodefn = util.decodefilename
68 self.decodefn = util.decodefilename
69 self.spath = os.path.join(self.path, "store")
69 self.spath = os.path.join(self.path, "store")
70 else:
70 else:
71 self.encodefn = lambda x: x
71 self.encodefn = lambda x: x
72 self.decodefn = lambda x: x
72 self.decodefn = lambda x: x
73 self.spath = self.path
73 self.spath = self.path
74 self.sopener = util.encodedopener(util.opener(self.spath), self.encodefn)
74 self.sopener = util.encodedopener(util.opener(self.spath), self.encodefn)
75
75
76 self.ui = ui.ui(parentui=parentui)
76 self.ui = ui.ui(parentui=parentui)
77 try:
77 try:
78 self.ui.readconfig(self.join("hgrc"), self.root)
78 self.ui.readconfig(self.join("hgrc"), self.root)
79 except IOError:
79 except IOError:
80 pass
80 pass
81
81
82 self.changelog = changelog.changelog(self.sopener)
82 self.changelog = changelog.changelog(self.sopener)
83 self.sopener.defversion = self.changelog.version
83 self.sopener.defversion = self.changelog.version
84 self.manifest = manifest.manifest(self.sopener)
84 self.manifest = manifest.manifest(self.sopener)
85
85
86 fallback = self.ui.config('ui', 'fallbackencoding')
86 fallback = self.ui.config('ui', 'fallbackencoding')
87 if fallback:
87 if fallback:
88 util._fallbackencoding = fallback
88 util._fallbackencoding = fallback
89
89
90 self.tagscache = None
90 self.tagscache = None
91 self.branchcache = None
91 self.branchcache = None
92 self.nodetagscache = None
92 self.nodetagscache = None
93 self.filterpats = {}
93 self.filterpats = {}
94 self.transhandle = None
94 self.transhandle = None
95
95
96 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
96 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
97
97
98 def url(self):
98 def url(self):
99 return 'file:' + self.root
99 return 'file:' + self.root
100
100
101 def hook(self, name, throw=False, **args):
101 def hook(self, name, throw=False, **args):
102 def callhook(hname, funcname):
102 def callhook(hname, funcname):
103 '''call python hook. hook is callable object, looked up as
103 '''call python hook. hook is callable object, looked up as
104 name in python module. if callable returns "true", hook
104 name in python module. if callable returns "true", hook
105 fails, else passes. if hook raises exception, treated as
105 fails, else passes. if hook raises exception, treated as
106 hook failure. exception propagates if throw is "true".
106 hook failure. exception propagates if throw is "true".
107
107
108 reason for "true" meaning "hook failed" is so that
108 reason for "true" meaning "hook failed" is so that
109 unmodified commands (e.g. mercurial.commands.update) can
109 unmodified commands (e.g. mercurial.commands.update) can
110 be run as hooks without wrappers to convert return values.'''
110 be run as hooks without wrappers to convert return values.'''
111
111
112 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
112 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
113 obj = funcname
113 obj = funcname
114 if not callable(obj):
114 if not callable(obj):
115 d = funcname.rfind('.')
115 d = funcname.rfind('.')
116 if d == -1:
116 if d == -1:
117 raise util.Abort(_('%s hook is invalid ("%s" not in '
117 raise util.Abort(_('%s hook is invalid ("%s" not in '
118 'a module)') % (hname, funcname))
118 'a module)') % (hname, funcname))
119 modname = funcname[:d]
119 modname = funcname[:d]
120 try:
120 try:
121 obj = __import__(modname)
121 obj = __import__(modname)
122 except ImportError:
122 except ImportError:
123 try:
123 try:
124 # extensions are loaded with hgext_ prefix
124 # extensions are loaded with hgext_ prefix
125 obj = __import__("hgext_%s" % modname)
125 obj = __import__("hgext_%s" % modname)
126 except ImportError:
126 except ImportError:
127 raise util.Abort(_('%s hook is invalid '
127 raise util.Abort(_('%s hook is invalid '
128 '(import of "%s" failed)') %
128 '(import of "%s" failed)') %
129 (hname, modname))
129 (hname, modname))
130 try:
130 try:
131 for p in funcname.split('.')[1:]:
131 for p in funcname.split('.')[1:]:
132 obj = getattr(obj, p)
132 obj = getattr(obj, p)
133 except AttributeError, err:
133 except AttributeError, err:
134 raise util.Abort(_('%s hook is invalid '
134 raise util.Abort(_('%s hook is invalid '
135 '("%s" is not defined)') %
135 '("%s" is not defined)') %
136 (hname, funcname))
136 (hname, funcname))
137 if not callable(obj):
137 if not callable(obj):
138 raise util.Abort(_('%s hook is invalid '
138 raise util.Abort(_('%s hook is invalid '
139 '("%s" is not callable)') %
139 '("%s" is not callable)') %
140 (hname, funcname))
140 (hname, funcname))
141 try:
141 try:
142 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
142 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
143 except (KeyboardInterrupt, util.SignalInterrupt):
143 except (KeyboardInterrupt, util.SignalInterrupt):
144 raise
144 raise
145 except Exception, exc:
145 except Exception, exc:
146 if isinstance(exc, util.Abort):
146 if isinstance(exc, util.Abort):
147 self.ui.warn(_('error: %s hook failed: %s\n') %
147 self.ui.warn(_('error: %s hook failed: %s\n') %
148 (hname, exc.args[0]))
148 (hname, exc.args[0]))
149 else:
149 else:
150 self.ui.warn(_('error: %s hook raised an exception: '
150 self.ui.warn(_('error: %s hook raised an exception: '
151 '%s\n') % (hname, exc))
151 '%s\n') % (hname, exc))
152 if throw:
152 if throw:
153 raise
153 raise
154 self.ui.print_exc()
154 self.ui.print_exc()
155 return True
155 return True
156 if r:
156 if r:
157 if throw:
157 if throw:
158 raise util.Abort(_('%s hook failed') % hname)
158 raise util.Abort(_('%s hook failed') % hname)
159 self.ui.warn(_('warning: %s hook failed\n') % hname)
159 self.ui.warn(_('warning: %s hook failed\n') % hname)
160 return r
160 return r
161
161
162 def runhook(name, cmd):
162 def runhook(name, cmd):
163 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
163 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
164 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
164 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
165 r = util.system(cmd, environ=env, cwd=self.root)
165 r = util.system(cmd, environ=env, cwd=self.root)
166 if r:
166 if r:
167 desc, r = util.explain_exit(r)
167 desc, r = util.explain_exit(r)
168 if throw:
168 if throw:
169 raise util.Abort(_('%s hook %s') % (name, desc))
169 raise util.Abort(_('%s hook %s') % (name, desc))
170 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
170 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
171 return r
171 return r
172
172
173 r = False
173 r = False
174 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
174 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
175 if hname.split(".", 1)[0] == name and cmd]
175 if hname.split(".", 1)[0] == name and cmd]
176 hooks.sort()
176 hooks.sort()
177 for hname, cmd in hooks:
177 for hname, cmd in hooks:
178 if callable(cmd):
178 if callable(cmd):
179 r = callhook(hname, cmd) or r
179 r = callhook(hname, cmd) or r
180 elif cmd.startswith('python:'):
180 elif cmd.startswith('python:'):
181 r = callhook(hname, cmd[7:].strip()) or r
181 r = callhook(hname, cmd[7:].strip()) or r
182 else:
182 else:
183 r = runhook(hname, cmd) or r
183 r = runhook(hname, cmd) or r
184 return r
184 return r
185
185
186 tag_disallowed = ':\r\n'
186 tag_disallowed = ':\r\n'
187
187
188 def _tag(self, name, node, message, local, user, date, parent=None):
188 def _tag(self, name, node, message, local, user, date, parent=None):
189 use_dirstate = parent is None
189 use_dirstate = parent is None
190
190
191 for c in self.tag_disallowed:
191 for c in self.tag_disallowed:
192 if c in name:
192 if c in name:
193 raise util.Abort(_('%r cannot be used in a tag name') % c)
193 raise util.Abort(_('%r cannot be used in a tag name') % c)
194
194
195 self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
195 self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
196
196
197 if local:
197 if local:
198 # local tags are stored in the current charset
198 # local tags are stored in the current charset
199 self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name))
199 self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name))
200 self.hook('tag', node=hex(node), tag=name, local=local)
200 self.hook('tag', node=hex(node), tag=name, local=local)
201 return
201 return
202
202
203 # committed tags are stored in UTF-8
203 # committed tags are stored in UTF-8
204 line = '%s %s\n' % (hex(node), util.fromlocal(name))
204 line = '%s %s\n' % (hex(node), util.fromlocal(name))
205 if use_dirstate:
205 if use_dirstate:
206 self.wfile('.hgtags', 'ab').write(line)
206 self.wfile('.hgtags', 'ab').write(line)
207 else:
207 else:
208 ntags = self.filectx('.hgtags', parent).data()
208 ntags = self.filectx('.hgtags', parent).data()
209 self.wfile('.hgtags', 'ab').write(ntags + line)
209 self.wfile('.hgtags', 'ab').write(ntags + line)
210 if use_dirstate and self.dirstate.state('.hgtags') == '?':
210 if use_dirstate and self.dirstate.state('.hgtags') == '?':
211 self.add(['.hgtags'])
211 self.add(['.hgtags'])
212
212
213 tagnode = self.commit(['.hgtags'], message, user, date, p1=parent)
213 tagnode = self.commit(['.hgtags'], message, user, date, p1=parent)
214
214
215 self.hook('tag', node=hex(node), tag=name, local=local)
215 self.hook('tag', node=hex(node), tag=name, local=local)
216
216
217 return tagnode
217 return tagnode
218
218
219 def tag(self, name, node, message, local, user, date):
219 def tag(self, name, node, message, local, user, date):
220 '''tag a revision with a symbolic name.
220 '''tag a revision with a symbolic name.
221
221
222 if local is True, the tag is stored in a per-repository file.
222 if local is True, the tag is stored in a per-repository file.
223 otherwise, it is stored in the .hgtags file, and a new
223 otherwise, it is stored in the .hgtags file, and a new
224 changeset is committed with the change.
224 changeset is committed with the change.
225
225
226 keyword arguments:
226 keyword arguments:
227
227
228 local: whether to store tag in non-version-controlled file
228 local: whether to store tag in non-version-controlled file
229 (default False)
229 (default False)
230
230
231 message: commit message to use if committing
231 message: commit message to use if committing
232
232
233 user: name of user to use if committing
233 user: name of user to use if committing
234
234
235 date: date tuple to use if committing'''
235 date: date tuple to use if committing'''
236
236
237 for x in self.status()[:5]:
237 for x in self.status()[:5]:
238 if '.hgtags' in x:
238 if '.hgtags' in x:
239 raise util.Abort(_('working copy of .hgtags is changed '
239 raise util.Abort(_('working copy of .hgtags is changed '
240 '(please commit .hgtags manually)'))
240 '(please commit .hgtags manually)'))
241
241
242
242
243 self._tag(name, node, message, local, user, date)
243 self._tag(name, node, message, local, user, date)
244
244
245 def tags(self):
245 def tags(self):
246 '''return a mapping of tag to node'''
246 '''return a mapping of tag to node'''
247 if self.tagscache:
247 if self.tagscache:
248 return self.tagscache
248 return self.tagscache
249
249
250 globaltags = {}
250 globaltags = {}
251
251
252 def readtags(lines, fn):
252 def readtags(lines, fn):
253 filetags = {}
253 filetags = {}
254 count = 0
254 count = 0
255
255
256 def warn(msg):
256 def warn(msg):
257 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
257 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
258
258
259 for l in lines:
259 for l in lines:
260 count += 1
260 count += 1
261 if not l:
261 if not l:
262 continue
262 continue
263 s = l.split(" ", 1)
263 s = l.split(" ", 1)
264 if len(s) != 2:
264 if len(s) != 2:
265 warn(_("cannot parse entry"))
265 warn(_("cannot parse entry"))
266 continue
266 continue
267 node, key = s
267 node, key = s
268 key = util.tolocal(key.strip()) # stored in UTF-8
268 key = util.tolocal(key.strip()) # stored in UTF-8
269 try:
269 try:
270 bin_n = bin(node)
270 bin_n = bin(node)
271 except TypeError:
271 except TypeError:
272 warn(_("node '%s' is not well formed") % node)
272 warn(_("node '%s' is not well formed") % node)
273 continue
273 continue
274 if bin_n not in self.changelog.nodemap:
274 if bin_n not in self.changelog.nodemap:
275 warn(_("tag '%s' refers to unknown node") % key)
275 warn(_("tag '%s' refers to unknown node") % key)
276 continue
276 continue
277
277
278 h = []
278 h = []
279 if key in filetags:
279 if key in filetags:
280 n, h = filetags[key]
280 n, h = filetags[key]
281 h.append(n)
281 h.append(n)
282 filetags[key] = (bin_n, h)
282 filetags[key] = (bin_n, h)
283
283
284 for k,nh in filetags.items():
284 for k,nh in filetags.items():
285 if k not in globaltags:
285 if k not in globaltags:
286 globaltags[k] = nh
286 globaltags[k] = nh
287 continue
287 continue
288 # we prefer the global tag if:
288 # we prefer the global tag if:
289 # it supercedes us OR
289 # it supercedes us OR
290 # mutual supercedes and it has a higher rank
290 # mutual supercedes and it has a higher rank
291 # otherwise we win because we're tip-most
291 # otherwise we win because we're tip-most
292 an, ah = nh
292 an, ah = nh
293 bn, bh = globaltags[k]
293 bn, bh = globaltags[k]
294 if bn != an and an in bh and \
294 if bn != an and an in bh and \
295 (bn not in ah or len(bh) > len(ah)):
295 (bn not in ah or len(bh) > len(ah)):
296 an = bn
296 an = bn
297 ah.extend([n for n in bh if n not in ah])
297 ah.extend([n for n in bh if n not in ah])
298 globaltags[k] = an, ah
298 globaltags[k] = an, ah
299
299
300 # read the tags file from each head, ending with the tip
300 # read the tags file from each head, ending with the tip
301 f = None
301 f = None
302 for rev, node, fnode in self._hgtagsnodes():
302 for rev, node, fnode in self._hgtagsnodes():
303 f = (f and f.filectx(fnode) or
303 f = (f and f.filectx(fnode) or
304 self.filectx('.hgtags', fileid=fnode))
304 self.filectx('.hgtags', fileid=fnode))
305 readtags(f.data().splitlines(), f)
305 readtags(f.data().splitlines(), f)
306
306
307 try:
307 try:
308 data = util.fromlocal(self.opener("localtags").read())
308 data = util.fromlocal(self.opener("localtags").read())
309 # localtags are stored in the local character set
309 # localtags are stored in the local character set
310 # while the internal tag table is stored in UTF-8
310 # while the internal tag table is stored in UTF-8
311 readtags(data.splitlines(), "localtags")
311 readtags(data.splitlines(), "localtags")
312 except IOError:
312 except IOError:
313 pass
313 pass
314
314
315 self.tagscache = {}
315 self.tagscache = {}
316 for k,nh in globaltags.items():
316 for k,nh in globaltags.items():
317 n = nh[0]
317 n = nh[0]
318 if n != nullid:
318 if n != nullid:
319 self.tagscache[k] = n
319 self.tagscache[k] = n
320 self.tagscache['tip'] = self.changelog.tip()
320 self.tagscache['tip'] = self.changelog.tip()
321
321
322 return self.tagscache
322 return self.tagscache
323
323
324 def _hgtagsnodes(self):
324 def _hgtagsnodes(self):
325 heads = self.heads()
325 heads = self.heads()
326 heads.reverse()
326 heads.reverse()
327 last = {}
327 last = {}
328 ret = []
328 ret = []
329 for node in heads:
329 for node in heads:
330 c = self.changectx(node)
330 c = self.changectx(node)
331 rev = c.rev()
331 rev = c.rev()
332 try:
332 try:
333 fnode = c.filenode('.hgtags')
333 fnode = c.filenode('.hgtags')
334 except revlog.LookupError:
334 except revlog.LookupError:
335 continue
335 continue
336 ret.append((rev, node, fnode))
336 ret.append((rev, node, fnode))
337 if fnode in last:
337 if fnode in last:
338 ret[last[fnode]] = None
338 ret[last[fnode]] = None
339 last[fnode] = len(ret) - 1
339 last[fnode] = len(ret) - 1
340 return [item for item in ret if item]
340 return [item for item in ret if item]
341
341
342 def tagslist(self):
342 def tagslist(self):
343 '''return a list of tags ordered by revision'''
343 '''return a list of tags ordered by revision'''
344 l = []
344 l = []
345 for t, n in self.tags().items():
345 for t, n in self.tags().items():
346 try:
346 try:
347 r = self.changelog.rev(n)
347 r = self.changelog.rev(n)
348 except:
348 except:
349 r = -2 # sort to the beginning of the list if unknown
349 r = -2 # sort to the beginning of the list if unknown
350 l.append((r, t, n))
350 l.append((r, t, n))
351 l.sort()
351 l.sort()
352 return [(t, n) for r, t, n in l]
352 return [(t, n) for r, t, n in l]
353
353
354 def nodetags(self, node):
354 def nodetags(self, node):
355 '''return the tags associated with a node'''
355 '''return the tags associated with a node'''
356 if not self.nodetagscache:
356 if not self.nodetagscache:
357 self.nodetagscache = {}
357 self.nodetagscache = {}
358 for t, n in self.tags().items():
358 for t, n in self.tags().items():
359 self.nodetagscache.setdefault(n, []).append(t)
359 self.nodetagscache.setdefault(n, []).append(t)
360 return self.nodetagscache.get(node, [])
360 return self.nodetagscache.get(node, [])
361
361
362 def _branchtags(self):
362 def _branchtags(self):
363 partial, last, lrev = self._readbranchcache()
363 partial, last, lrev = self._readbranchcache()
364
364
365 tiprev = self.changelog.count() - 1
365 tiprev = self.changelog.count() - 1
366 if lrev != tiprev:
366 if lrev != tiprev:
367 self._updatebranchcache(partial, lrev+1, tiprev+1)
367 self._updatebranchcache(partial, lrev+1, tiprev+1)
368 self._writebranchcache(partial, self.changelog.tip(), tiprev)
368 self._writebranchcache(partial, self.changelog.tip(), tiprev)
369
369
370 return partial
370 return partial
371
371
372 def branchtags(self):
372 def branchtags(self):
373 if self.branchcache is not None:
373 if self.branchcache is not None:
374 return self.branchcache
374 return self.branchcache
375
375
376 self.branchcache = {} # avoid recursion in changectx
376 self.branchcache = {} # avoid recursion in changectx
377 partial = self._branchtags()
377 partial = self._branchtags()
378
378
379 # the branch cache is stored on disk as UTF-8, but in the local
379 # the branch cache is stored on disk as UTF-8, but in the local
380 # charset internally
380 # charset internally
381 for k, v in partial.items():
381 for k, v in partial.items():
382 self.branchcache[util.tolocal(k)] = v
382 self.branchcache[util.tolocal(k)] = v
383 return self.branchcache
383 return self.branchcache
384
384
385 def _readbranchcache(self):
385 def _readbranchcache(self):
386 partial = {}
386 partial = {}
387 try:
387 try:
388 f = self.opener("branch.cache")
388 f = self.opener("branch.cache")
389 lines = f.read().split('\n')
389 lines = f.read().split('\n')
390 f.close()
390 f.close()
391 except (IOError, OSError):
391 except (IOError, OSError):
392 return {}, nullid, nullrev
392 return {}, nullid, nullrev
393
393
394 try:
394 try:
395 last, lrev = lines.pop(0).split(" ", 1)
395 last, lrev = lines.pop(0).split(" ", 1)
396 last, lrev = bin(last), int(lrev)
396 last, lrev = bin(last), int(lrev)
397 if not (lrev < self.changelog.count() and
397 if not (lrev < self.changelog.count() and
398 self.changelog.node(lrev) == last): # sanity check
398 self.changelog.node(lrev) == last): # sanity check
399 # invalidate the cache
399 # invalidate the cache
400 raise ValueError('Invalid branch cache: unknown tip')
400 raise ValueError('Invalid branch cache: unknown tip')
401 for l in lines:
401 for l in lines:
402 if not l: continue
402 if not l: continue
403 node, label = l.split(" ", 1)
403 node, label = l.split(" ", 1)
404 partial[label.strip()] = bin(node)
404 partial[label.strip()] = bin(node)
405 except (KeyboardInterrupt, util.SignalInterrupt):
405 except (KeyboardInterrupt, util.SignalInterrupt):
406 raise
406 raise
407 except Exception, inst:
407 except Exception, inst:
408 if self.ui.debugflag:
408 if self.ui.debugflag:
409 self.ui.warn(str(inst), '\n')
409 self.ui.warn(str(inst), '\n')
410 partial, last, lrev = {}, nullid, nullrev
410 partial, last, lrev = {}, nullid, nullrev
411 return partial, last, lrev
411 return partial, last, lrev
412
412
413 def _writebranchcache(self, branches, tip, tiprev):
413 def _writebranchcache(self, branches, tip, tiprev):
414 try:
414 try:
415 f = self.opener("branch.cache", "w", atomictemp=True)
415 f = self.opener("branch.cache", "w", atomictemp=True)
416 f.write("%s %s\n" % (hex(tip), tiprev))
416 f.write("%s %s\n" % (hex(tip), tiprev))
417 for label, node in branches.iteritems():
417 for label, node in branches.iteritems():
418 f.write("%s %s\n" % (hex(node), label))
418 f.write("%s %s\n" % (hex(node), label))
419 f.rename()
419 f.rename()
420 except (IOError, OSError):
420 except (IOError, OSError):
421 pass
421 pass
422
422
423 def _updatebranchcache(self, partial, start, end):
423 def _updatebranchcache(self, partial, start, end):
424 for r in xrange(start, end):
424 for r in xrange(start, end):
425 c = self.changectx(r)
425 c = self.changectx(r)
426 b = c.branch()
426 b = c.branch()
427 partial[b] = c.node()
427 partial[b] = c.node()
428
428
429 def lookup(self, key):
429 def lookup(self, key):
430 if key == '.':
430 if key == '.':
431 key, second = self.dirstate.parents()
431 key, second = self.dirstate.parents()
432 if key == nullid:
432 if key == nullid:
433 raise repo.RepoError(_("no revision checked out"))
433 raise repo.RepoError(_("no revision checked out"))
434 if second != nullid:
434 if second != nullid:
435 self.ui.warn(_("warning: working directory has two parents, "
435 self.ui.warn(_("warning: working directory has two parents, "
436 "tag '.' uses the first\n"))
436 "tag '.' uses the first\n"))
437 elif key == 'null':
437 elif key == 'null':
438 return nullid
438 return nullid
439 n = self.changelog._match(key)
439 n = self.changelog._match(key)
440 if n:
440 if n:
441 return n
441 return n
442 if key in self.tags():
442 if key in self.tags():
443 return self.tags()[key]
443 return self.tags()[key]
444 if key in self.branchtags():
444 if key in self.branchtags():
445 return self.branchtags()[key]
445 return self.branchtags()[key]
446 n = self.changelog._partialmatch(key)
446 n = self.changelog._partialmatch(key)
447 if n:
447 if n:
448 return n
448 return n
449 raise repo.RepoError(_("unknown revision '%s'") % key)
449 raise repo.RepoError(_("unknown revision '%s'") % key)
450
450
451 def dev(self):
451 def dev(self):
452 return os.lstat(self.path).st_dev
452 return os.lstat(self.path).st_dev
453
453
454 def local(self):
454 def local(self):
455 return True
455 return True
456
456
457 def join(self, f):
457 def join(self, f):
458 return os.path.join(self.path, f)
458 return os.path.join(self.path, f)
459
459
460 def sjoin(self, f):
460 def sjoin(self, f):
461 f = self.encodefn(f)
461 f = self.encodefn(f)
462 return os.path.join(self.spath, f)
462 return os.path.join(self.spath, f)
463
463
464 def wjoin(self, f):
464 def wjoin(self, f):
465 return os.path.join(self.root, f)
465 return os.path.join(self.root, f)
466
466
467 def file(self, f):
467 def file(self, f):
468 if f[0] == '/':
468 if f[0] == '/':
469 f = f[1:]
469 f = f[1:]
470 return filelog.filelog(self.sopener, f)
470 return filelog.filelog(self.sopener, f)
471
471
472 def changectx(self, changeid=None):
472 def changectx(self, changeid=None):
473 return context.changectx(self, changeid)
473 return context.changectx(self, changeid)
474
474
475 def workingctx(self):
475 def workingctx(self):
476 return context.workingctx(self)
476 return context.workingctx(self)
477
477
478 def parents(self, changeid=None):
478 def parents(self, changeid=None):
479 '''
479 '''
480 get list of changectxs for parents of changeid or working directory
480 get list of changectxs for parents of changeid or working directory
481 '''
481 '''
482 if changeid is None:
482 if changeid is None:
483 pl = self.dirstate.parents()
483 pl = self.dirstate.parents()
484 else:
484 else:
485 n = self.changelog.lookup(changeid)
485 n = self.changelog.lookup(changeid)
486 pl = self.changelog.parents(n)
486 pl = self.changelog.parents(n)
487 if pl[1] == nullid:
487 if pl[1] == nullid:
488 return [self.changectx(pl[0])]
488 return [self.changectx(pl[0])]
489 return [self.changectx(pl[0]), self.changectx(pl[1])]
489 return [self.changectx(pl[0]), self.changectx(pl[1])]
490
490
491 def filectx(self, path, changeid=None, fileid=None):
491 def filectx(self, path, changeid=None, fileid=None):
492 """changeid can be a changeset revision, node, or tag.
492 """changeid can be a changeset revision, node, or tag.
493 fileid can be a file revision or node."""
493 fileid can be a file revision or node."""
494 return context.filectx(self, path, changeid, fileid)
494 return context.filectx(self, path, changeid, fileid)
495
495
496 def getcwd(self):
496 def getcwd(self):
497 return self.dirstate.getcwd()
497 return self.dirstate.getcwd()
498
498
499 def pathto(self, f, cwd=None):
499 def pathto(self, f, cwd=None):
500 return self.dirstate.pathto(f, cwd)
500 return self.dirstate.pathto(f, cwd)
501
501
502 def wfile(self, f, mode='r'):
502 def wfile(self, f, mode='r'):
503 return self.wopener(f, mode)
503 return self.wopener(f, mode)
504
504
505 def _link(self, f):
505 def _link(self, f):
506 return os.path.islink(self.wjoin(f))
506 return os.path.islink(self.wjoin(f))
507
507
508 def _filter(self, filter, filename, data):
508 def _filter(self, filter, filename, data):
509 if filter not in self.filterpats:
509 if filter not in self.filterpats:
510 l = []
510 l = []
511 for pat, cmd in self.ui.configitems(filter):
511 for pat, cmd in self.ui.configitems(filter):
512 mf = util.matcher(self.root, "", [pat], [], [])[1]
512 mf = util.matcher(self.root, "", [pat], [], [])[1]
513 l.append((mf, cmd))
513 l.append((mf, cmd))
514 self.filterpats[filter] = l
514 self.filterpats[filter] = l
515
515
516 for mf, cmd in self.filterpats[filter]:
516 for mf, cmd in self.filterpats[filter]:
517 if mf(filename):
517 if mf(filename):
518 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
518 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
519 data = util.filter(data, cmd)
519 data = util.filter(data, cmd)
520 break
520 break
521
521
522 return data
522 return data
523
523
524 def wread(self, filename):
524 def wread(self, filename):
525 if self._link(filename):
525 if self._link(filename):
526 data = os.readlink(self.wjoin(filename))
526 data = os.readlink(self.wjoin(filename))
527 else:
527 else:
528 data = self.wopener(filename, 'r').read()
528 data = self.wopener(filename, 'r').read()
529 return self._filter("encode", filename, data)
529 return self._filter("encode", filename, data)
530
530
531 def wwrite(self, filename, data, flags):
531 def wwrite(self, filename, data, flags):
532 data = self._filter("decode", filename, data)
532 data = self._filter("decode", filename, data)
533 if "l" in flags:
533 if "l" in flags:
534 f = self.wjoin(filename)
534 f = self.wjoin(filename)
535 try:
535 try:
536 os.unlink(f)
536 os.unlink(f)
537 except OSError:
537 except OSError:
538 pass
538 pass
539 d = os.path.dirname(f)
539 d = os.path.dirname(f)
540 if not os.path.exists(d):
540 if not os.path.exists(d):
541 os.makedirs(d)
541 os.makedirs(d)
542 os.symlink(data, f)
542 os.symlink(data, f)
543 else:
543 else:
544 try:
544 try:
545 if self._link(filename):
545 if self._link(filename):
546 os.unlink(self.wjoin(filename))
546 os.unlink(self.wjoin(filename))
547 except OSError:
547 except OSError:
548 pass
548 pass
549 self.wopener(filename, 'w').write(data)
549 self.wopener(filename, 'w').write(data)
550 util.set_exec(self.wjoin(filename), "x" in flags)
550 util.set_exec(self.wjoin(filename), "x" in flags)
551
551
552 def wwritedata(self, filename, data):
552 def wwritedata(self, filename, data):
553 return self._filter("decode", filename, data)
553 return self._filter("decode", filename, data)
554
554
555 def transaction(self):
555 def transaction(self):
556 tr = self.transhandle
556 tr = self.transhandle
557 if tr != None and tr.running():
557 if tr != None and tr.running():
558 return tr.nest()
558 return tr.nest()
559
559
560 # save dirstate for rollback
560 # save dirstate for rollback
561 try:
561 try:
562 ds = self.opener("dirstate").read()
562 ds = self.opener("dirstate").read()
563 except IOError:
563 except IOError:
564 ds = ""
564 ds = ""
565 self.opener("journal.dirstate", "w").write(ds)
565 self.opener("journal.dirstate", "w").write(ds)
566
566
567 renames = [(self.sjoin("journal"), self.sjoin("undo")),
567 renames = [(self.sjoin("journal"), self.sjoin("undo")),
568 (self.join("journal.dirstate"), self.join("undo.dirstate"))]
568 (self.join("journal.dirstate"), self.join("undo.dirstate"))]
569 tr = transaction.transaction(self.ui.warn, self.sopener,
569 tr = transaction.transaction(self.ui.warn, self.sopener,
570 self.sjoin("journal"),
570 self.sjoin("journal"),
571 aftertrans(renames))
571 aftertrans(renames))
572 self.transhandle = tr
572 self.transhandle = tr
573 return tr
573 return tr
574
574
575 def recover(self):
575 def recover(self):
576 l = self.lock()
576 l = self.lock()
577 if os.path.exists(self.sjoin("journal")):
577 if os.path.exists(self.sjoin("journal")):
578 self.ui.status(_("rolling back interrupted transaction\n"))
578 self.ui.status(_("rolling back interrupted transaction\n"))
579 transaction.rollback(self.sopener, self.sjoin("journal"))
579 transaction.rollback(self.sopener, self.sjoin("journal"))
580 self.reload()
580 self.reload()
581 return True
581 return True
582 else:
582 else:
583 self.ui.warn(_("no interrupted transaction available\n"))
583 self.ui.warn(_("no interrupted transaction available\n"))
584 return False
584 return False
585
585
586 def rollback(self, wlock=None, lock=None):
586 def rollback(self, wlock=None, lock=None):
587 if not wlock:
587 if not wlock:
588 wlock = self.wlock()
588 wlock = self.wlock()
589 if not lock:
589 if not lock:
590 lock = self.lock()
590 lock = self.lock()
591 if os.path.exists(self.sjoin("undo")):
591 if os.path.exists(self.sjoin("undo")):
592 self.ui.status(_("rolling back last transaction\n"))
592 self.ui.status(_("rolling back last transaction\n"))
593 transaction.rollback(self.sopener, self.sjoin("undo"))
593 transaction.rollback(self.sopener, self.sjoin("undo"))
594 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
594 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
595 self.reload()
595 self.reload()
596 self.wreload()
596 self.wreload()
597 else:
597 else:
598 self.ui.warn(_("no rollback information available\n"))
598 self.ui.warn(_("no rollback information available\n"))
599
599
600 def wreload(self):
600 def wreload(self):
601 self.dirstate.reload()
601 self.dirstate.reload()
602
602
603 def reload(self):
603 def reload(self):
604 self.changelog.load()
604 self.changelog.load()
605 self.manifest.load()
605 self.manifest.load()
606 self.tagscache = None
606 self.tagscache = None
607 self.nodetagscache = None
607 self.nodetagscache = None
608
608
609 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
609 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
610 desc=None):
610 desc=None):
611 try:
611 try:
612 l = lock.lock(lockname, 0, releasefn, desc=desc)
612 l = lock.lock(lockname, 0, releasefn, desc=desc)
613 except lock.LockHeld, inst:
613 except lock.LockHeld, inst:
614 if not wait:
614 if not wait:
615 raise
615 raise
616 self.ui.warn(_("waiting for lock on %s held by %r\n") %
616 self.ui.warn(_("waiting for lock on %s held by %r\n") %
617 (desc, inst.locker))
617 (desc, inst.locker))
618 # default to 600 seconds timeout
618 # default to 600 seconds timeout
619 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
619 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
620 releasefn, desc=desc)
620 releasefn, desc=desc)
621 if acquirefn:
621 if acquirefn:
622 acquirefn()
622 acquirefn()
623 return l
623 return l
624
624
625 def lock(self, wait=1):
625 def lock(self, wait=1):
626 return self.do_lock(self.sjoin("lock"), wait, acquirefn=self.reload,
626 return self.do_lock(self.sjoin("lock"), wait, acquirefn=self.reload,
627 desc=_('repository %s') % self.origroot)
627 desc=_('repository %s') % self.origroot)
628
628
629 def wlock(self, wait=1):
629 def wlock(self, wait=1):
630 return self.do_lock(self.join("wlock"), wait, self.dirstate.write,
630 return self.do_lock(self.join("wlock"), wait, self.dirstate.write,
631 self.wreload,
631 self.wreload,
632 desc=_('working directory of %s') % self.origroot)
632 desc=_('working directory of %s') % self.origroot)
633
633
634 def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist):
634 def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist):
635 """
635 """
636 commit an individual file as part of a larger transaction
636 commit an individual file as part of a larger transaction
637 """
637 """
638
638
639 t = self.wread(fn)
639 t = self.wread(fn)
640 fl = self.file(fn)
640 fl = self.file(fn)
641 fp1 = manifest1.get(fn, nullid)
641 fp1 = manifest1.get(fn, nullid)
642 fp2 = manifest2.get(fn, nullid)
642 fp2 = manifest2.get(fn, nullid)
643
643
644 meta = {}
644 meta = {}
645 cp = self.dirstate.copied(fn)
645 cp = self.dirstate.copied(fn)
646 if cp:
646 if cp:
647 # Mark the new revision of this file as a copy of another
647 # Mark the new revision of this file as a copy of another
648 # file. This copy data will effectively act as a parent
648 # file. This copy data will effectively act as a parent
649 # of this new revision. If this is a merge, the first
649 # of this new revision. If this is a merge, the first
650 # parent will be the nullid (meaning "look up the copy data")
650 # parent will be the nullid (meaning "look up the copy data")
651 # and the second one will be the other parent. For example:
651 # and the second one will be the other parent. For example:
652 #
652 #
653 # 0 --- 1 --- 3 rev1 changes file foo
653 # 0 --- 1 --- 3 rev1 changes file foo
654 # \ / rev2 renames foo to bar and changes it
654 # \ / rev2 renames foo to bar and changes it
655 # \- 2 -/ rev3 should have bar with all changes and
655 # \- 2 -/ rev3 should have bar with all changes and
656 # should record that bar descends from
656 # should record that bar descends from
657 # bar in rev2 and foo in rev1
657 # bar in rev2 and foo in rev1
658 #
658 #
659 # this allows this merge to succeed:
659 # this allows this merge to succeed:
660 #
660 #
661 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
661 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
662 # \ / merging rev3 and rev4 should use bar@rev2
662 # \ / merging rev3 and rev4 should use bar@rev2
663 # \- 2 --- 4 as the merge base
663 # \- 2 --- 4 as the merge base
664 #
664 #
665 meta["copy"] = cp
665 meta["copy"] = cp
666 if not manifest2: # not a branch merge
666 if not manifest2: # not a branch merge
667 meta["copyrev"] = hex(manifest1.get(cp, nullid))
667 meta["copyrev"] = hex(manifest1.get(cp, nullid))
668 fp2 = nullid
668 fp2 = nullid
669 elif fp2 != nullid: # copied on remote side
669 elif fp2 != nullid: # copied on remote side
670 meta["copyrev"] = hex(manifest1.get(cp, nullid))
670 meta["copyrev"] = hex(manifest1.get(cp, nullid))
671 elif fp1 != nullid: # copied on local side, reversed
671 elif fp1 != nullid: # copied on local side, reversed
672 meta["copyrev"] = hex(manifest2.get(cp))
672 meta["copyrev"] = hex(manifest2.get(cp))
673 fp2 = fp1
673 fp2 = fp1
674 else: # directory rename
674 else: # directory rename
675 meta["copyrev"] = hex(manifest1.get(cp, nullid))
675 meta["copyrev"] = hex(manifest1.get(cp, nullid))
676 self.ui.debug(_(" %s: copy %s:%s\n") %
676 self.ui.debug(_(" %s: copy %s:%s\n") %
677 (fn, cp, meta["copyrev"]))
677 (fn, cp, meta["copyrev"]))
678 fp1 = nullid
678 fp1 = nullid
679 elif fp2 != nullid:
679 elif fp2 != nullid:
680 # is one parent an ancestor of the other?
680 # is one parent an ancestor of the other?
681 fpa = fl.ancestor(fp1, fp2)
681 fpa = fl.ancestor(fp1, fp2)
682 if fpa == fp1:
682 if fpa == fp1:
683 fp1, fp2 = fp2, nullid
683 fp1, fp2 = fp2, nullid
684 elif fpa == fp2:
684 elif fpa == fp2:
685 fp2 = nullid
685 fp2 = nullid
686
686
687 # is the file unmodified from the parent? report existing entry
687 # is the file unmodified from the parent? report existing entry
688 if fp2 == nullid and not fl.cmp(fp1, t):
688 if fp2 == nullid and not fl.cmp(fp1, t):
689 return fp1
689 return fp1
690
690
691 changelist.append(fn)
691 changelist.append(fn)
692 return fl.add(t, meta, transaction, linkrev, fp1, fp2)
692 return fl.add(t, meta, transaction, linkrev, fp1, fp2)
693
693
694 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None, extra={}):
694 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None, extra={}):
695 if p1 is None:
695 if p1 is None:
696 p1, p2 = self.dirstate.parents()
696 p1, p2 = self.dirstate.parents()
697 return self.commit(files=files, text=text, user=user, date=date,
697 return self.commit(files=files, text=text, user=user, date=date,
698 p1=p1, p2=p2, wlock=wlock, extra=extra)
698 p1=p1, p2=p2, wlock=wlock, extra=extra)
699
699
700 def commit(self, files=None, text="", user=None, date=None,
700 def commit(self, files=None, text="", user=None, date=None,
701 match=util.always, force=False, lock=None, wlock=None,
701 match=util.always, force=False, lock=None, wlock=None,
702 force_editor=False, p1=None, p2=None, extra={}):
702 force_editor=False, p1=None, p2=None, extra={}):
703
703
704 commit = []
704 commit = []
705 remove = []
705 remove = []
706 changed = []
706 changed = []
707 use_dirstate = (p1 is None) # not rawcommit
707 use_dirstate = (p1 is None) # not rawcommit
708 extra = extra.copy()
708 extra = extra.copy()
709
709
710 if use_dirstate:
710 if use_dirstate:
711 if files:
711 if files:
712 for f in files:
712 for f in files:
713 s = self.dirstate.state(f)
713 s = self.dirstate.state(f)
714 if s in 'nmai':
714 if s in 'nmai':
715 commit.append(f)
715 commit.append(f)
716 elif s == 'r':
716 elif s == 'r':
717 remove.append(f)
717 remove.append(f)
718 else:
718 else:
719 self.ui.warn(_("%s not tracked!\n") % f)
719 self.ui.warn(_("%s not tracked!\n") % f)
720 else:
720 else:
721 changes = self.status(match=match)[:5]
721 changes = self.status(match=match)[:5]
722 modified, added, removed, deleted, unknown = changes
722 modified, added, removed, deleted, unknown = changes
723 commit = modified + added
723 commit = modified + added
724 remove = removed
724 remove = removed
725 else:
725 else:
726 commit = files
726 commit = files
727
727
728 if use_dirstate:
728 if use_dirstate:
729 p1, p2 = self.dirstate.parents()
729 p1, p2 = self.dirstate.parents()
730 update_dirstate = True
730 update_dirstate = True
731 else:
731 else:
732 p1, p2 = p1, p2 or nullid
732 p1, p2 = p1, p2 or nullid
733 update_dirstate = (self.dirstate.parents()[0] == p1)
733 update_dirstate = (self.dirstate.parents()[0] == p1)
734
734
735 c1 = self.changelog.read(p1)
735 c1 = self.changelog.read(p1)
736 c2 = self.changelog.read(p2)
736 c2 = self.changelog.read(p2)
737 m1 = self.manifest.read(c1[0]).copy()
737 m1 = self.manifest.read(c1[0]).copy()
738 m2 = self.manifest.read(c2[0])
738 m2 = self.manifest.read(c2[0])
739
739
740 if use_dirstate:
740 if use_dirstate:
741 branchname = self.workingctx().branch()
741 branchname = self.workingctx().branch()
742 try:
742 try:
743 branchname = branchname.decode('UTF-8').encode('UTF-8')
743 branchname = branchname.decode('UTF-8').encode('UTF-8')
744 except UnicodeDecodeError:
744 except UnicodeDecodeError:
745 raise util.Abort(_('branch name not in UTF-8!'))
745 raise util.Abort(_('branch name not in UTF-8!'))
746 else:
746 else:
747 branchname = ""
747 branchname = ""
748
748
749 if use_dirstate:
749 if use_dirstate:
750 oldname = c1[5].get("branch") # stored in UTF-8
750 oldname = c1[5].get("branch") # stored in UTF-8
751 if not commit and not remove and not force and p2 == nullid and \
751 if not commit and not remove and not force and p2 == nullid and \
752 branchname == oldname:
752 branchname == oldname:
753 self.ui.status(_("nothing changed\n"))
753 self.ui.status(_("nothing changed\n"))
754 return None
754 return None
755
755
756 xp1 = hex(p1)
756 xp1 = hex(p1)
757 if p2 == nullid: xp2 = ''
757 if p2 == nullid: xp2 = ''
758 else: xp2 = hex(p2)
758 else: xp2 = hex(p2)
759
759
760 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
760 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
761
761
762 if not wlock:
762 if not wlock:
763 wlock = self.wlock()
763 wlock = self.wlock()
764 if not lock:
764 if not lock:
765 lock = self.lock()
765 lock = self.lock()
766 tr = self.transaction()
766 tr = self.transaction()
767
767
768 # check in files
768 # check in files
769 new = {}
769 new = {}
770 linkrev = self.changelog.count()
770 linkrev = self.changelog.count()
771 commit.sort()
771 commit.sort()
772 is_exec = util.execfunc(self.root, m1.execf)
772 is_exec = util.execfunc(self.root, m1.execf)
773 is_link = util.linkfunc(self.root, m1.linkf)
773 is_link = util.linkfunc(self.root, m1.linkf)
774 for f in commit:
774 for f in commit:
775 self.ui.note(f + "\n")
775 self.ui.note(f + "\n")
776 try:
776 try:
777 new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
777 new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
778 new_exec = is_exec(f)
778 new_exec = is_exec(f)
779 new_link = is_link(f)
779 new_link = is_link(f)
780 if not changed or changed[-1] != f:
780 if not changed or changed[-1] != f:
781 # mention the file in the changelog if some flag changed,
781 # mention the file in the changelog if some flag changed,
782 # even if there was no content change.
782 # even if there was no content change.
783 old_exec = m1.execf(f)
783 old_exec = m1.execf(f)
784 old_link = m1.linkf(f)
784 old_link = m1.linkf(f)
785 if old_exec != new_exec or old_link != new_link:
785 if old_exec != new_exec or old_link != new_link:
786 changed.append(f)
786 changed.append(f)
787 m1.set(f, new_exec, new_link)
787 m1.set(f, new_exec, new_link)
788 except (OSError, IOError):
788 except (OSError, IOError):
789 if use_dirstate:
789 if use_dirstate:
790 self.ui.warn(_("trouble committing %s!\n") % f)
790 self.ui.warn(_("trouble committing %s!\n") % f)
791 raise
791 raise
792 else:
792 else:
793 remove.append(f)
793 remove.append(f)
794
794
795 # update manifest
795 # update manifest
796 m1.update(new)
796 m1.update(new)
797 remove.sort()
797 remove.sort()
798 removed = []
798 removed = []
799
799
800 for f in remove:
800 for f in remove:
801 if f in m1:
801 if f in m1:
802 del m1[f]
802 del m1[f]
803 removed.append(f)
803 removed.append(f)
804 elif f in m2:
804 elif f in m2:
805 removed.append(f)
805 removed.append(f)
806 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed))
806 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed))
807
807
808 # add changeset
808 # add changeset
809 new = new.keys()
809 new = new.keys()
810 new.sort()
810 new.sort()
811
811
812 user = user or self.ui.username()
812 user = user or self.ui.username()
813 if not text or force_editor:
813 if not text or force_editor:
814 edittext = []
814 edittext = []
815 if text:
815 if text:
816 edittext.append(text)
816 edittext.append(text)
817 edittext.append("")
817 edittext.append("")
818 edittext.append("HG: user: %s" % user)
818 edittext.append("HG: user: %s" % user)
819 if p2 != nullid:
819 if p2 != nullid:
820 edittext.append("HG: branch merge")
820 edittext.append("HG: branch merge")
821 if branchname:
821 if branchname:
822 edittext.append("HG: branch %s" % util.tolocal(branchname))
822 edittext.append("HG: branch %s" % util.tolocal(branchname))
823 edittext.extend(["HG: changed %s" % f for f in changed])
823 edittext.extend(["HG: changed %s" % f for f in changed])
824 edittext.extend(["HG: removed %s" % f for f in removed])
824 edittext.extend(["HG: removed %s" % f for f in removed])
825 if not changed and not remove:
825 if not changed and not remove:
826 edittext.append("HG: no files changed")
826 edittext.append("HG: no files changed")
827 edittext.append("")
827 edittext.append("")
828 # run editor in the repository root
828 # run editor in the repository root
829 olddir = os.getcwd()
829 olddir = os.getcwd()
830 os.chdir(self.root)
830 os.chdir(self.root)
831 text = self.ui.edit("\n".join(edittext), user)
831 text = self.ui.edit("\n".join(edittext), user)
832 os.chdir(olddir)
832 os.chdir(olddir)
833
833
834 lines = [line.rstrip() for line in text.rstrip().splitlines()]
834 lines = [line.rstrip() for line in text.rstrip().splitlines()]
835 while lines and not lines[0]:
835 while lines and not lines[0]:
836 del lines[0]
836 del lines[0]
837 if not lines:
837 if not lines:
838 return None
838 return None
839 text = '\n'.join(lines)
839 text = '\n'.join(lines)
840 if branchname:
840 if branchname:
841 extra["branch"] = branchname
841 extra["branch"] = branchname
842 n = self.changelog.add(mn, changed + removed, text, tr, p1, p2,
842 n = self.changelog.add(mn, changed + removed, text, tr, p1, p2,
843 user, date, extra)
843 user, date, extra)
844 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
844 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
845 parent2=xp2)
845 parent2=xp2)
846 tr.close()
846 tr.close()
847
847
848 if self.branchcache and "branch" in extra:
848 if self.branchcache and "branch" in extra:
849 self.branchcache[util.tolocal(extra["branch"])] = n
849 self.branchcache[util.tolocal(extra["branch"])] = n
850
850
851 if use_dirstate or update_dirstate:
851 if use_dirstate or update_dirstate:
852 self.dirstate.setparents(n)
852 self.dirstate.setparents(n)
853 if use_dirstate:
853 if use_dirstate:
854 self.dirstate.update(new, "n")
854 self.dirstate.update(new, "n")
855 self.dirstate.forget(removed)
855 self.dirstate.forget(removed)
856
856
857 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
857 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
858 return n
858 return n
859
859
860 def walk(self, node=None, files=[], match=util.always, badmatch=None):
860 def walk(self, node=None, files=[], match=util.always, badmatch=None):
861 '''
861 '''
862 walk recursively through the directory tree or a given
862 walk recursively through the directory tree or a given
863 changeset, finding all files matched by the match
863 changeset, finding all files matched by the match
864 function
864 function
865
865
866 results are yielded in a tuple (src, filename), where src
866 results are yielded in a tuple (src, filename), where src
867 is one of:
867 is one of:
868 'f' the file was found in the directory tree
868 'f' the file was found in the directory tree
869 'm' the file was only in the dirstate and not in the tree
869 'm' the file was only in the dirstate and not in the tree
870 'b' file was not found and matched badmatch
870 'b' file was not found and matched badmatch
871 '''
871 '''
872
872
873 if node:
873 if node:
874 fdict = dict.fromkeys(files)
874 fdict = dict.fromkeys(files)
875 # for dirstate.walk, files=['.'] means "walk the whole tree".
875 # for dirstate.walk, files=['.'] means "walk the whole tree".
876 # follow that here, too
876 # follow that here, too
877 fdict.pop('.', None)
877 fdict.pop('.', None)
878 mdict = self.manifest.read(self.changelog.read(node)[0])
878 mdict = self.manifest.read(self.changelog.read(node)[0])
879 mfiles = mdict.keys()
879 mfiles = mdict.keys()
880 mfiles.sort()
880 mfiles.sort()
881 for fn in mfiles:
881 for fn in mfiles:
882 for ffn in fdict:
882 for ffn in fdict:
883 # match if the file is the exact name or a directory
883 # match if the file is the exact name or a directory
884 if ffn == fn or fn.startswith("%s/" % ffn):
884 if ffn == fn or fn.startswith("%s/" % ffn):
885 del fdict[ffn]
885 del fdict[ffn]
886 break
886 break
887 if match(fn):
887 if match(fn):
888 yield 'm', fn
888 yield 'm', fn
889 ffiles = fdict.keys()
889 ffiles = fdict.keys()
890 ffiles.sort()
890 ffiles.sort()
891 for fn in ffiles:
891 for fn in ffiles:
892 if badmatch and badmatch(fn):
892 if badmatch and badmatch(fn):
893 if match(fn):
893 if match(fn):
894 yield 'b', fn
894 yield 'b', fn
895 else:
895 else:
896 self.ui.warn(_('%s: No such file in rev %s\n')
896 self.ui.warn(_('%s: No such file in rev %s\n')
897 % (self.pathto(fn), short(node)))
897 % (self.pathto(fn), short(node)))
898 else:
898 else:
899 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
899 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
900 yield src, fn
900 yield src, fn
901
901
902 def status(self, node1=None, node2=None, files=[], match=util.always,
902 def status(self, node1=None, node2=None, files=[], match=util.always,
903 wlock=None, list_ignored=False, list_clean=False):
903 wlock=None, list_ignored=False, list_clean=False):
904 """return status of files between two nodes or node and working directory
904 """return status of files between two nodes or node and working directory
905
905
906 If node1 is None, use the first dirstate parent instead.
906 If node1 is None, use the first dirstate parent instead.
907 If node2 is None, compare node1 with working directory.
907 If node2 is None, compare node1 with working directory.
908 """
908 """
909
909
910 def fcmp(fn, getnode):
910 def fcmp(fn, getnode):
911 t1 = self.wread(fn)
911 t1 = self.wread(fn)
912 return self.file(fn).cmp(getnode(fn), t1)
912 return self.file(fn).cmp(getnode(fn), t1)
913
913
914 def mfmatches(node):
914 def mfmatches(node):
915 change = self.changelog.read(node)
915 change = self.changelog.read(node)
916 mf = self.manifest.read(change[0]).copy()
916 mf = self.manifest.read(change[0]).copy()
917 for fn in mf.keys():
917 for fn in mf.keys():
918 if not match(fn):
918 if not match(fn):
919 del mf[fn]
919 del mf[fn]
920 return mf
920 return mf
921
921
922 modified, added, removed, deleted, unknown = [], [], [], [], []
922 modified, added, removed, deleted, unknown = [], [], [], [], []
923 ignored, clean = [], []
923 ignored, clean = [], []
924
924
925 compareworking = False
925 compareworking = False
926 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
926 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
927 compareworking = True
927 compareworking = True
928
928
929 if not compareworking:
929 if not compareworking:
930 # read the manifest from node1 before the manifest from node2,
930 # read the manifest from node1 before the manifest from node2,
931 # so that we'll hit the manifest cache if we're going through
931 # so that we'll hit the manifest cache if we're going through
932 # all the revisions in parent->child order.
932 # all the revisions in parent->child order.
933 mf1 = mfmatches(node1)
933 mf1 = mfmatches(node1)
934
934
935 mywlock = False
935 mywlock = False
936
936
937 # are we comparing the working directory?
937 # are we comparing the working directory?
938 if not node2:
938 if not node2:
939 (lookup, modified, added, removed, deleted, unknown,
939 (lookup, modified, added, removed, deleted, unknown,
940 ignored, clean) = self.dirstate.status(files, match,
940 ignored, clean) = self.dirstate.status(files, match,
941 list_ignored, list_clean)
941 list_ignored, list_clean)
942
942
943 # are we comparing working dir against its parent?
943 # are we comparing working dir against its parent?
944 if compareworking:
944 if compareworking:
945 if lookup:
945 if lookup:
946 # do a full compare of any files that might have changed
946 # do a full compare of any files that might have changed
947 mnode = self.changelog.read(self.dirstate.parents()[0])[0]
947 mnode = self.changelog.read(self.dirstate.parents()[0])[0]
948 getnode = lambda fn: (self.manifest.find(mnode, fn)[0] or
948 getnode = lambda fn: (self.manifest.find(mnode, fn)[0] or
949 nullid)
949 nullid)
950 for f in lookup:
950 for f in lookup:
951 if fcmp(f, getnode):
951 if fcmp(f, getnode):
952 modified.append(f)
952 modified.append(f)
953 else:
953 else:
954 clean.append(f)
954 if list_clean:
955 clean.append(f)
955 if not wlock and not mywlock:
956 if not wlock and not mywlock:
956 mywlock = True
957 mywlock = True
957 try:
958 try:
958 wlock = self.wlock(wait=0)
959 wlock = self.wlock(wait=0)
959 except lock.LockException:
960 except lock.LockException:
960 pass
961 pass
961 if wlock:
962 if wlock:
962 self.dirstate.update([f], "n")
963 self.dirstate.update([f], "n")
963 else:
964 else:
964 # we are comparing working dir against non-parent
965 # we are comparing working dir against non-parent
965 # generate a pseudo-manifest for the working dir
966 # generate a pseudo-manifest for the working dir
966 # XXX: create it in dirstate.py ?
967 # XXX: create it in dirstate.py ?
967 mf2 = mfmatches(self.dirstate.parents()[0])
968 mf2 = mfmatches(self.dirstate.parents()[0])
968 is_exec = util.execfunc(self.root, mf2.execf)
969 is_exec = util.execfunc(self.root, mf2.execf)
969 is_link = util.linkfunc(self.root, mf2.linkf)
970 is_link = util.linkfunc(self.root, mf2.linkf)
970 for f in lookup + modified + added:
971 for f in lookup + modified + added:
971 mf2[f] = ""
972 mf2[f] = ""
972 mf2.set(f, is_exec(f), is_link(f))
973 mf2.set(f, is_exec(f), is_link(f))
973 for f in removed:
974 for f in removed:
974 if f in mf2:
975 if f in mf2:
975 del mf2[f]
976 del mf2[f]
976
977
977 if mywlock and wlock:
978 if mywlock and wlock:
978 wlock.release()
979 wlock.release()
979 else:
980 else:
980 # we are comparing two revisions
981 # we are comparing two revisions
981 mf2 = mfmatches(node2)
982 mf2 = mfmatches(node2)
982
983
983 if not compareworking:
984 if not compareworking:
984 # flush lists from dirstate before comparing manifests
985 # flush lists from dirstate before comparing manifests
985 modified, added, clean = [], [], []
986 modified, added, clean = [], [], []
986
987
987 # make sure to sort the files so we talk to the disk in a
988 # make sure to sort the files so we talk to the disk in a
988 # reasonable order
989 # reasonable order
989 mf2keys = mf2.keys()
990 mf2keys = mf2.keys()
990 mf2keys.sort()
991 mf2keys.sort()
991 getnode = lambda fn: mf1.get(fn, nullid)
992 getnode = lambda fn: mf1.get(fn, nullid)
992 for fn in mf2keys:
993 for fn in mf2keys:
993 if mf1.has_key(fn):
994 if mf1.has_key(fn):
994 if mf1.flags(fn) != mf2.flags(fn) or \
995 if mf1.flags(fn) != mf2.flags(fn) or \
995 (mf1[fn] != mf2[fn] and (mf2[fn] != "" or
996 (mf1[fn] != mf2[fn] and (mf2[fn] != "" or
996 fcmp(fn, getnode))):
997 fcmp(fn, getnode))):
997 modified.append(fn)
998 modified.append(fn)
998 elif list_clean:
999 elif list_clean:
999 clean.append(fn)
1000 clean.append(fn)
1000 del mf1[fn]
1001 del mf1[fn]
1001 else:
1002 else:
1002 added.append(fn)
1003 added.append(fn)
1003
1004
1004 removed = mf1.keys()
1005 removed = mf1.keys()
1005
1006
1006 # sort and return results:
1007 # sort and return results:
1007 for l in modified, added, removed, deleted, unknown, ignored, clean:
1008 for l in modified, added, removed, deleted, unknown, ignored, clean:
1008 l.sort()
1009 l.sort()
1009 return (modified, added, removed, deleted, unknown, ignored, clean)
1010 return (modified, added, removed, deleted, unknown, ignored, clean)
1010
1011
1011 def add(self, list, wlock=None):
1012 def add(self, list, wlock=None):
1012 if not wlock:
1013 if not wlock:
1013 wlock = self.wlock()
1014 wlock = self.wlock()
1014 for f in list:
1015 for f in list:
1015 p = self.wjoin(f)
1016 p = self.wjoin(f)
1016 islink = os.path.islink(p)
1017 try:
1017 size = os.lstat(p).st_size
1018 st = os.lstat(p)
1018 if size > 10000000:
1019 except:
1020 self.ui.warn(_("%s does not exist!\n") % f)
1021 continue
1022 if st.st_size > 10000000:
1019 self.ui.warn(_("%s: files over 10MB may cause memory and"
1023 self.ui.warn(_("%s: files over 10MB may cause memory and"
1020 " performance problems\n"
1024 " performance problems\n"
1021 "(use 'hg revert %s' to unadd the file)\n")
1025 "(use 'hg revert %s' to unadd the file)\n")
1022 % (f, f))
1026 % (f, f))
1023 if not islink and not os.path.exists(p):
1027 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1024 self.ui.warn(_("%s does not exist!\n") % f)
1025 elif not islink and not os.path.isfile(p):
1026 self.ui.warn(_("%s not added: only files and symlinks "
1028 self.ui.warn(_("%s not added: only files and symlinks "
1027 "supported currently\n") % f)
1029 "supported currently\n") % f)
1028 elif self.dirstate.state(f) in 'an':
1030 elif self.dirstate.state(f) in 'an':
1029 self.ui.warn(_("%s already tracked!\n") % f)
1031 self.ui.warn(_("%s already tracked!\n") % f)
1030 else:
1032 else:
1031 self.dirstate.update([f], "a")
1033 self.dirstate.update([f], "a")
1032
1034
1033 def forget(self, list, wlock=None):
1035 def forget(self, list, wlock=None):
1034 if not wlock:
1036 if not wlock:
1035 wlock = self.wlock()
1037 wlock = self.wlock()
1036 for f in list:
1038 for f in list:
1037 if self.dirstate.state(f) not in 'ai':
1039 if self.dirstate.state(f) not in 'ai':
1038 self.ui.warn(_("%s not added!\n") % f)
1040 self.ui.warn(_("%s not added!\n") % f)
1039 else:
1041 else:
1040 self.dirstate.forget([f])
1042 self.dirstate.forget([f])
1041
1043
1042 def remove(self, list, unlink=False, wlock=None):
1044 def remove(self, list, unlink=False, wlock=None):
1043 if unlink:
1045 if unlink:
1044 for f in list:
1046 for f in list:
1045 try:
1047 try:
1046 util.unlink(self.wjoin(f))
1048 util.unlink(self.wjoin(f))
1047 except OSError, inst:
1049 except OSError, inst:
1048 if inst.errno != errno.ENOENT:
1050 if inst.errno != errno.ENOENT:
1049 raise
1051 raise
1050 if not wlock:
1052 if not wlock:
1051 wlock = self.wlock()
1053 wlock = self.wlock()
1052 for f in list:
1054 for f in list:
1053 if unlink and os.path.exists(self.wjoin(f)):
1055 if unlink and os.path.exists(self.wjoin(f)):
1054 self.ui.warn(_("%s still exists!\n") % f)
1056 self.ui.warn(_("%s still exists!\n") % f)
1055 elif self.dirstate.state(f) == 'a':
1057 elif self.dirstate.state(f) == 'a':
1056 self.dirstate.forget([f])
1058 self.dirstate.forget([f])
1057 elif f not in self.dirstate:
1059 elif f not in self.dirstate:
1058 self.ui.warn(_("%s not tracked!\n") % f)
1060 self.ui.warn(_("%s not tracked!\n") % f)
1059 else:
1061 else:
1060 self.dirstate.update([f], "r")
1062 self.dirstate.update([f], "r")
1061
1063
1062 def undelete(self, list, wlock=None):
1064 def undelete(self, list, wlock=None):
1063 p = self.dirstate.parents()[0]
1065 p = self.dirstate.parents()[0]
1064 mn = self.changelog.read(p)[0]
1066 mn = self.changelog.read(p)[0]
1065 m = self.manifest.read(mn)
1067 m = self.manifest.read(mn)
1066 if not wlock:
1068 if not wlock:
1067 wlock = self.wlock()
1069 wlock = self.wlock()
1068 for f in list:
1070 for f in list:
1069 if self.dirstate.state(f) not in "r":
1071 if self.dirstate.state(f) not in "r":
1070 self.ui.warn("%s not removed!\n" % f)
1072 self.ui.warn("%s not removed!\n" % f)
1071 else:
1073 else:
1072 t = self.file(f).read(m[f])
1074 t = self.file(f).read(m[f])
1073 self.wwrite(f, t, m.flags(f))
1075 self.wwrite(f, t, m.flags(f))
1074 self.dirstate.update([f], "n")
1076 self.dirstate.update([f], "n")
1075
1077
1076 def copy(self, source, dest, wlock=None):
1078 def copy(self, source, dest, wlock=None):
1077 p = self.wjoin(dest)
1079 p = self.wjoin(dest)
1078 if not (os.path.exists(p) or os.path.islink(p)):
1080 if not (os.path.exists(p) or os.path.islink(p)):
1079 self.ui.warn(_("%s does not exist!\n") % dest)
1081 self.ui.warn(_("%s does not exist!\n") % dest)
1080 elif not (os.path.isfile(p) or os.path.islink(p)):
1082 elif not (os.path.isfile(p) or os.path.islink(p)):
1081 self.ui.warn(_("copy failed: %s is not a file or a "
1083 self.ui.warn(_("copy failed: %s is not a file or a "
1082 "symbolic link\n") % dest)
1084 "symbolic link\n") % dest)
1083 else:
1085 else:
1084 if not wlock:
1086 if not wlock:
1085 wlock = self.wlock()
1087 wlock = self.wlock()
1086 if self.dirstate.state(dest) == '?':
1088 if self.dirstate.state(dest) == '?':
1087 self.dirstate.update([dest], "a")
1089 self.dirstate.update([dest], "a")
1088 self.dirstate.copy(source, dest)
1090 self.dirstate.copy(source, dest)
1089
1091
1090 def heads(self, start=None):
1092 def heads(self, start=None):
1091 heads = self.changelog.heads(start)
1093 heads = self.changelog.heads(start)
1092 # sort the output in rev descending order
1094 # sort the output in rev descending order
1093 heads = [(-self.changelog.rev(h), h) for h in heads]
1095 heads = [(-self.changelog.rev(h), h) for h in heads]
1094 heads.sort()
1096 heads.sort()
1095 return [n for (r, n) in heads]
1097 return [n for (r, n) in heads]
1096
1098
1097 def branches(self, nodes):
1099 def branches(self, nodes):
1098 if not nodes:
1100 if not nodes:
1099 nodes = [self.changelog.tip()]
1101 nodes = [self.changelog.tip()]
1100 b = []
1102 b = []
1101 for n in nodes:
1103 for n in nodes:
1102 t = n
1104 t = n
1103 while 1:
1105 while 1:
1104 p = self.changelog.parents(n)
1106 p = self.changelog.parents(n)
1105 if p[1] != nullid or p[0] == nullid:
1107 if p[1] != nullid or p[0] == nullid:
1106 b.append((t, n, p[0], p[1]))
1108 b.append((t, n, p[0], p[1]))
1107 break
1109 break
1108 n = p[0]
1110 n = p[0]
1109 return b
1111 return b
1110
1112
1111 def between(self, pairs):
1113 def between(self, pairs):
1112 r = []
1114 r = []
1113
1115
1114 for top, bottom in pairs:
1116 for top, bottom in pairs:
1115 n, l, i = top, [], 0
1117 n, l, i = top, [], 0
1116 f = 1
1118 f = 1
1117
1119
1118 while n != bottom:
1120 while n != bottom:
1119 p = self.changelog.parents(n)[0]
1121 p = self.changelog.parents(n)[0]
1120 if i == f:
1122 if i == f:
1121 l.append(n)
1123 l.append(n)
1122 f = f * 2
1124 f = f * 2
1123 n = p
1125 n = p
1124 i += 1
1126 i += 1
1125
1127
1126 r.append(l)
1128 r.append(l)
1127
1129
1128 return r
1130 return r
1129
1131
1130 def findincoming(self, remote, base=None, heads=None, force=False):
1132 def findincoming(self, remote, base=None, heads=None, force=False):
1131 """Return list of roots of the subsets of missing nodes from remote
1133 """Return list of roots of the subsets of missing nodes from remote
1132
1134
1133 If base dict is specified, assume that these nodes and their parents
1135 If base dict is specified, assume that these nodes and their parents
1134 exist on the remote side and that no child of a node of base exists
1136 exist on the remote side and that no child of a node of base exists
1135 in both remote and self.
1137 in both remote and self.
1136 Furthermore base will be updated to include the nodes that exists
1138 Furthermore base will be updated to include the nodes that exists
1137 in self and remote but no children exists in self and remote.
1139 in self and remote but no children exists in self and remote.
1138 If a list of heads is specified, return only nodes which are heads
1140 If a list of heads is specified, return only nodes which are heads
1139 or ancestors of these heads.
1141 or ancestors of these heads.
1140
1142
1141 All the ancestors of base are in self and in remote.
1143 All the ancestors of base are in self and in remote.
1142 All the descendants of the list returned are missing in self.
1144 All the descendants of the list returned are missing in self.
1143 (and so we know that the rest of the nodes are missing in remote, see
1145 (and so we know that the rest of the nodes are missing in remote, see
1144 outgoing)
1146 outgoing)
1145 """
1147 """
1146 m = self.changelog.nodemap
1148 m = self.changelog.nodemap
1147 search = []
1149 search = []
1148 fetch = {}
1150 fetch = {}
1149 seen = {}
1151 seen = {}
1150 seenbranch = {}
1152 seenbranch = {}
1151 if base == None:
1153 if base == None:
1152 base = {}
1154 base = {}
1153
1155
1154 if not heads:
1156 if not heads:
1155 heads = remote.heads()
1157 heads = remote.heads()
1156
1158
1157 if self.changelog.tip() == nullid:
1159 if self.changelog.tip() == nullid:
1158 base[nullid] = 1
1160 base[nullid] = 1
1159 if heads != [nullid]:
1161 if heads != [nullid]:
1160 return [nullid]
1162 return [nullid]
1161 return []
1163 return []
1162
1164
1163 # assume we're closer to the tip than the root
1165 # assume we're closer to the tip than the root
1164 # and start by examining the heads
1166 # and start by examining the heads
1165 self.ui.status(_("searching for changes\n"))
1167 self.ui.status(_("searching for changes\n"))
1166
1168
1167 unknown = []
1169 unknown = []
1168 for h in heads:
1170 for h in heads:
1169 if h not in m:
1171 if h not in m:
1170 unknown.append(h)
1172 unknown.append(h)
1171 else:
1173 else:
1172 base[h] = 1
1174 base[h] = 1
1173
1175
1174 if not unknown:
1176 if not unknown:
1175 return []
1177 return []
1176
1178
1177 req = dict.fromkeys(unknown)
1179 req = dict.fromkeys(unknown)
1178 reqcnt = 0
1180 reqcnt = 0
1179
1181
1180 # search through remote branches
1182 # search through remote branches
1181 # a 'branch' here is a linear segment of history, with four parts:
1183 # a 'branch' here is a linear segment of history, with four parts:
1182 # head, root, first parent, second parent
1184 # head, root, first parent, second parent
1183 # (a branch always has two parents (or none) by definition)
1185 # (a branch always has two parents (or none) by definition)
1184 unknown = remote.branches(unknown)
1186 unknown = remote.branches(unknown)
1185 while unknown:
1187 while unknown:
1186 r = []
1188 r = []
1187 while unknown:
1189 while unknown:
1188 n = unknown.pop(0)
1190 n = unknown.pop(0)
1189 if n[0] in seen:
1191 if n[0] in seen:
1190 continue
1192 continue
1191
1193
1192 self.ui.debug(_("examining %s:%s\n")
1194 self.ui.debug(_("examining %s:%s\n")
1193 % (short(n[0]), short(n[1])))
1195 % (short(n[0]), short(n[1])))
1194 if n[0] == nullid: # found the end of the branch
1196 if n[0] == nullid: # found the end of the branch
1195 pass
1197 pass
1196 elif n in seenbranch:
1198 elif n in seenbranch:
1197 self.ui.debug(_("branch already found\n"))
1199 self.ui.debug(_("branch already found\n"))
1198 continue
1200 continue
1199 elif n[1] and n[1] in m: # do we know the base?
1201 elif n[1] and n[1] in m: # do we know the base?
1200 self.ui.debug(_("found incomplete branch %s:%s\n")
1202 self.ui.debug(_("found incomplete branch %s:%s\n")
1201 % (short(n[0]), short(n[1])))
1203 % (short(n[0]), short(n[1])))
1202 search.append(n) # schedule branch range for scanning
1204 search.append(n) # schedule branch range for scanning
1203 seenbranch[n] = 1
1205 seenbranch[n] = 1
1204 else:
1206 else:
1205 if n[1] not in seen and n[1] not in fetch:
1207 if n[1] not in seen and n[1] not in fetch:
1206 if n[2] in m and n[3] in m:
1208 if n[2] in m and n[3] in m:
1207 self.ui.debug(_("found new changeset %s\n") %
1209 self.ui.debug(_("found new changeset %s\n") %
1208 short(n[1]))
1210 short(n[1]))
1209 fetch[n[1]] = 1 # earliest unknown
1211 fetch[n[1]] = 1 # earliest unknown
1210 for p in n[2:4]:
1212 for p in n[2:4]:
1211 if p in m:
1213 if p in m:
1212 base[p] = 1 # latest known
1214 base[p] = 1 # latest known
1213
1215
1214 for p in n[2:4]:
1216 for p in n[2:4]:
1215 if p not in req and p not in m:
1217 if p not in req and p not in m:
1216 r.append(p)
1218 r.append(p)
1217 req[p] = 1
1219 req[p] = 1
1218 seen[n[0]] = 1
1220 seen[n[0]] = 1
1219
1221
1220 if r:
1222 if r:
1221 reqcnt += 1
1223 reqcnt += 1
1222 self.ui.debug(_("request %d: %s\n") %
1224 self.ui.debug(_("request %d: %s\n") %
1223 (reqcnt, " ".join(map(short, r))))
1225 (reqcnt, " ".join(map(short, r))))
1224 for p in xrange(0, len(r), 10):
1226 for p in xrange(0, len(r), 10):
1225 for b in remote.branches(r[p:p+10]):
1227 for b in remote.branches(r[p:p+10]):
1226 self.ui.debug(_("received %s:%s\n") %
1228 self.ui.debug(_("received %s:%s\n") %
1227 (short(b[0]), short(b[1])))
1229 (short(b[0]), short(b[1])))
1228 unknown.append(b)
1230 unknown.append(b)
1229
1231
1230 # do binary search on the branches we found
1232 # do binary search on the branches we found
1231 while search:
1233 while search:
1232 n = search.pop(0)
1234 n = search.pop(0)
1233 reqcnt += 1
1235 reqcnt += 1
1234 l = remote.between([(n[0], n[1])])[0]
1236 l = remote.between([(n[0], n[1])])[0]
1235 l.append(n[1])
1237 l.append(n[1])
1236 p = n[0]
1238 p = n[0]
1237 f = 1
1239 f = 1
1238 for i in l:
1240 for i in l:
1239 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1241 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1240 if i in m:
1242 if i in m:
1241 if f <= 2:
1243 if f <= 2:
1242 self.ui.debug(_("found new branch changeset %s\n") %
1244 self.ui.debug(_("found new branch changeset %s\n") %
1243 short(p))
1245 short(p))
1244 fetch[p] = 1
1246 fetch[p] = 1
1245 base[i] = 1
1247 base[i] = 1
1246 else:
1248 else:
1247 self.ui.debug(_("narrowed branch search to %s:%s\n")
1249 self.ui.debug(_("narrowed branch search to %s:%s\n")
1248 % (short(p), short(i)))
1250 % (short(p), short(i)))
1249 search.append((p, i))
1251 search.append((p, i))
1250 break
1252 break
1251 p, f = i, f * 2
1253 p, f = i, f * 2
1252
1254
1253 # sanity check our fetch list
1255 # sanity check our fetch list
1254 for f in fetch.keys():
1256 for f in fetch.keys():
1255 if f in m:
1257 if f in m:
1256 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1258 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1257
1259
1258 if base.keys() == [nullid]:
1260 if base.keys() == [nullid]:
1259 if force:
1261 if force:
1260 self.ui.warn(_("warning: repository is unrelated\n"))
1262 self.ui.warn(_("warning: repository is unrelated\n"))
1261 else:
1263 else:
1262 raise util.Abort(_("repository is unrelated"))
1264 raise util.Abort(_("repository is unrelated"))
1263
1265
1264 self.ui.debug(_("found new changesets starting at ") +
1266 self.ui.debug(_("found new changesets starting at ") +
1265 " ".join([short(f) for f in fetch]) + "\n")
1267 " ".join([short(f) for f in fetch]) + "\n")
1266
1268
1267 self.ui.debug(_("%d total queries\n") % reqcnt)
1269 self.ui.debug(_("%d total queries\n") % reqcnt)
1268
1270
1269 return fetch.keys()
1271 return fetch.keys()
1270
1272
1271 def findoutgoing(self, remote, base=None, heads=None, force=False):
1273 def findoutgoing(self, remote, base=None, heads=None, force=False):
1272 """Return list of nodes that are roots of subsets not in remote
1274 """Return list of nodes that are roots of subsets not in remote
1273
1275
1274 If base dict is specified, assume that these nodes and their parents
1276 If base dict is specified, assume that these nodes and their parents
1275 exist on the remote side.
1277 exist on the remote side.
1276 If a list of heads is specified, return only nodes which are heads
1278 If a list of heads is specified, return only nodes which are heads
1277 or ancestors of these heads, and return a second element which
1279 or ancestors of these heads, and return a second element which
1278 contains all remote heads which get new children.
1280 contains all remote heads which get new children.
1279 """
1281 """
1280 if base == None:
1282 if base == None:
1281 base = {}
1283 base = {}
1282 self.findincoming(remote, base, heads, force=force)
1284 self.findincoming(remote, base, heads, force=force)
1283
1285
1284 self.ui.debug(_("common changesets up to ")
1286 self.ui.debug(_("common changesets up to ")
1285 + " ".join(map(short, base.keys())) + "\n")
1287 + " ".join(map(short, base.keys())) + "\n")
1286
1288
1287 remain = dict.fromkeys(self.changelog.nodemap)
1289 remain = dict.fromkeys(self.changelog.nodemap)
1288
1290
1289 # prune everything remote has from the tree
1291 # prune everything remote has from the tree
1290 del remain[nullid]
1292 del remain[nullid]
1291 remove = base.keys()
1293 remove = base.keys()
1292 while remove:
1294 while remove:
1293 n = remove.pop(0)
1295 n = remove.pop(0)
1294 if n in remain:
1296 if n in remain:
1295 del remain[n]
1297 del remain[n]
1296 for p in self.changelog.parents(n):
1298 for p in self.changelog.parents(n):
1297 remove.append(p)
1299 remove.append(p)
1298
1300
1299 # find every node whose parents have been pruned
1301 # find every node whose parents have been pruned
1300 subset = []
1302 subset = []
1301 # find every remote head that will get new children
1303 # find every remote head that will get new children
1302 updated_heads = {}
1304 updated_heads = {}
1303 for n in remain:
1305 for n in remain:
1304 p1, p2 = self.changelog.parents(n)
1306 p1, p2 = self.changelog.parents(n)
1305 if p1 not in remain and p2 not in remain:
1307 if p1 not in remain and p2 not in remain:
1306 subset.append(n)
1308 subset.append(n)
1307 if heads:
1309 if heads:
1308 if p1 in heads:
1310 if p1 in heads:
1309 updated_heads[p1] = True
1311 updated_heads[p1] = True
1310 if p2 in heads:
1312 if p2 in heads:
1311 updated_heads[p2] = True
1313 updated_heads[p2] = True
1312
1314
1313 # this is the set of all roots we have to push
1315 # this is the set of all roots we have to push
1314 if heads:
1316 if heads:
1315 return subset, updated_heads.keys()
1317 return subset, updated_heads.keys()
1316 else:
1318 else:
1317 return subset
1319 return subset
1318
1320
1319 def pull(self, remote, heads=None, force=False, lock=None):
1321 def pull(self, remote, heads=None, force=False, lock=None):
1320 mylock = False
1322 mylock = False
1321 if not lock:
1323 if not lock:
1322 lock = self.lock()
1324 lock = self.lock()
1323 mylock = True
1325 mylock = True
1324
1326
1325 try:
1327 try:
1326 fetch = self.findincoming(remote, force=force)
1328 fetch = self.findincoming(remote, force=force)
1327 if fetch == [nullid]:
1329 if fetch == [nullid]:
1328 self.ui.status(_("requesting all changes\n"))
1330 self.ui.status(_("requesting all changes\n"))
1329
1331
1330 if not fetch:
1332 if not fetch:
1331 self.ui.status(_("no changes found\n"))
1333 self.ui.status(_("no changes found\n"))
1332 return 0
1334 return 0
1333
1335
1334 if heads is None:
1336 if heads is None:
1335 cg = remote.changegroup(fetch, 'pull')
1337 cg = remote.changegroup(fetch, 'pull')
1336 else:
1338 else:
1337 if 'changegroupsubset' not in remote.capabilities:
1339 if 'changegroupsubset' not in remote.capabilities:
1338 raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
1340 raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
1339 cg = remote.changegroupsubset(fetch, heads, 'pull')
1341 cg = remote.changegroupsubset(fetch, heads, 'pull')
1340 return self.addchangegroup(cg, 'pull', remote.url())
1342 return self.addchangegroup(cg, 'pull', remote.url())
1341 finally:
1343 finally:
1342 if mylock:
1344 if mylock:
1343 lock.release()
1345 lock.release()
1344
1346
1345 def push(self, remote, force=False, revs=None):
1347 def push(self, remote, force=False, revs=None):
1346 # there are two ways to push to remote repo:
1348 # there are two ways to push to remote repo:
1347 #
1349 #
1348 # addchangegroup assumes local user can lock remote
1350 # addchangegroup assumes local user can lock remote
1349 # repo (local filesystem, old ssh servers).
1351 # repo (local filesystem, old ssh servers).
1350 #
1352 #
1351 # unbundle assumes local user cannot lock remote repo (new ssh
1353 # unbundle assumes local user cannot lock remote repo (new ssh
1352 # servers, http servers).
1354 # servers, http servers).
1353
1355
1354 if remote.capable('unbundle'):
1356 if remote.capable('unbundle'):
1355 return self.push_unbundle(remote, force, revs)
1357 return self.push_unbundle(remote, force, revs)
1356 return self.push_addchangegroup(remote, force, revs)
1358 return self.push_addchangegroup(remote, force, revs)
1357
1359
1358 def prepush(self, remote, force, revs):
1360 def prepush(self, remote, force, revs):
1359 base = {}
1361 base = {}
1360 remote_heads = remote.heads()
1362 remote_heads = remote.heads()
1361 inc = self.findincoming(remote, base, remote_heads, force=force)
1363 inc = self.findincoming(remote, base, remote_heads, force=force)
1362
1364
1363 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1365 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1364 if revs is not None:
1366 if revs is not None:
1365 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1367 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1366 else:
1368 else:
1367 bases, heads = update, self.changelog.heads()
1369 bases, heads = update, self.changelog.heads()
1368
1370
1369 if not bases:
1371 if not bases:
1370 self.ui.status(_("no changes found\n"))
1372 self.ui.status(_("no changes found\n"))
1371 return None, 1
1373 return None, 1
1372 elif not force:
1374 elif not force:
1373 # check if we're creating new remote heads
1375 # check if we're creating new remote heads
1374 # to be a remote head after push, node must be either
1376 # to be a remote head after push, node must be either
1375 # - unknown locally
1377 # - unknown locally
1376 # - a local outgoing head descended from update
1378 # - a local outgoing head descended from update
1377 # - a remote head that's known locally and not
1379 # - a remote head that's known locally and not
1378 # ancestral to an outgoing head
1380 # ancestral to an outgoing head
1379
1381
1380 warn = 0
1382 warn = 0
1381
1383
1382 if remote_heads == [nullid]:
1384 if remote_heads == [nullid]:
1383 warn = 0
1385 warn = 0
1384 elif not revs and len(heads) > len(remote_heads):
1386 elif not revs and len(heads) > len(remote_heads):
1385 warn = 1
1387 warn = 1
1386 else:
1388 else:
1387 newheads = list(heads)
1389 newheads = list(heads)
1388 for r in remote_heads:
1390 for r in remote_heads:
1389 if r in self.changelog.nodemap:
1391 if r in self.changelog.nodemap:
1390 desc = self.changelog.heads(r, heads)
1392 desc = self.changelog.heads(r, heads)
1391 l = [h for h in heads if h in desc]
1393 l = [h for h in heads if h in desc]
1392 if not l:
1394 if not l:
1393 newheads.append(r)
1395 newheads.append(r)
1394 else:
1396 else:
1395 newheads.append(r)
1397 newheads.append(r)
1396 if len(newheads) > len(remote_heads):
1398 if len(newheads) > len(remote_heads):
1397 warn = 1
1399 warn = 1
1398
1400
1399 if warn:
1401 if warn:
1400 self.ui.warn(_("abort: push creates new remote branches!\n"))
1402 self.ui.warn(_("abort: push creates new remote branches!\n"))
1401 self.ui.status(_("(did you forget to merge?"
1403 self.ui.status(_("(did you forget to merge?"
1402 " use push -f to force)\n"))
1404 " use push -f to force)\n"))
1403 return None, 1
1405 return None, 1
1404 elif inc:
1406 elif inc:
1405 self.ui.warn(_("note: unsynced remote changes!\n"))
1407 self.ui.warn(_("note: unsynced remote changes!\n"))
1406
1408
1407
1409
1408 if revs is None:
1410 if revs is None:
1409 cg = self.changegroup(update, 'push')
1411 cg = self.changegroup(update, 'push')
1410 else:
1412 else:
1411 cg = self.changegroupsubset(update, revs, 'push')
1413 cg = self.changegroupsubset(update, revs, 'push')
1412 return cg, remote_heads
1414 return cg, remote_heads
1413
1415
1414 def push_addchangegroup(self, remote, force, revs):
1416 def push_addchangegroup(self, remote, force, revs):
1415 lock = remote.lock()
1417 lock = remote.lock()
1416
1418
1417 ret = self.prepush(remote, force, revs)
1419 ret = self.prepush(remote, force, revs)
1418 if ret[0] is not None:
1420 if ret[0] is not None:
1419 cg, remote_heads = ret
1421 cg, remote_heads = ret
1420 return remote.addchangegroup(cg, 'push', self.url())
1422 return remote.addchangegroup(cg, 'push', self.url())
1421 return ret[1]
1423 return ret[1]
1422
1424
1423 def push_unbundle(self, remote, force, revs):
1425 def push_unbundle(self, remote, force, revs):
1424 # local repo finds heads on server, finds out what revs it
1426 # local repo finds heads on server, finds out what revs it
1425 # must push. once revs transferred, if server finds it has
1427 # must push. once revs transferred, if server finds it has
1426 # different heads (someone else won commit/push race), server
1428 # different heads (someone else won commit/push race), server
1427 # aborts.
1429 # aborts.
1428
1430
1429 ret = self.prepush(remote, force, revs)
1431 ret = self.prepush(remote, force, revs)
1430 if ret[0] is not None:
1432 if ret[0] is not None:
1431 cg, remote_heads = ret
1433 cg, remote_heads = ret
1432 if force: remote_heads = ['force']
1434 if force: remote_heads = ['force']
1433 return remote.unbundle(cg, remote_heads, 'push')
1435 return remote.unbundle(cg, remote_heads, 'push')
1434 return ret[1]
1436 return ret[1]
1435
1437
1436 def changegroupinfo(self, nodes):
1438 def changegroupinfo(self, nodes):
1437 self.ui.note(_("%d changesets found\n") % len(nodes))
1439 self.ui.note(_("%d changesets found\n") % len(nodes))
1438 if self.ui.debugflag:
1440 if self.ui.debugflag:
1439 self.ui.debug(_("List of changesets:\n"))
1441 self.ui.debug(_("List of changesets:\n"))
1440 for node in nodes:
1442 for node in nodes:
1441 self.ui.debug("%s\n" % hex(node))
1443 self.ui.debug("%s\n" % hex(node))
1442
1444
1443 def changegroupsubset(self, bases, heads, source):
1445 def changegroupsubset(self, bases, heads, source):
1444 """This function generates a changegroup consisting of all the nodes
1446 """This function generates a changegroup consisting of all the nodes
1445 that are descendents of any of the bases, and ancestors of any of
1447 that are descendents of any of the bases, and ancestors of any of
1446 the heads.
1448 the heads.
1447
1449
1448 It is fairly complex as determining which filenodes and which
1450 It is fairly complex as determining which filenodes and which
1449 manifest nodes need to be included for the changeset to be complete
1451 manifest nodes need to be included for the changeset to be complete
1450 is non-trivial.
1452 is non-trivial.
1451
1453
1452 Another wrinkle is doing the reverse, figuring out which changeset in
1454 Another wrinkle is doing the reverse, figuring out which changeset in
1453 the changegroup a particular filenode or manifestnode belongs to."""
1455 the changegroup a particular filenode or manifestnode belongs to."""
1454
1456
1455 self.hook('preoutgoing', throw=True, source=source)
1457 self.hook('preoutgoing', throw=True, source=source)
1456
1458
1457 # Set up some initial variables
1459 # Set up some initial variables
1458 # Make it easy to refer to self.changelog
1460 # Make it easy to refer to self.changelog
1459 cl = self.changelog
1461 cl = self.changelog
1460 # msng is short for missing - compute the list of changesets in this
1462 # msng is short for missing - compute the list of changesets in this
1461 # changegroup.
1463 # changegroup.
1462 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1464 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1463 self.changegroupinfo(msng_cl_lst)
1465 self.changegroupinfo(msng_cl_lst)
1464 # Some bases may turn out to be superfluous, and some heads may be
1466 # Some bases may turn out to be superfluous, and some heads may be
1465 # too. nodesbetween will return the minimal set of bases and heads
1467 # too. nodesbetween will return the minimal set of bases and heads
1466 # necessary to re-create the changegroup.
1468 # necessary to re-create the changegroup.
1467
1469
1468 # Known heads are the list of heads that it is assumed the recipient
1470 # Known heads are the list of heads that it is assumed the recipient
1469 # of this changegroup will know about.
1471 # of this changegroup will know about.
1470 knownheads = {}
1472 knownheads = {}
1471 # We assume that all parents of bases are known heads.
1473 # We assume that all parents of bases are known heads.
1472 for n in bases:
1474 for n in bases:
1473 for p in cl.parents(n):
1475 for p in cl.parents(n):
1474 if p != nullid:
1476 if p != nullid:
1475 knownheads[p] = 1
1477 knownheads[p] = 1
1476 knownheads = knownheads.keys()
1478 knownheads = knownheads.keys()
1477 if knownheads:
1479 if knownheads:
1478 # Now that we know what heads are known, we can compute which
1480 # Now that we know what heads are known, we can compute which
1479 # changesets are known. The recipient must know about all
1481 # changesets are known. The recipient must know about all
1480 # changesets required to reach the known heads from the null
1482 # changesets required to reach the known heads from the null
1481 # changeset.
1483 # changeset.
1482 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1484 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1483 junk = None
1485 junk = None
1484 # Transform the list into an ersatz set.
1486 # Transform the list into an ersatz set.
1485 has_cl_set = dict.fromkeys(has_cl_set)
1487 has_cl_set = dict.fromkeys(has_cl_set)
1486 else:
1488 else:
1487 # If there were no known heads, the recipient cannot be assumed to
1489 # If there were no known heads, the recipient cannot be assumed to
1488 # know about any changesets.
1490 # know about any changesets.
1489 has_cl_set = {}
1491 has_cl_set = {}
1490
1492
1491 # Make it easy to refer to self.manifest
1493 # Make it easy to refer to self.manifest
1492 mnfst = self.manifest
1494 mnfst = self.manifest
1493 # We don't know which manifests are missing yet
1495 # We don't know which manifests are missing yet
1494 msng_mnfst_set = {}
1496 msng_mnfst_set = {}
1495 # Nor do we know which filenodes are missing.
1497 # Nor do we know which filenodes are missing.
1496 msng_filenode_set = {}
1498 msng_filenode_set = {}
1497
1499
1498 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1500 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1499 junk = None
1501 junk = None
1500
1502
1501 # A changeset always belongs to itself, so the changenode lookup
1503 # A changeset always belongs to itself, so the changenode lookup
1502 # function for a changenode is identity.
1504 # function for a changenode is identity.
1503 def identity(x):
1505 def identity(x):
1504 return x
1506 return x
1505
1507
1506 # A function generating function. Sets up an environment for the
1508 # A function generating function. Sets up an environment for the
1507 # inner function.
1509 # inner function.
1508 def cmp_by_rev_func(revlog):
1510 def cmp_by_rev_func(revlog):
1509 # Compare two nodes by their revision number in the environment's
1511 # Compare two nodes by their revision number in the environment's
1510 # revision history. Since the revision number both represents the
1512 # revision history. Since the revision number both represents the
1511 # most efficient order to read the nodes in, and represents a
1513 # most efficient order to read the nodes in, and represents a
1512 # topological sorting of the nodes, this function is often useful.
1514 # topological sorting of the nodes, this function is often useful.
1513 def cmp_by_rev(a, b):
1515 def cmp_by_rev(a, b):
1514 return cmp(revlog.rev(a), revlog.rev(b))
1516 return cmp(revlog.rev(a), revlog.rev(b))
1515 return cmp_by_rev
1517 return cmp_by_rev
1516
1518
1517 # If we determine that a particular file or manifest node must be a
1519 # If we determine that a particular file or manifest node must be a
1518 # node that the recipient of the changegroup will already have, we can
1520 # node that the recipient of the changegroup will already have, we can
1519 # also assume the recipient will have all the parents. This function
1521 # also assume the recipient will have all the parents. This function
1520 # prunes them from the set of missing nodes.
1522 # prunes them from the set of missing nodes.
1521 def prune_parents(revlog, hasset, msngset):
1523 def prune_parents(revlog, hasset, msngset):
1522 haslst = hasset.keys()
1524 haslst = hasset.keys()
1523 haslst.sort(cmp_by_rev_func(revlog))
1525 haslst.sort(cmp_by_rev_func(revlog))
1524 for node in haslst:
1526 for node in haslst:
1525 parentlst = [p for p in revlog.parents(node) if p != nullid]
1527 parentlst = [p for p in revlog.parents(node) if p != nullid]
1526 while parentlst:
1528 while parentlst:
1527 n = parentlst.pop()
1529 n = parentlst.pop()
1528 if n not in hasset:
1530 if n not in hasset:
1529 hasset[n] = 1
1531 hasset[n] = 1
1530 p = [p for p in revlog.parents(n) if p != nullid]
1532 p = [p for p in revlog.parents(n) if p != nullid]
1531 parentlst.extend(p)
1533 parentlst.extend(p)
1532 for n in hasset:
1534 for n in hasset:
1533 msngset.pop(n, None)
1535 msngset.pop(n, None)
1534
1536
1535 # This is a function generating function used to set up an environment
1537 # This is a function generating function used to set up an environment
1536 # for the inner function to execute in.
1538 # for the inner function to execute in.
1537 def manifest_and_file_collector(changedfileset):
1539 def manifest_and_file_collector(changedfileset):
1538 # This is an information gathering function that gathers
1540 # This is an information gathering function that gathers
1539 # information from each changeset node that goes out as part of
1541 # information from each changeset node that goes out as part of
1540 # the changegroup. The information gathered is a list of which
1542 # the changegroup. The information gathered is a list of which
1541 # manifest nodes are potentially required (the recipient may
1543 # manifest nodes are potentially required (the recipient may
1542 # already have them) and total list of all files which were
1544 # already have them) and total list of all files which were
1543 # changed in any changeset in the changegroup.
1545 # changed in any changeset in the changegroup.
1544 #
1546 #
1545 # We also remember the first changenode we saw any manifest
1547 # We also remember the first changenode we saw any manifest
1546 # referenced by so we can later determine which changenode 'owns'
1548 # referenced by so we can later determine which changenode 'owns'
1547 # the manifest.
1549 # the manifest.
1548 def collect_manifests_and_files(clnode):
1550 def collect_manifests_and_files(clnode):
1549 c = cl.read(clnode)
1551 c = cl.read(clnode)
1550 for f in c[3]:
1552 for f in c[3]:
1551 # This is to make sure we only have one instance of each
1553 # This is to make sure we only have one instance of each
1552 # filename string for each filename.
1554 # filename string for each filename.
1553 changedfileset.setdefault(f, f)
1555 changedfileset.setdefault(f, f)
1554 msng_mnfst_set.setdefault(c[0], clnode)
1556 msng_mnfst_set.setdefault(c[0], clnode)
1555 return collect_manifests_and_files
1557 return collect_manifests_and_files
1556
1558
1557 # Figure out which manifest nodes (of the ones we think might be part
1559 # Figure out which manifest nodes (of the ones we think might be part
1558 # of the changegroup) the recipient must know about and remove them
1560 # of the changegroup) the recipient must know about and remove them
1559 # from the changegroup.
1561 # from the changegroup.
1560 def prune_manifests():
1562 def prune_manifests():
1561 has_mnfst_set = {}
1563 has_mnfst_set = {}
1562 for n in msng_mnfst_set:
1564 for n in msng_mnfst_set:
1563 # If a 'missing' manifest thinks it belongs to a changenode
1565 # If a 'missing' manifest thinks it belongs to a changenode
1564 # the recipient is assumed to have, obviously the recipient
1566 # the recipient is assumed to have, obviously the recipient
1565 # must have that manifest.
1567 # must have that manifest.
1566 linknode = cl.node(mnfst.linkrev(n))
1568 linknode = cl.node(mnfst.linkrev(n))
1567 if linknode in has_cl_set:
1569 if linknode in has_cl_set:
1568 has_mnfst_set[n] = 1
1570 has_mnfst_set[n] = 1
1569 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1571 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1570
1572
1571 # Use the information collected in collect_manifests_and_files to say
1573 # Use the information collected in collect_manifests_and_files to say
1572 # which changenode any manifestnode belongs to.
1574 # which changenode any manifestnode belongs to.
1573 def lookup_manifest_link(mnfstnode):
1575 def lookup_manifest_link(mnfstnode):
1574 return msng_mnfst_set[mnfstnode]
1576 return msng_mnfst_set[mnfstnode]
1575
1577
1576 # A function generating function that sets up the initial environment
1578 # A function generating function that sets up the initial environment
1577 # the inner function.
1579 # the inner function.
1578 def filenode_collector(changedfiles):
1580 def filenode_collector(changedfiles):
1579 next_rev = [0]
1581 next_rev = [0]
1580 # This gathers information from each manifestnode included in the
1582 # This gathers information from each manifestnode included in the
1581 # changegroup about which filenodes the manifest node references
1583 # changegroup about which filenodes the manifest node references
1582 # so we can include those in the changegroup too.
1584 # so we can include those in the changegroup too.
1583 #
1585 #
1584 # It also remembers which changenode each filenode belongs to. It
1586 # It also remembers which changenode each filenode belongs to. It
1585 # does this by assuming the a filenode belongs to the changenode
1587 # does this by assuming the a filenode belongs to the changenode
1586 # the first manifest that references it belongs to.
1588 # the first manifest that references it belongs to.
1587 def collect_msng_filenodes(mnfstnode):
1589 def collect_msng_filenodes(mnfstnode):
1588 r = mnfst.rev(mnfstnode)
1590 r = mnfst.rev(mnfstnode)
1589 if r == next_rev[0]:
1591 if r == next_rev[0]:
1590 # If the last rev we looked at was the one just previous,
1592 # If the last rev we looked at was the one just previous,
1591 # we only need to see a diff.
1593 # we only need to see a diff.
1592 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1594 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1593 # For each line in the delta
1595 # For each line in the delta
1594 for dline in delta.splitlines():
1596 for dline in delta.splitlines():
1595 # get the filename and filenode for that line
1597 # get the filename and filenode for that line
1596 f, fnode = dline.split('\0')
1598 f, fnode = dline.split('\0')
1597 fnode = bin(fnode[:40])
1599 fnode = bin(fnode[:40])
1598 f = changedfiles.get(f, None)
1600 f = changedfiles.get(f, None)
1599 # And if the file is in the list of files we care
1601 # And if the file is in the list of files we care
1600 # about.
1602 # about.
1601 if f is not None:
1603 if f is not None:
1602 # Get the changenode this manifest belongs to
1604 # Get the changenode this manifest belongs to
1603 clnode = msng_mnfst_set[mnfstnode]
1605 clnode = msng_mnfst_set[mnfstnode]
1604 # Create the set of filenodes for the file if
1606 # Create the set of filenodes for the file if
1605 # there isn't one already.
1607 # there isn't one already.
1606 ndset = msng_filenode_set.setdefault(f, {})
1608 ndset = msng_filenode_set.setdefault(f, {})
1607 # And set the filenode's changelog node to the
1609 # And set the filenode's changelog node to the
1608 # manifest's if it hasn't been set already.
1610 # manifest's if it hasn't been set already.
1609 ndset.setdefault(fnode, clnode)
1611 ndset.setdefault(fnode, clnode)
1610 else:
1612 else:
1611 # Otherwise we need a full manifest.
1613 # Otherwise we need a full manifest.
1612 m = mnfst.read(mnfstnode)
1614 m = mnfst.read(mnfstnode)
1613 # For every file in we care about.
1615 # For every file in we care about.
1614 for f in changedfiles:
1616 for f in changedfiles:
1615 fnode = m.get(f, None)
1617 fnode = m.get(f, None)
1616 # If it's in the manifest
1618 # If it's in the manifest
1617 if fnode is not None:
1619 if fnode is not None:
1618 # See comments above.
1620 # See comments above.
1619 clnode = msng_mnfst_set[mnfstnode]
1621 clnode = msng_mnfst_set[mnfstnode]
1620 ndset = msng_filenode_set.setdefault(f, {})
1622 ndset = msng_filenode_set.setdefault(f, {})
1621 ndset.setdefault(fnode, clnode)
1623 ndset.setdefault(fnode, clnode)
1622 # Remember the revision we hope to see next.
1624 # Remember the revision we hope to see next.
1623 next_rev[0] = r + 1
1625 next_rev[0] = r + 1
1624 return collect_msng_filenodes
1626 return collect_msng_filenodes
1625
1627
1626 # We have a list of filenodes we think we need for a file, lets remove
1628 # We have a list of filenodes we think we need for a file, lets remove
1627 # all those we now the recipient must have.
1629 # all those we now the recipient must have.
1628 def prune_filenodes(f, filerevlog):
1630 def prune_filenodes(f, filerevlog):
1629 msngset = msng_filenode_set[f]
1631 msngset = msng_filenode_set[f]
1630 hasset = {}
1632 hasset = {}
1631 # If a 'missing' filenode thinks it belongs to a changenode we
1633 # If a 'missing' filenode thinks it belongs to a changenode we
1632 # assume the recipient must have, then the recipient must have
1634 # assume the recipient must have, then the recipient must have
1633 # that filenode.
1635 # that filenode.
1634 for n in msngset:
1636 for n in msngset:
1635 clnode = cl.node(filerevlog.linkrev(n))
1637 clnode = cl.node(filerevlog.linkrev(n))
1636 if clnode in has_cl_set:
1638 if clnode in has_cl_set:
1637 hasset[n] = 1
1639 hasset[n] = 1
1638 prune_parents(filerevlog, hasset, msngset)
1640 prune_parents(filerevlog, hasset, msngset)
1639
1641
1640 # A function generator function that sets up the a context for the
1642 # A function generator function that sets up the a context for the
1641 # inner function.
1643 # inner function.
1642 def lookup_filenode_link_func(fname):
1644 def lookup_filenode_link_func(fname):
1643 msngset = msng_filenode_set[fname]
1645 msngset = msng_filenode_set[fname]
1644 # Lookup the changenode the filenode belongs to.
1646 # Lookup the changenode the filenode belongs to.
1645 def lookup_filenode_link(fnode):
1647 def lookup_filenode_link(fnode):
1646 return msngset[fnode]
1648 return msngset[fnode]
1647 return lookup_filenode_link
1649 return lookup_filenode_link
1648
1650
1649 # Now that we have all theses utility functions to help out and
1651 # Now that we have all theses utility functions to help out and
1650 # logically divide up the task, generate the group.
1652 # logically divide up the task, generate the group.
1651 def gengroup():
1653 def gengroup():
1652 # The set of changed files starts empty.
1654 # The set of changed files starts empty.
1653 changedfiles = {}
1655 changedfiles = {}
1654 # Create a changenode group generator that will call our functions
1656 # Create a changenode group generator that will call our functions
1655 # back to lookup the owning changenode and collect information.
1657 # back to lookup the owning changenode and collect information.
1656 group = cl.group(msng_cl_lst, identity,
1658 group = cl.group(msng_cl_lst, identity,
1657 manifest_and_file_collector(changedfiles))
1659 manifest_and_file_collector(changedfiles))
1658 for chnk in group:
1660 for chnk in group:
1659 yield chnk
1661 yield chnk
1660
1662
1661 # The list of manifests has been collected by the generator
1663 # The list of manifests has been collected by the generator
1662 # calling our functions back.
1664 # calling our functions back.
1663 prune_manifests()
1665 prune_manifests()
1664 msng_mnfst_lst = msng_mnfst_set.keys()
1666 msng_mnfst_lst = msng_mnfst_set.keys()
1665 # Sort the manifestnodes by revision number.
1667 # Sort the manifestnodes by revision number.
1666 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1668 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1667 # Create a generator for the manifestnodes that calls our lookup
1669 # Create a generator for the manifestnodes that calls our lookup
1668 # and data collection functions back.
1670 # and data collection functions back.
1669 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1671 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1670 filenode_collector(changedfiles))
1672 filenode_collector(changedfiles))
1671 for chnk in group:
1673 for chnk in group:
1672 yield chnk
1674 yield chnk
1673
1675
1674 # These are no longer needed, dereference and toss the memory for
1676 # These are no longer needed, dereference and toss the memory for
1675 # them.
1677 # them.
1676 msng_mnfst_lst = None
1678 msng_mnfst_lst = None
1677 msng_mnfst_set.clear()
1679 msng_mnfst_set.clear()
1678
1680
1679 changedfiles = changedfiles.keys()
1681 changedfiles = changedfiles.keys()
1680 changedfiles.sort()
1682 changedfiles.sort()
1681 # Go through all our files in order sorted by name.
1683 # Go through all our files in order sorted by name.
1682 for fname in changedfiles:
1684 for fname in changedfiles:
1683 filerevlog = self.file(fname)
1685 filerevlog = self.file(fname)
1684 # Toss out the filenodes that the recipient isn't really
1686 # Toss out the filenodes that the recipient isn't really
1685 # missing.
1687 # missing.
1686 if msng_filenode_set.has_key(fname):
1688 if msng_filenode_set.has_key(fname):
1687 prune_filenodes(fname, filerevlog)
1689 prune_filenodes(fname, filerevlog)
1688 msng_filenode_lst = msng_filenode_set[fname].keys()
1690 msng_filenode_lst = msng_filenode_set[fname].keys()
1689 else:
1691 else:
1690 msng_filenode_lst = []
1692 msng_filenode_lst = []
1691 # If any filenodes are left, generate the group for them,
1693 # If any filenodes are left, generate the group for them,
1692 # otherwise don't bother.
1694 # otherwise don't bother.
1693 if len(msng_filenode_lst) > 0:
1695 if len(msng_filenode_lst) > 0:
1694 yield changegroup.genchunk(fname)
1696 yield changegroup.genchunk(fname)
1695 # Sort the filenodes by their revision #
1697 # Sort the filenodes by their revision #
1696 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1698 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1697 # Create a group generator and only pass in a changenode
1699 # Create a group generator and only pass in a changenode
1698 # lookup function as we need to collect no information
1700 # lookup function as we need to collect no information
1699 # from filenodes.
1701 # from filenodes.
1700 group = filerevlog.group(msng_filenode_lst,
1702 group = filerevlog.group(msng_filenode_lst,
1701 lookup_filenode_link_func(fname))
1703 lookup_filenode_link_func(fname))
1702 for chnk in group:
1704 for chnk in group:
1703 yield chnk
1705 yield chnk
1704 if msng_filenode_set.has_key(fname):
1706 if msng_filenode_set.has_key(fname):
1705 # Don't need this anymore, toss it to free memory.
1707 # Don't need this anymore, toss it to free memory.
1706 del msng_filenode_set[fname]
1708 del msng_filenode_set[fname]
1707 # Signal that no more groups are left.
1709 # Signal that no more groups are left.
1708 yield changegroup.closechunk()
1710 yield changegroup.closechunk()
1709
1711
1710 if msng_cl_lst:
1712 if msng_cl_lst:
1711 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1713 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1712
1714
1713 return util.chunkbuffer(gengroup())
1715 return util.chunkbuffer(gengroup())
1714
1716
1715 def changegroup(self, basenodes, source):
1717 def changegroup(self, basenodes, source):
1716 """Generate a changegroup of all nodes that we have that a recipient
1718 """Generate a changegroup of all nodes that we have that a recipient
1717 doesn't.
1719 doesn't.
1718
1720
1719 This is much easier than the previous function as we can assume that
1721 This is much easier than the previous function as we can assume that
1720 the recipient has any changenode we aren't sending them."""
1722 the recipient has any changenode we aren't sending them."""
1721
1723
1722 self.hook('preoutgoing', throw=True, source=source)
1724 self.hook('preoutgoing', throw=True, source=source)
1723
1725
1724 cl = self.changelog
1726 cl = self.changelog
1725 nodes = cl.nodesbetween(basenodes, None)[0]
1727 nodes = cl.nodesbetween(basenodes, None)[0]
1726 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1728 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1727 self.changegroupinfo(nodes)
1729 self.changegroupinfo(nodes)
1728
1730
1729 def identity(x):
1731 def identity(x):
1730 return x
1732 return x
1731
1733
1732 def gennodelst(revlog):
1734 def gennodelst(revlog):
1733 for r in xrange(0, revlog.count()):
1735 for r in xrange(0, revlog.count()):
1734 n = revlog.node(r)
1736 n = revlog.node(r)
1735 if revlog.linkrev(n) in revset:
1737 if revlog.linkrev(n) in revset:
1736 yield n
1738 yield n
1737
1739
1738 def changed_file_collector(changedfileset):
1740 def changed_file_collector(changedfileset):
1739 def collect_changed_files(clnode):
1741 def collect_changed_files(clnode):
1740 c = cl.read(clnode)
1742 c = cl.read(clnode)
1741 for fname in c[3]:
1743 for fname in c[3]:
1742 changedfileset[fname] = 1
1744 changedfileset[fname] = 1
1743 return collect_changed_files
1745 return collect_changed_files
1744
1746
1745 def lookuprevlink_func(revlog):
1747 def lookuprevlink_func(revlog):
1746 def lookuprevlink(n):
1748 def lookuprevlink(n):
1747 return cl.node(revlog.linkrev(n))
1749 return cl.node(revlog.linkrev(n))
1748 return lookuprevlink
1750 return lookuprevlink
1749
1751
1750 def gengroup():
1752 def gengroup():
1751 # construct a list of all changed files
1753 # construct a list of all changed files
1752 changedfiles = {}
1754 changedfiles = {}
1753
1755
1754 for chnk in cl.group(nodes, identity,
1756 for chnk in cl.group(nodes, identity,
1755 changed_file_collector(changedfiles)):
1757 changed_file_collector(changedfiles)):
1756 yield chnk
1758 yield chnk
1757 changedfiles = changedfiles.keys()
1759 changedfiles = changedfiles.keys()
1758 changedfiles.sort()
1760 changedfiles.sort()
1759
1761
1760 mnfst = self.manifest
1762 mnfst = self.manifest
1761 nodeiter = gennodelst(mnfst)
1763 nodeiter = gennodelst(mnfst)
1762 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1764 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1763 yield chnk
1765 yield chnk
1764
1766
1765 for fname in changedfiles:
1767 for fname in changedfiles:
1766 filerevlog = self.file(fname)
1768 filerevlog = self.file(fname)
1767 nodeiter = gennodelst(filerevlog)
1769 nodeiter = gennodelst(filerevlog)
1768 nodeiter = list(nodeiter)
1770 nodeiter = list(nodeiter)
1769 if nodeiter:
1771 if nodeiter:
1770 yield changegroup.genchunk(fname)
1772 yield changegroup.genchunk(fname)
1771 lookup = lookuprevlink_func(filerevlog)
1773 lookup = lookuprevlink_func(filerevlog)
1772 for chnk in filerevlog.group(nodeiter, lookup):
1774 for chnk in filerevlog.group(nodeiter, lookup):
1773 yield chnk
1775 yield chnk
1774
1776
1775 yield changegroup.closechunk()
1777 yield changegroup.closechunk()
1776
1778
1777 if nodes:
1779 if nodes:
1778 self.hook('outgoing', node=hex(nodes[0]), source=source)
1780 self.hook('outgoing', node=hex(nodes[0]), source=source)
1779
1781
1780 return util.chunkbuffer(gengroup())
1782 return util.chunkbuffer(gengroup())
1781
1783
1782 def addchangegroup(self, source, srctype, url):
1784 def addchangegroup(self, source, srctype, url):
1783 """add changegroup to repo.
1785 """add changegroup to repo.
1784
1786
1785 return values:
1787 return values:
1786 - nothing changed or no source: 0
1788 - nothing changed or no source: 0
1787 - more heads than before: 1+added heads (2..n)
1789 - more heads than before: 1+added heads (2..n)
1788 - less heads than before: -1-removed heads (-2..-n)
1790 - less heads than before: -1-removed heads (-2..-n)
1789 - number of heads stays the same: 1
1791 - number of heads stays the same: 1
1790 """
1792 """
1791 def csmap(x):
1793 def csmap(x):
1792 self.ui.debug(_("add changeset %s\n") % short(x))
1794 self.ui.debug(_("add changeset %s\n") % short(x))
1793 return cl.count()
1795 return cl.count()
1794
1796
1795 def revmap(x):
1797 def revmap(x):
1796 return cl.rev(x)
1798 return cl.rev(x)
1797
1799
1798 if not source:
1800 if not source:
1799 return 0
1801 return 0
1800
1802
1801 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1803 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1802
1804
1803 changesets = files = revisions = 0
1805 changesets = files = revisions = 0
1804
1806
1805 tr = self.transaction()
1807 tr = self.transaction()
1806
1808
1807 # write changelog data to temp files so concurrent readers will not see
1809 # write changelog data to temp files so concurrent readers will not see
1808 # inconsistent view
1810 # inconsistent view
1809 cl = self.changelog
1811 cl = self.changelog
1810 cl.delayupdate()
1812 cl.delayupdate()
1811 oldheads = len(cl.heads())
1813 oldheads = len(cl.heads())
1812
1814
1813 # pull off the changeset group
1815 # pull off the changeset group
1814 self.ui.status(_("adding changesets\n"))
1816 self.ui.status(_("adding changesets\n"))
1815 cor = cl.count() - 1
1817 cor = cl.count() - 1
1816 chunkiter = changegroup.chunkiter(source)
1818 chunkiter = changegroup.chunkiter(source)
1817 if cl.addgroup(chunkiter, csmap, tr, 1) is None:
1819 if cl.addgroup(chunkiter, csmap, tr, 1) is None:
1818 raise util.Abort(_("received changelog group is empty"))
1820 raise util.Abort(_("received changelog group is empty"))
1819 cnr = cl.count() - 1
1821 cnr = cl.count() - 1
1820 changesets = cnr - cor
1822 changesets = cnr - cor
1821
1823
1822 # pull off the manifest group
1824 # pull off the manifest group
1823 self.ui.status(_("adding manifests\n"))
1825 self.ui.status(_("adding manifests\n"))
1824 chunkiter = changegroup.chunkiter(source)
1826 chunkiter = changegroup.chunkiter(source)
1825 # no need to check for empty manifest group here:
1827 # no need to check for empty manifest group here:
1826 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1828 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1827 # no new manifest will be created and the manifest group will
1829 # no new manifest will be created and the manifest group will
1828 # be empty during the pull
1830 # be empty during the pull
1829 self.manifest.addgroup(chunkiter, revmap, tr)
1831 self.manifest.addgroup(chunkiter, revmap, tr)
1830
1832
1831 # process the files
1833 # process the files
1832 self.ui.status(_("adding file changes\n"))
1834 self.ui.status(_("adding file changes\n"))
1833 while 1:
1835 while 1:
1834 f = changegroup.getchunk(source)
1836 f = changegroup.getchunk(source)
1835 if not f:
1837 if not f:
1836 break
1838 break
1837 self.ui.debug(_("adding %s revisions\n") % f)
1839 self.ui.debug(_("adding %s revisions\n") % f)
1838 fl = self.file(f)
1840 fl = self.file(f)
1839 o = fl.count()
1841 o = fl.count()
1840 chunkiter = changegroup.chunkiter(source)
1842 chunkiter = changegroup.chunkiter(source)
1841 if fl.addgroup(chunkiter, revmap, tr) is None:
1843 if fl.addgroup(chunkiter, revmap, tr) is None:
1842 raise util.Abort(_("received file revlog group is empty"))
1844 raise util.Abort(_("received file revlog group is empty"))
1843 revisions += fl.count() - o
1845 revisions += fl.count() - o
1844 files += 1
1846 files += 1
1845
1847
1846 # make changelog see real files again
1848 # make changelog see real files again
1847 cl.finalize(tr)
1849 cl.finalize(tr)
1848
1850
1849 newheads = len(self.changelog.heads())
1851 newheads = len(self.changelog.heads())
1850 heads = ""
1852 heads = ""
1851 if oldheads and newheads != oldheads:
1853 if oldheads and newheads != oldheads:
1852 heads = _(" (%+d heads)") % (newheads - oldheads)
1854 heads = _(" (%+d heads)") % (newheads - oldheads)
1853
1855
1854 self.ui.status(_("added %d changesets"
1856 self.ui.status(_("added %d changesets"
1855 " with %d changes to %d files%s\n")
1857 " with %d changes to %d files%s\n")
1856 % (changesets, revisions, files, heads))
1858 % (changesets, revisions, files, heads))
1857
1859
1858 if changesets > 0:
1860 if changesets > 0:
1859 self.hook('pretxnchangegroup', throw=True,
1861 self.hook('pretxnchangegroup', throw=True,
1860 node=hex(self.changelog.node(cor+1)), source=srctype,
1862 node=hex(self.changelog.node(cor+1)), source=srctype,
1861 url=url)
1863 url=url)
1862
1864
1863 tr.close()
1865 tr.close()
1864
1866
1865 if changesets > 0:
1867 if changesets > 0:
1866 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1868 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1867 source=srctype, url=url)
1869 source=srctype, url=url)
1868
1870
1869 for i in xrange(cor + 1, cnr + 1):
1871 for i in xrange(cor + 1, cnr + 1):
1870 self.hook("incoming", node=hex(self.changelog.node(i)),
1872 self.hook("incoming", node=hex(self.changelog.node(i)),
1871 source=srctype, url=url)
1873 source=srctype, url=url)
1872
1874
1873 # never return 0 here:
1875 # never return 0 here:
1874 if newheads < oldheads:
1876 if newheads < oldheads:
1875 return newheads - oldheads - 1
1877 return newheads - oldheads - 1
1876 else:
1878 else:
1877 return newheads - oldheads + 1
1879 return newheads - oldheads + 1
1878
1880
1879
1881
1880 def stream_in(self, remote):
1882 def stream_in(self, remote):
1881 fp = remote.stream_out()
1883 fp = remote.stream_out()
1882 l = fp.readline()
1884 l = fp.readline()
1883 try:
1885 try:
1884 resp = int(l)
1886 resp = int(l)
1885 except ValueError:
1887 except ValueError:
1886 raise util.UnexpectedOutput(
1888 raise util.UnexpectedOutput(
1887 _('Unexpected response from remote server:'), l)
1889 _('Unexpected response from remote server:'), l)
1888 if resp == 1:
1890 if resp == 1:
1889 raise util.Abort(_('operation forbidden by server'))
1891 raise util.Abort(_('operation forbidden by server'))
1890 elif resp == 2:
1892 elif resp == 2:
1891 raise util.Abort(_('locking the remote repository failed'))
1893 raise util.Abort(_('locking the remote repository failed'))
1892 elif resp != 0:
1894 elif resp != 0:
1893 raise util.Abort(_('the server sent an unknown error code'))
1895 raise util.Abort(_('the server sent an unknown error code'))
1894 self.ui.status(_('streaming all changes\n'))
1896 self.ui.status(_('streaming all changes\n'))
1895 l = fp.readline()
1897 l = fp.readline()
1896 try:
1898 try:
1897 total_files, total_bytes = map(int, l.split(' ', 1))
1899 total_files, total_bytes = map(int, l.split(' ', 1))
1898 except ValueError, TypeError:
1900 except ValueError, TypeError:
1899 raise util.UnexpectedOutput(
1901 raise util.UnexpectedOutput(
1900 _('Unexpected response from remote server:'), l)
1902 _('Unexpected response from remote server:'), l)
1901 self.ui.status(_('%d files to transfer, %s of data\n') %
1903 self.ui.status(_('%d files to transfer, %s of data\n') %
1902 (total_files, util.bytecount(total_bytes)))
1904 (total_files, util.bytecount(total_bytes)))
1903 start = time.time()
1905 start = time.time()
1904 for i in xrange(total_files):
1906 for i in xrange(total_files):
1905 # XXX doesn't support '\n' or '\r' in filenames
1907 # XXX doesn't support '\n' or '\r' in filenames
1906 l = fp.readline()
1908 l = fp.readline()
1907 try:
1909 try:
1908 name, size = l.split('\0', 1)
1910 name, size = l.split('\0', 1)
1909 size = int(size)
1911 size = int(size)
1910 except ValueError, TypeError:
1912 except ValueError, TypeError:
1911 raise util.UnexpectedOutput(
1913 raise util.UnexpectedOutput(
1912 _('Unexpected response from remote server:'), l)
1914 _('Unexpected response from remote server:'), l)
1913 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1915 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1914 ofp = self.sopener(name, 'w')
1916 ofp = self.sopener(name, 'w')
1915 for chunk in util.filechunkiter(fp, limit=size):
1917 for chunk in util.filechunkiter(fp, limit=size):
1916 ofp.write(chunk)
1918 ofp.write(chunk)
1917 ofp.close()
1919 ofp.close()
1918 elapsed = time.time() - start
1920 elapsed = time.time() - start
1919 if elapsed <= 0:
1921 if elapsed <= 0:
1920 elapsed = 0.001
1922 elapsed = 0.001
1921 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1923 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1922 (util.bytecount(total_bytes), elapsed,
1924 (util.bytecount(total_bytes), elapsed,
1923 util.bytecount(total_bytes / elapsed)))
1925 util.bytecount(total_bytes / elapsed)))
1924 self.reload()
1926 self.reload()
1925 return len(self.heads()) + 1
1927 return len(self.heads()) + 1
1926
1928
1927 def clone(self, remote, heads=[], stream=False):
1929 def clone(self, remote, heads=[], stream=False):
1928 '''clone remote repository.
1930 '''clone remote repository.
1929
1931
1930 keyword arguments:
1932 keyword arguments:
1931 heads: list of revs to clone (forces use of pull)
1933 heads: list of revs to clone (forces use of pull)
1932 stream: use streaming clone if possible'''
1934 stream: use streaming clone if possible'''
1933
1935
1934 # now, all clients that can request uncompressed clones can
1936 # now, all clients that can request uncompressed clones can
1935 # read repo formats supported by all servers that can serve
1937 # read repo formats supported by all servers that can serve
1936 # them.
1938 # them.
1937
1939
1938 # if revlog format changes, client will have to check version
1940 # if revlog format changes, client will have to check version
1939 # and format flags on "stream" capability, and use
1941 # and format flags on "stream" capability, and use
1940 # uncompressed only if compatible.
1942 # uncompressed only if compatible.
1941
1943
1942 if stream and not heads and remote.capable('stream'):
1944 if stream and not heads and remote.capable('stream'):
1943 return self.stream_in(remote)
1945 return self.stream_in(remote)
1944 return self.pull(remote, heads)
1946 return self.pull(remote, heads)
1945
1947
1946 # used to avoid circular references so destructors work
1948 # used to avoid circular references so destructors work
1947 def aftertrans(files):
1949 def aftertrans(files):
1948 renamefiles = [tuple(t) for t in files]
1950 renamefiles = [tuple(t) for t in files]
1949 def a():
1951 def a():
1950 for src, dest in renamefiles:
1952 for src, dest in renamefiles:
1951 util.rename(src, dest)
1953 util.rename(src, dest)
1952 return a
1954 return a
1953
1955
1954 def instance(ui, path, create):
1956 def instance(ui, path, create):
1955 return localrepository(ui, util.drop_scheme('file', path), create)
1957 return localrepository(ui, util.drop_scheme('file', path), create)
1956
1958
1957 def islocal(path):
1959 def islocal(path):
1958 return True
1960 return True
General Comments 0
You need to be logged in to leave comments. Login now