##// END OF EJS Templates
localrepo: add setparents() to adjust dirstate copies (issue3407)...
Patrick Mezard -
r16551:ebf6d38c stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -1,517 +1,517 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''High-level command function for lfconvert, plus the cmdtable.'''
9 '''High-level command function for lfconvert, plus the cmdtable.'''
10
10
11 import os
11 import os
12 import shutil
12 import shutil
13
13
14 from mercurial import util, match as match_, hg, node, context, error, cmdutil
14 from mercurial import util, match as match_, hg, node, context, error, cmdutil
15 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17 import lfutil
17 import lfutil
18 import basestore
18 import basestore
19
19
20 # -- Commands ----------------------------------------------------------
20 # -- Commands ----------------------------------------------------------
21
21
22 def lfconvert(ui, src, dest, *pats, **opts):
22 def lfconvert(ui, src, dest, *pats, **opts):
23 '''convert a normal repository to a largefiles repository
23 '''convert a normal repository to a largefiles repository
24
24
25 Convert repository SOURCE to a new repository DEST, identical to
25 Convert repository SOURCE to a new repository DEST, identical to
26 SOURCE except that certain files will be converted as largefiles:
26 SOURCE except that certain files will be converted as largefiles:
27 specifically, any file that matches any PATTERN *or* whose size is
27 specifically, any file that matches any PATTERN *or* whose size is
28 above the minimum size threshold is converted as a largefile. The
28 above the minimum size threshold is converted as a largefile. The
29 size used to determine whether or not to track a file as a
29 size used to determine whether or not to track a file as a
30 largefile is the size of the first version of the file. The
30 largefile is the size of the first version of the file. The
31 minimum size can be specified either with --size or in
31 minimum size can be specified either with --size or in
32 configuration as ``largefiles.size``.
32 configuration as ``largefiles.size``.
33
33
34 After running this command you will need to make sure that
34 After running this command you will need to make sure that
35 largefiles is enabled anywhere you intend to push the new
35 largefiles is enabled anywhere you intend to push the new
36 repository.
36 repository.
37
37
38 Use --to-normal to convert largefiles back to normal files; after
38 Use --to-normal to convert largefiles back to normal files; after
39 this, the DEST repository can be used without largefiles at all.'''
39 this, the DEST repository can be used without largefiles at all.'''
40
40
41 if opts['to_normal']:
41 if opts['to_normal']:
42 tolfile = False
42 tolfile = False
43 else:
43 else:
44 tolfile = True
44 tolfile = True
45 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
45 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
46
46
47 if not hg.islocal(src):
47 if not hg.islocal(src):
48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
49 if not hg.islocal(dest):
49 if not hg.islocal(dest):
50 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
50 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
51
51
52 rsrc = hg.repository(ui, src)
52 rsrc = hg.repository(ui, src)
53 ui.status(_('initializing destination %s\n') % dest)
53 ui.status(_('initializing destination %s\n') % dest)
54 rdst = hg.repository(ui, dest, create=True)
54 rdst = hg.repository(ui, dest, create=True)
55
55
56 success = False
56 success = False
57 try:
57 try:
58 # Lock destination to prevent modification while it is converted to.
58 # Lock destination to prevent modification while it is converted to.
59 # Don't need to lock src because we are just reading from its history
59 # Don't need to lock src because we are just reading from its history
60 # which can't change.
60 # which can't change.
61 dstlock = rdst.lock()
61 dstlock = rdst.lock()
62
62
63 # Get a list of all changesets in the source. The easy way to do this
63 # Get a list of all changesets in the source. The easy way to do this
64 # is to simply walk the changelog, using changelog.nodesbewteen().
64 # is to simply walk the changelog, using changelog.nodesbewteen().
65 # Take a look at mercurial/revlog.py:639 for more details.
65 # Take a look at mercurial/revlog.py:639 for more details.
66 # Use a generator instead of a list to decrease memory usage
66 # Use a generator instead of a list to decrease memory usage
67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
68 rsrc.heads())[0])
68 rsrc.heads())[0])
69 revmap = {node.nullid: node.nullid}
69 revmap = {node.nullid: node.nullid}
70 if tolfile:
70 if tolfile:
71 lfiles = set()
71 lfiles = set()
72 normalfiles = set()
72 normalfiles = set()
73 if not pats:
73 if not pats:
74 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
74 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
75 if pats:
75 if pats:
76 matcher = match_.match(rsrc.root, '', list(pats))
76 matcher = match_.match(rsrc.root, '', list(pats))
77 else:
77 else:
78 matcher = None
78 matcher = None
79
79
80 lfiletohash = {}
80 lfiletohash = {}
81 for ctx in ctxs:
81 for ctx in ctxs:
82 ui.progress(_('converting revisions'), ctx.rev(),
82 ui.progress(_('converting revisions'), ctx.rev(),
83 unit=_('revision'), total=rsrc['tip'].rev())
83 unit=_('revision'), total=rsrc['tip'].rev())
84 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
84 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
85 lfiles, normalfiles, matcher, size, lfiletohash)
85 lfiles, normalfiles, matcher, size, lfiletohash)
86 ui.progress(_('converting revisions'), None)
86 ui.progress(_('converting revisions'), None)
87
87
88 if os.path.exists(rdst.wjoin(lfutil.shortname)):
88 if os.path.exists(rdst.wjoin(lfutil.shortname)):
89 shutil.rmtree(rdst.wjoin(lfutil.shortname))
89 shutil.rmtree(rdst.wjoin(lfutil.shortname))
90
90
91 for f in lfiletohash.keys():
91 for f in lfiletohash.keys():
92 if os.path.isfile(rdst.wjoin(f)):
92 if os.path.isfile(rdst.wjoin(f)):
93 os.unlink(rdst.wjoin(f))
93 os.unlink(rdst.wjoin(f))
94 try:
94 try:
95 os.removedirs(os.path.dirname(rdst.wjoin(f)))
95 os.removedirs(os.path.dirname(rdst.wjoin(f)))
96 except OSError:
96 except OSError:
97 pass
97 pass
98
98
99 # If there were any files converted to largefiles, add largefiles
99 # If there were any files converted to largefiles, add largefiles
100 # to the destination repository's requirements.
100 # to the destination repository's requirements.
101 if lfiles:
101 if lfiles:
102 rdst.requirements.add('largefiles')
102 rdst.requirements.add('largefiles')
103 rdst._writerequirements()
103 rdst._writerequirements()
104 else:
104 else:
105 for ctx in ctxs:
105 for ctx in ctxs:
106 ui.progress(_('converting revisions'), ctx.rev(),
106 ui.progress(_('converting revisions'), ctx.rev(),
107 unit=_('revision'), total=rsrc['tip'].rev())
107 unit=_('revision'), total=rsrc['tip'].rev())
108 _addchangeset(ui, rsrc, rdst, ctx, revmap)
108 _addchangeset(ui, rsrc, rdst, ctx, revmap)
109
109
110 ui.progress(_('converting revisions'), None)
110 ui.progress(_('converting revisions'), None)
111 success = True
111 success = True
112 finally:
112 finally:
113 if not success:
113 if not success:
114 # we failed, remove the new directory
114 # we failed, remove the new directory
115 shutil.rmtree(rdst.root)
115 shutil.rmtree(rdst.root)
116 dstlock.release()
116 dstlock.release()
117
117
118 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
118 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
119 # Convert src parents to dst parents
119 # Convert src parents to dst parents
120 parents = _convertparents(ctx, revmap)
120 parents = _convertparents(ctx, revmap)
121
121
122 # Generate list of changed files
122 # Generate list of changed files
123 files = _getchangedfiles(ctx, parents)
123 files = _getchangedfiles(ctx, parents)
124
124
125 def getfilectx(repo, memctx, f):
125 def getfilectx(repo, memctx, f):
126 if lfutil.standin(f) in files:
126 if lfutil.standin(f) in files:
127 # if the file isn't in the manifest then it was removed
127 # if the file isn't in the manifest then it was removed
128 # or renamed, raise IOError to indicate this
128 # or renamed, raise IOError to indicate this
129 try:
129 try:
130 fctx = ctx.filectx(lfutil.standin(f))
130 fctx = ctx.filectx(lfutil.standin(f))
131 except error.LookupError:
131 except error.LookupError:
132 raise IOError()
132 raise IOError()
133 renamed = fctx.renamed()
133 renamed = fctx.renamed()
134 if renamed:
134 if renamed:
135 renamed = lfutil.splitstandin(renamed[0])
135 renamed = lfutil.splitstandin(renamed[0])
136
136
137 hash = fctx.data().strip()
137 hash = fctx.data().strip()
138 path = lfutil.findfile(rsrc, hash)
138 path = lfutil.findfile(rsrc, hash)
139 ### TODO: What if the file is not cached?
139 ### TODO: What if the file is not cached?
140 data = ''
140 data = ''
141 fd = None
141 fd = None
142 try:
142 try:
143 fd = open(path, 'rb')
143 fd = open(path, 'rb')
144 data = fd.read()
144 data = fd.read()
145 finally:
145 finally:
146 if fd:
146 if fd:
147 fd.close()
147 fd.close()
148 return context.memfilectx(f, data, 'l' in fctx.flags(),
148 return context.memfilectx(f, data, 'l' in fctx.flags(),
149 'x' in fctx.flags(), renamed)
149 'x' in fctx.flags(), renamed)
150 else:
150 else:
151 return _getnormalcontext(repo.ui, ctx, f, revmap)
151 return _getnormalcontext(repo.ui, ctx, f, revmap)
152
152
153 dstfiles = []
153 dstfiles = []
154 for file in files:
154 for file in files:
155 if lfutil.isstandin(file):
155 if lfutil.isstandin(file):
156 dstfiles.append(lfutil.splitstandin(file))
156 dstfiles.append(lfutil.splitstandin(file))
157 else:
157 else:
158 dstfiles.append(file)
158 dstfiles.append(file)
159 # Commit
159 # Commit
160 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
160 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
161
161
162 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
162 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
163 matcher, size, lfiletohash):
163 matcher, size, lfiletohash):
164 # Convert src parents to dst parents
164 # Convert src parents to dst parents
165 parents = _convertparents(ctx, revmap)
165 parents = _convertparents(ctx, revmap)
166
166
167 # Generate list of changed files
167 # Generate list of changed files
168 files = _getchangedfiles(ctx, parents)
168 files = _getchangedfiles(ctx, parents)
169
169
170 dstfiles = []
170 dstfiles = []
171 for f in files:
171 for f in files:
172 if f not in lfiles and f not in normalfiles:
172 if f not in lfiles and f not in normalfiles:
173 islfile = _islfile(f, ctx, matcher, size)
173 islfile = _islfile(f, ctx, matcher, size)
174 # If this file was renamed or copied then copy
174 # If this file was renamed or copied then copy
175 # the lfileness of its predecessor
175 # the lfileness of its predecessor
176 if f in ctx.manifest():
176 if f in ctx.manifest():
177 fctx = ctx.filectx(f)
177 fctx = ctx.filectx(f)
178 renamed = fctx.renamed()
178 renamed = fctx.renamed()
179 renamedlfile = renamed and renamed[0] in lfiles
179 renamedlfile = renamed and renamed[0] in lfiles
180 islfile |= renamedlfile
180 islfile |= renamedlfile
181 if 'l' in fctx.flags():
181 if 'l' in fctx.flags():
182 if renamedlfile:
182 if renamedlfile:
183 raise util.Abort(
183 raise util.Abort(
184 _('renamed/copied largefile %s becomes symlink')
184 _('renamed/copied largefile %s becomes symlink')
185 % f)
185 % f)
186 islfile = False
186 islfile = False
187 if islfile:
187 if islfile:
188 lfiles.add(f)
188 lfiles.add(f)
189 else:
189 else:
190 normalfiles.add(f)
190 normalfiles.add(f)
191
191
192 if f in lfiles:
192 if f in lfiles:
193 dstfiles.append(lfutil.standin(f))
193 dstfiles.append(lfutil.standin(f))
194 # largefile in manifest if it has not been removed/renamed
194 # largefile in manifest if it has not been removed/renamed
195 if f in ctx.manifest():
195 if f in ctx.manifest():
196 fctx = ctx.filectx(f)
196 fctx = ctx.filectx(f)
197 if 'l' in fctx.flags():
197 if 'l' in fctx.flags():
198 renamed = fctx.renamed()
198 renamed = fctx.renamed()
199 if renamed and renamed[0] in lfiles:
199 if renamed and renamed[0] in lfiles:
200 raise util.Abort(_('largefile %s becomes symlink') % f)
200 raise util.Abort(_('largefile %s becomes symlink') % f)
201
201
202 # largefile was modified, update standins
202 # largefile was modified, update standins
203 fullpath = rdst.wjoin(f)
203 fullpath = rdst.wjoin(f)
204 util.makedirs(os.path.dirname(fullpath))
204 util.makedirs(os.path.dirname(fullpath))
205 m = util.sha1('')
205 m = util.sha1('')
206 m.update(ctx[f].data())
206 m.update(ctx[f].data())
207 hash = m.hexdigest()
207 hash = m.hexdigest()
208 if f not in lfiletohash or lfiletohash[f] != hash:
208 if f not in lfiletohash or lfiletohash[f] != hash:
209 try:
209 try:
210 fd = open(fullpath, 'wb')
210 fd = open(fullpath, 'wb')
211 fd.write(ctx[f].data())
211 fd.write(ctx[f].data())
212 finally:
212 finally:
213 if fd:
213 if fd:
214 fd.close()
214 fd.close()
215 executable = 'x' in ctx[f].flags()
215 executable = 'x' in ctx[f].flags()
216 os.chmod(fullpath, lfutil.getmode(executable))
216 os.chmod(fullpath, lfutil.getmode(executable))
217 lfutil.writestandin(rdst, lfutil.standin(f), hash,
217 lfutil.writestandin(rdst, lfutil.standin(f), hash,
218 executable)
218 executable)
219 lfiletohash[f] = hash
219 lfiletohash[f] = hash
220 else:
220 else:
221 # normal file
221 # normal file
222 dstfiles.append(f)
222 dstfiles.append(f)
223
223
224 def getfilectx(repo, memctx, f):
224 def getfilectx(repo, memctx, f):
225 if lfutil.isstandin(f):
225 if lfutil.isstandin(f):
226 # if the file isn't in the manifest then it was removed
226 # if the file isn't in the manifest then it was removed
227 # or renamed, raise IOError to indicate this
227 # or renamed, raise IOError to indicate this
228 srcfname = lfutil.splitstandin(f)
228 srcfname = lfutil.splitstandin(f)
229 try:
229 try:
230 fctx = ctx.filectx(srcfname)
230 fctx = ctx.filectx(srcfname)
231 except error.LookupError:
231 except error.LookupError:
232 raise IOError()
232 raise IOError()
233 renamed = fctx.renamed()
233 renamed = fctx.renamed()
234 if renamed:
234 if renamed:
235 # standin is always a largefile because largefile-ness
235 # standin is always a largefile because largefile-ness
236 # doesn't change after rename or copy
236 # doesn't change after rename or copy
237 renamed = lfutil.standin(renamed[0])
237 renamed = lfutil.standin(renamed[0])
238
238
239 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
239 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
240 fctx.flags(), 'x' in fctx.flags(), renamed)
240 fctx.flags(), 'x' in fctx.flags(), renamed)
241 else:
241 else:
242 return _getnormalcontext(repo.ui, ctx, f, revmap)
242 return _getnormalcontext(repo.ui, ctx, f, revmap)
243
243
244 # Commit
244 # Commit
245 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
245 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
246
246
247 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
247 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
248 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
248 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
249 getfilectx, ctx.user(), ctx.date(), ctx.extra())
249 getfilectx, ctx.user(), ctx.date(), ctx.extra())
250 ret = rdst.commitctx(mctx)
250 ret = rdst.commitctx(mctx)
251 rdst.dirstate.setparents(ret)
251 rdst.setparents(ret)
252 revmap[ctx.node()] = rdst.changelog.tip()
252 revmap[ctx.node()] = rdst.changelog.tip()
253
253
254 # Generate list of changed files
254 # Generate list of changed files
255 def _getchangedfiles(ctx, parents):
255 def _getchangedfiles(ctx, parents):
256 files = set(ctx.files())
256 files = set(ctx.files())
257 if node.nullid not in parents:
257 if node.nullid not in parents:
258 mc = ctx.manifest()
258 mc = ctx.manifest()
259 mp1 = ctx.parents()[0].manifest()
259 mp1 = ctx.parents()[0].manifest()
260 mp2 = ctx.parents()[1].manifest()
260 mp2 = ctx.parents()[1].manifest()
261 files |= (set(mp1) | set(mp2)) - set(mc)
261 files |= (set(mp1) | set(mp2)) - set(mc)
262 for f in mc:
262 for f in mc:
263 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
263 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
264 files.add(f)
264 files.add(f)
265 return files
265 return files
266
266
267 # Convert src parents to dst parents
267 # Convert src parents to dst parents
268 def _convertparents(ctx, revmap):
268 def _convertparents(ctx, revmap):
269 parents = []
269 parents = []
270 for p in ctx.parents():
270 for p in ctx.parents():
271 parents.append(revmap[p.node()])
271 parents.append(revmap[p.node()])
272 while len(parents) < 2:
272 while len(parents) < 2:
273 parents.append(node.nullid)
273 parents.append(node.nullid)
274 return parents
274 return parents
275
275
276 # Get memfilectx for a normal file
276 # Get memfilectx for a normal file
277 def _getnormalcontext(ui, ctx, f, revmap):
277 def _getnormalcontext(ui, ctx, f, revmap):
278 try:
278 try:
279 fctx = ctx.filectx(f)
279 fctx = ctx.filectx(f)
280 except error.LookupError:
280 except error.LookupError:
281 raise IOError()
281 raise IOError()
282 renamed = fctx.renamed()
282 renamed = fctx.renamed()
283 if renamed:
283 if renamed:
284 renamed = renamed[0]
284 renamed = renamed[0]
285
285
286 data = fctx.data()
286 data = fctx.data()
287 if f == '.hgtags':
287 if f == '.hgtags':
288 data = _converttags (ui, revmap, data)
288 data = _converttags (ui, revmap, data)
289 return context.memfilectx(f, data, 'l' in fctx.flags(),
289 return context.memfilectx(f, data, 'l' in fctx.flags(),
290 'x' in fctx.flags(), renamed)
290 'x' in fctx.flags(), renamed)
291
291
292 # Remap tag data using a revision map
292 # Remap tag data using a revision map
293 def _converttags(ui, revmap, data):
293 def _converttags(ui, revmap, data):
294 newdata = []
294 newdata = []
295 for line in data.splitlines():
295 for line in data.splitlines():
296 try:
296 try:
297 id, name = line.split(' ', 1)
297 id, name = line.split(' ', 1)
298 except ValueError:
298 except ValueError:
299 ui.warn(_('skipping incorrectly formatted tag %s\n'
299 ui.warn(_('skipping incorrectly formatted tag %s\n'
300 % line))
300 % line))
301 continue
301 continue
302 try:
302 try:
303 newid = node.bin(id)
303 newid = node.bin(id)
304 except TypeError:
304 except TypeError:
305 ui.warn(_('skipping incorrectly formatted id %s\n'
305 ui.warn(_('skipping incorrectly formatted id %s\n'
306 % id))
306 % id))
307 continue
307 continue
308 try:
308 try:
309 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
309 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
310 name))
310 name))
311 except KeyError:
311 except KeyError:
312 ui.warn(_('no mapping for id %s\n') % id)
312 ui.warn(_('no mapping for id %s\n') % id)
313 continue
313 continue
314 return ''.join(newdata)
314 return ''.join(newdata)
315
315
316 def _islfile(file, ctx, matcher, size):
316 def _islfile(file, ctx, matcher, size):
317 '''Return true if file should be considered a largefile, i.e.
317 '''Return true if file should be considered a largefile, i.e.
318 matcher matches it or it is larger than size.'''
318 matcher matches it or it is larger than size.'''
319 # never store special .hg* files as largefiles
319 # never store special .hg* files as largefiles
320 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
320 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
321 return False
321 return False
322 if matcher and matcher(file):
322 if matcher and matcher(file):
323 return True
323 return True
324 try:
324 try:
325 return ctx.filectx(file).size() >= size * 1024 * 1024
325 return ctx.filectx(file).size() >= size * 1024 * 1024
326 except error.LookupError:
326 except error.LookupError:
327 return False
327 return False
328
328
329 def uploadlfiles(ui, rsrc, rdst, files):
329 def uploadlfiles(ui, rsrc, rdst, files):
330 '''upload largefiles to the central store'''
330 '''upload largefiles to the central store'''
331
331
332 if not files:
332 if not files:
333 return
333 return
334
334
335 store = basestore._openstore(rsrc, rdst, put=True)
335 store = basestore._openstore(rsrc, rdst, put=True)
336
336
337 at = 0
337 at = 0
338 files = filter(lambda h: not store.exists(h), files)
338 files = filter(lambda h: not store.exists(h), files)
339 for hash in files:
339 for hash in files:
340 ui.progress(_('uploading largefiles'), at, unit='largefile',
340 ui.progress(_('uploading largefiles'), at, unit='largefile',
341 total=len(files))
341 total=len(files))
342 source = lfutil.findfile(rsrc, hash)
342 source = lfutil.findfile(rsrc, hash)
343 if not source:
343 if not source:
344 raise util.Abort(_('largefile %s missing from store'
344 raise util.Abort(_('largefile %s missing from store'
345 ' (needs to be uploaded)') % hash)
345 ' (needs to be uploaded)') % hash)
346 # XXX check for errors here
346 # XXX check for errors here
347 store.put(source, hash)
347 store.put(source, hash)
348 at += 1
348 at += 1
349 ui.progress(_('uploading largefiles'), None)
349 ui.progress(_('uploading largefiles'), None)
350
350
351 def verifylfiles(ui, repo, all=False, contents=False):
351 def verifylfiles(ui, repo, all=False, contents=False):
352 '''Verify that every big file revision in the current changeset
352 '''Verify that every big file revision in the current changeset
353 exists in the central store. With --contents, also verify that
353 exists in the central store. With --contents, also verify that
354 the contents of each big file revision are correct (SHA-1 hash
354 the contents of each big file revision are correct (SHA-1 hash
355 matches the revision ID). With --all, check every changeset in
355 matches the revision ID). With --all, check every changeset in
356 this repository.'''
356 this repository.'''
357 if all:
357 if all:
358 # Pass a list to the function rather than an iterator because we know a
358 # Pass a list to the function rather than an iterator because we know a
359 # list will work.
359 # list will work.
360 revs = range(len(repo))
360 revs = range(len(repo))
361 else:
361 else:
362 revs = ['.']
362 revs = ['.']
363
363
364 store = basestore._openstore(repo)
364 store = basestore._openstore(repo)
365 return store.verify(revs, contents=contents)
365 return store.verify(revs, contents=contents)
366
366
367 def cachelfiles(ui, repo, node):
367 def cachelfiles(ui, repo, node):
368 '''cachelfiles ensures that all largefiles needed by the specified revision
368 '''cachelfiles ensures that all largefiles needed by the specified revision
369 are present in the repository's largefile cache.
369 are present in the repository's largefile cache.
370
370
371 returns a tuple (cached, missing). cached is the list of files downloaded
371 returns a tuple (cached, missing). cached is the list of files downloaded
372 by this operation; missing is the list of files that were needed but could
372 by this operation; missing is the list of files that were needed but could
373 not be found.'''
373 not be found.'''
374 lfiles = lfutil.listlfiles(repo, node)
374 lfiles = lfutil.listlfiles(repo, node)
375 toget = []
375 toget = []
376
376
377 for lfile in lfiles:
377 for lfile in lfiles:
378 # If we are mid-merge, then we have to trust the standin that is in the
378 # If we are mid-merge, then we have to trust the standin that is in the
379 # working copy to have the correct hashvalue. This is because the
379 # working copy to have the correct hashvalue. This is because the
380 # original hg.merge() already updated the standin as part of the normal
380 # original hg.merge() already updated the standin as part of the normal
381 # merge process -- we just have to udpate the largefile to match.
381 # merge process -- we just have to udpate the largefile to match.
382 if (getattr(repo, "_ismerging", False) and
382 if (getattr(repo, "_ismerging", False) and
383 os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
383 os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
384 expectedhash = lfutil.readstandin(repo, lfile)
384 expectedhash = lfutil.readstandin(repo, lfile)
385 else:
385 else:
386 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
386 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
387
387
388 # if it exists and its hash matches, it might have been locally
388 # if it exists and its hash matches, it might have been locally
389 # modified before updating and the user chose 'local'. in this case,
389 # modified before updating and the user chose 'local'. in this case,
390 # it will not be in any store, so don't look for it.
390 # it will not be in any store, so don't look for it.
391 if ((not os.path.exists(repo.wjoin(lfile)) or
391 if ((not os.path.exists(repo.wjoin(lfile)) or
392 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
392 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
393 not lfutil.findfile(repo, expectedhash)):
393 not lfutil.findfile(repo, expectedhash)):
394 toget.append((lfile, expectedhash))
394 toget.append((lfile, expectedhash))
395
395
396 if toget:
396 if toget:
397 store = basestore._openstore(repo)
397 store = basestore._openstore(repo)
398 ret = store.get(toget)
398 ret = store.get(toget)
399 return ret
399 return ret
400
400
401 return ([], [])
401 return ([], [])
402
402
403 def updatelfiles(ui, repo, filelist=None, printmessage=True):
403 def updatelfiles(ui, repo, filelist=None, printmessage=True):
404 wlock = repo.wlock()
404 wlock = repo.wlock()
405 try:
405 try:
406 lfdirstate = lfutil.openlfdirstate(ui, repo)
406 lfdirstate = lfutil.openlfdirstate(ui, repo)
407 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
407 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
408
408
409 if filelist is not None:
409 if filelist is not None:
410 lfiles = [f for f in lfiles if f in filelist]
410 lfiles = [f for f in lfiles if f in filelist]
411
411
412 printed = False
412 printed = False
413 if printmessage and lfiles:
413 if printmessage and lfiles:
414 ui.status(_('getting changed largefiles\n'))
414 ui.status(_('getting changed largefiles\n'))
415 printed = True
415 printed = True
416 cachelfiles(ui, repo, '.')
416 cachelfiles(ui, repo, '.')
417
417
418 updated, removed = 0, 0
418 updated, removed = 0, 0
419 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
419 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
420 # increment the appropriate counter according to _updatelfile's
420 # increment the appropriate counter according to _updatelfile's
421 # return value
421 # return value
422 updated += i > 0 and i or 0
422 updated += i > 0 and i or 0
423 removed -= i < 0 and i or 0
423 removed -= i < 0 and i or 0
424 if printmessage and (removed or updated) and not printed:
424 if printmessage and (removed or updated) and not printed:
425 ui.status(_('getting changed largefiles\n'))
425 ui.status(_('getting changed largefiles\n'))
426 printed = True
426 printed = True
427
427
428 lfdirstate.write()
428 lfdirstate.write()
429 if printed and printmessage:
429 if printed and printmessage:
430 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
430 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
431 removed))
431 removed))
432 finally:
432 finally:
433 wlock.release()
433 wlock.release()
434
434
435 def _updatelfile(repo, lfdirstate, lfile):
435 def _updatelfile(repo, lfdirstate, lfile):
436 '''updates a single largefile and copies the state of its standin from
436 '''updates a single largefile and copies the state of its standin from
437 the repository's dirstate to its state in the lfdirstate.
437 the repository's dirstate to its state in the lfdirstate.
438
438
439 returns 1 if the file was modified, -1 if the file was removed, 0 if the
439 returns 1 if the file was modified, -1 if the file was removed, 0 if the
440 file was unchanged, and None if the needed largefile was missing from the
440 file was unchanged, and None if the needed largefile was missing from the
441 cache.'''
441 cache.'''
442 ret = 0
442 ret = 0
443 abslfile = repo.wjoin(lfile)
443 abslfile = repo.wjoin(lfile)
444 absstandin = repo.wjoin(lfutil.standin(lfile))
444 absstandin = repo.wjoin(lfutil.standin(lfile))
445 if os.path.exists(absstandin):
445 if os.path.exists(absstandin):
446 if os.path.exists(absstandin+'.orig'):
446 if os.path.exists(absstandin+'.orig'):
447 shutil.copyfile(abslfile, abslfile+'.orig')
447 shutil.copyfile(abslfile, abslfile+'.orig')
448 expecthash = lfutil.readstandin(repo, lfile)
448 expecthash = lfutil.readstandin(repo, lfile)
449 if (expecthash != '' and
449 if (expecthash != '' and
450 (not os.path.exists(abslfile) or
450 (not os.path.exists(abslfile) or
451 expecthash != lfutil.hashfile(abslfile))):
451 expecthash != lfutil.hashfile(abslfile))):
452 if not lfutil.copyfromcache(repo, expecthash, lfile):
452 if not lfutil.copyfromcache(repo, expecthash, lfile):
453 # use normallookup() to allocate entry in largefiles dirstate,
453 # use normallookup() to allocate entry in largefiles dirstate,
454 # because lack of it misleads lfilesrepo.status() into
454 # because lack of it misleads lfilesrepo.status() into
455 # recognition that such cache missing files are REMOVED.
455 # recognition that such cache missing files are REMOVED.
456 lfdirstate.normallookup(lfile)
456 lfdirstate.normallookup(lfile)
457 return None # don't try to set the mode
457 return None # don't try to set the mode
458 ret = 1
458 ret = 1
459 mode = os.stat(absstandin).st_mode
459 mode = os.stat(absstandin).st_mode
460 if mode != os.stat(abslfile).st_mode:
460 if mode != os.stat(abslfile).st_mode:
461 os.chmod(abslfile, mode)
461 os.chmod(abslfile, mode)
462 ret = 1
462 ret = 1
463 else:
463 else:
464 # Remove lfiles for which the standin is deleted, unless the
464 # Remove lfiles for which the standin is deleted, unless the
465 # lfile is added to the repository again. This happens when a
465 # lfile is added to the repository again. This happens when a
466 # largefile is converted back to a normal file: the standin
466 # largefile is converted back to a normal file: the standin
467 # disappears, but a new (normal) file appears as the lfile.
467 # disappears, but a new (normal) file appears as the lfile.
468 if os.path.exists(abslfile) and lfile not in repo[None]:
468 if os.path.exists(abslfile) and lfile not in repo[None]:
469 util.unlinkpath(abslfile)
469 util.unlinkpath(abslfile)
470 ret = -1
470 ret = -1
471 state = repo.dirstate[lfutil.standin(lfile)]
471 state = repo.dirstate[lfutil.standin(lfile)]
472 if state == 'n':
472 if state == 'n':
473 # When rebasing, we need to synchronize the standin and the largefile,
473 # When rebasing, we need to synchronize the standin and the largefile,
474 # because otherwise the largefile will get reverted. But for commit's
474 # because otherwise the largefile will get reverted. But for commit's
475 # sake, we have to mark the file as unclean.
475 # sake, we have to mark the file as unclean.
476 if getattr(repo, "_isrebasing", False):
476 if getattr(repo, "_isrebasing", False):
477 lfdirstate.normallookup(lfile)
477 lfdirstate.normallookup(lfile)
478 else:
478 else:
479 lfdirstate.normal(lfile)
479 lfdirstate.normal(lfile)
480 elif state == 'r':
480 elif state == 'r':
481 lfdirstate.remove(lfile)
481 lfdirstate.remove(lfile)
482 elif state == 'a':
482 elif state == 'a':
483 lfdirstate.add(lfile)
483 lfdirstate.add(lfile)
484 elif state == '?':
484 elif state == '?':
485 lfdirstate.drop(lfile)
485 lfdirstate.drop(lfile)
486 return ret
486 return ret
487
487
488 def catlfile(repo, lfile, rev, filename):
488 def catlfile(repo, lfile, rev, filename):
489 hash = lfutil.readstandin(repo, lfile, rev)
489 hash = lfutil.readstandin(repo, lfile, rev)
490 if not lfutil.inusercache(repo.ui, hash):
490 if not lfutil.inusercache(repo.ui, hash):
491 store = basestore._openstore(repo)
491 store = basestore._openstore(repo)
492 success, missing = store.get([(lfile, hash)])
492 success, missing = store.get([(lfile, hash)])
493 if len(success) != 1:
493 if len(success) != 1:
494 raise util.Abort(
494 raise util.Abort(
495 _('largefile %s is not in cache and could not be downloaded')
495 _('largefile %s is not in cache and could not be downloaded')
496 % lfile)
496 % lfile)
497 path = lfutil.usercachepath(repo.ui, hash)
497 path = lfutil.usercachepath(repo.ui, hash)
498 fpout = cmdutil.makefileobj(repo, filename)
498 fpout = cmdutil.makefileobj(repo, filename)
499 fpin = open(path, "rb")
499 fpin = open(path, "rb")
500 fpout.write(fpin.read())
500 fpout.write(fpin.read())
501 fpout.close()
501 fpout.close()
502 fpin.close()
502 fpin.close()
503 return 0
503 return 0
504
504
505 # -- hg commands declarations ------------------------------------------------
505 # -- hg commands declarations ------------------------------------------------
506
506
507 cmdtable = {
507 cmdtable = {
508 'lfconvert': (lfconvert,
508 'lfconvert': (lfconvert,
509 [('s', 'size', '',
509 [('s', 'size', '',
510 _('minimum size (MB) for files to be converted '
510 _('minimum size (MB) for files to be converted '
511 'as largefiles'),
511 'as largefiles'),
512 'SIZE'),
512 'SIZE'),
513 ('', 'to-normal', False,
513 ('', 'to-normal', False,
514 _('convert from a largefiles repo to a normal repo')),
514 _('convert from a largefiles repo to a normal repo')),
515 ],
515 ],
516 _('hg lfconvert SOURCE DEST [FILE ...]')),
516 _('hg lfconvert SOURCE DEST [FILE ...]')),
517 }
517 }
@@ -1,3419 +1,3419 b''
1 # mq.py - patch queues for mercurial
1 # mq.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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
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 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49 '''
49 '''
50
50
51 from mercurial.i18n import _
51 from mercurial.i18n import _
52 from mercurial.node import bin, hex, short, nullid, nullrev
52 from mercurial.node import bin, hex, short, nullid, nullrev
53 from mercurial.lock import release
53 from mercurial.lock import release
54 from mercurial import commands, cmdutil, hg, scmutil, util, revset
54 from mercurial import commands, cmdutil, hg, scmutil, util, revset
55 from mercurial import repair, extensions, url, error, phases
55 from mercurial import repair, extensions, url, error, phases
56 from mercurial import patch as patchmod
56 from mercurial import patch as patchmod
57 import os, re, errno, shutil
57 import os, re, errno, shutil
58
58
59 commands.norepo += " qclone"
59 commands.norepo += " qclone"
60
60
61 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
61 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
62
62
63 cmdtable = {}
63 cmdtable = {}
64 command = cmdutil.command(cmdtable)
64 command = cmdutil.command(cmdtable)
65
65
66 # Patch names looks like unix-file names.
66 # Patch names looks like unix-file names.
67 # They must be joinable with queue directory and result in the patch path.
67 # They must be joinable with queue directory and result in the patch path.
68 normname = util.normpath
68 normname = util.normpath
69
69
70 class statusentry(object):
70 class statusentry(object):
71 def __init__(self, node, name):
71 def __init__(self, node, name):
72 self.node, self.name = node, name
72 self.node, self.name = node, name
73 def __repr__(self):
73 def __repr__(self):
74 return hex(self.node) + ':' + self.name
74 return hex(self.node) + ':' + self.name
75
75
76 class patchheader(object):
76 class patchheader(object):
77 def __init__(self, pf, plainmode=False):
77 def __init__(self, pf, plainmode=False):
78 def eatdiff(lines):
78 def eatdiff(lines):
79 while lines:
79 while lines:
80 l = lines[-1]
80 l = lines[-1]
81 if (l.startswith("diff -") or
81 if (l.startswith("diff -") or
82 l.startswith("Index:") or
82 l.startswith("Index:") or
83 l.startswith("===========")):
83 l.startswith("===========")):
84 del lines[-1]
84 del lines[-1]
85 else:
85 else:
86 break
86 break
87 def eatempty(lines):
87 def eatempty(lines):
88 while lines:
88 while lines:
89 if not lines[-1].strip():
89 if not lines[-1].strip():
90 del lines[-1]
90 del lines[-1]
91 else:
91 else:
92 break
92 break
93
93
94 message = []
94 message = []
95 comments = []
95 comments = []
96 user = None
96 user = None
97 date = None
97 date = None
98 parent = None
98 parent = None
99 format = None
99 format = None
100 subject = None
100 subject = None
101 branch = None
101 branch = None
102 nodeid = None
102 nodeid = None
103 diffstart = 0
103 diffstart = 0
104
104
105 for line in file(pf):
105 for line in file(pf):
106 line = line.rstrip()
106 line = line.rstrip()
107 if (line.startswith('diff --git')
107 if (line.startswith('diff --git')
108 or (diffstart and line.startswith('+++ '))):
108 or (diffstart and line.startswith('+++ '))):
109 diffstart = 2
109 diffstart = 2
110 break
110 break
111 diffstart = 0 # reset
111 diffstart = 0 # reset
112 if line.startswith("--- "):
112 if line.startswith("--- "):
113 diffstart = 1
113 diffstart = 1
114 continue
114 continue
115 elif format == "hgpatch":
115 elif format == "hgpatch":
116 # parse values when importing the result of an hg export
116 # parse values when importing the result of an hg export
117 if line.startswith("# User "):
117 if line.startswith("# User "):
118 user = line[7:]
118 user = line[7:]
119 elif line.startswith("# Date "):
119 elif line.startswith("# Date "):
120 date = line[7:]
120 date = line[7:]
121 elif line.startswith("# Parent "):
121 elif line.startswith("# Parent "):
122 parent = line[9:].lstrip()
122 parent = line[9:].lstrip()
123 elif line.startswith("# Branch "):
123 elif line.startswith("# Branch "):
124 branch = line[9:]
124 branch = line[9:]
125 elif line.startswith("# Node ID "):
125 elif line.startswith("# Node ID "):
126 nodeid = line[10:]
126 nodeid = line[10:]
127 elif not line.startswith("# ") and line:
127 elif not line.startswith("# ") and line:
128 message.append(line)
128 message.append(line)
129 format = None
129 format = None
130 elif line == '# HG changeset patch':
130 elif line == '# HG changeset patch':
131 message = []
131 message = []
132 format = "hgpatch"
132 format = "hgpatch"
133 elif (format != "tagdone" and (line.startswith("Subject: ") or
133 elif (format != "tagdone" and (line.startswith("Subject: ") or
134 line.startswith("subject: "))):
134 line.startswith("subject: "))):
135 subject = line[9:]
135 subject = line[9:]
136 format = "tag"
136 format = "tag"
137 elif (format != "tagdone" and (line.startswith("From: ") or
137 elif (format != "tagdone" and (line.startswith("From: ") or
138 line.startswith("from: "))):
138 line.startswith("from: "))):
139 user = line[6:]
139 user = line[6:]
140 format = "tag"
140 format = "tag"
141 elif (format != "tagdone" and (line.startswith("Date: ") or
141 elif (format != "tagdone" and (line.startswith("Date: ") or
142 line.startswith("date: "))):
142 line.startswith("date: "))):
143 date = line[6:]
143 date = line[6:]
144 format = "tag"
144 format = "tag"
145 elif format == "tag" and line == "":
145 elif format == "tag" and line == "":
146 # when looking for tags (subject: from: etc) they
146 # when looking for tags (subject: from: etc) they
147 # end once you find a blank line in the source
147 # end once you find a blank line in the source
148 format = "tagdone"
148 format = "tagdone"
149 elif message or line:
149 elif message or line:
150 message.append(line)
150 message.append(line)
151 comments.append(line)
151 comments.append(line)
152
152
153 eatdiff(message)
153 eatdiff(message)
154 eatdiff(comments)
154 eatdiff(comments)
155 # Remember the exact starting line of the patch diffs before consuming
155 # Remember the exact starting line of the patch diffs before consuming
156 # empty lines, for external use by TortoiseHg and others
156 # empty lines, for external use by TortoiseHg and others
157 self.diffstartline = len(comments)
157 self.diffstartline = len(comments)
158 eatempty(message)
158 eatempty(message)
159 eatempty(comments)
159 eatempty(comments)
160
160
161 # make sure message isn't empty
161 # make sure message isn't empty
162 if format and format.startswith("tag") and subject:
162 if format and format.startswith("tag") and subject:
163 message.insert(0, "")
163 message.insert(0, "")
164 message.insert(0, subject)
164 message.insert(0, subject)
165
165
166 self.message = message
166 self.message = message
167 self.comments = comments
167 self.comments = comments
168 self.user = user
168 self.user = user
169 self.date = date
169 self.date = date
170 self.parent = parent
170 self.parent = parent
171 # nodeid and branch are for external use by TortoiseHg and others
171 # nodeid and branch are for external use by TortoiseHg and others
172 self.nodeid = nodeid
172 self.nodeid = nodeid
173 self.branch = branch
173 self.branch = branch
174 self.haspatch = diffstart > 1
174 self.haspatch = diffstart > 1
175 self.plainmode = plainmode
175 self.plainmode = plainmode
176
176
177 def setuser(self, user):
177 def setuser(self, user):
178 if not self.updateheader(['From: ', '# User '], user):
178 if not self.updateheader(['From: ', '# User '], user):
179 try:
179 try:
180 patchheaderat = self.comments.index('# HG changeset patch')
180 patchheaderat = self.comments.index('# HG changeset patch')
181 self.comments.insert(patchheaderat + 1, '# User ' + user)
181 self.comments.insert(patchheaderat + 1, '# User ' + user)
182 except ValueError:
182 except ValueError:
183 if self.plainmode or self._hasheader(['Date: ']):
183 if self.plainmode or self._hasheader(['Date: ']):
184 self.comments = ['From: ' + user] + self.comments
184 self.comments = ['From: ' + user] + self.comments
185 else:
185 else:
186 tmp = ['# HG changeset patch', '# User ' + user, '']
186 tmp = ['# HG changeset patch', '# User ' + user, '']
187 self.comments = tmp + self.comments
187 self.comments = tmp + self.comments
188 self.user = user
188 self.user = user
189
189
190 def setdate(self, date):
190 def setdate(self, date):
191 if not self.updateheader(['Date: ', '# Date '], date):
191 if not self.updateheader(['Date: ', '# Date '], date):
192 try:
192 try:
193 patchheaderat = self.comments.index('# HG changeset patch')
193 patchheaderat = self.comments.index('# HG changeset patch')
194 self.comments.insert(patchheaderat + 1, '# Date ' + date)
194 self.comments.insert(patchheaderat + 1, '# Date ' + date)
195 except ValueError:
195 except ValueError:
196 if self.plainmode or self._hasheader(['From: ']):
196 if self.plainmode or self._hasheader(['From: ']):
197 self.comments = ['Date: ' + date] + self.comments
197 self.comments = ['Date: ' + date] + self.comments
198 else:
198 else:
199 tmp = ['# HG changeset patch', '# Date ' + date, '']
199 tmp = ['# HG changeset patch', '# Date ' + date, '']
200 self.comments = tmp + self.comments
200 self.comments = tmp + self.comments
201 self.date = date
201 self.date = date
202
202
203 def setparent(self, parent):
203 def setparent(self, parent):
204 if not self.updateheader(['# Parent '], parent):
204 if not self.updateheader(['# Parent '], parent):
205 try:
205 try:
206 patchheaderat = self.comments.index('# HG changeset patch')
206 patchheaderat = self.comments.index('# HG changeset patch')
207 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
207 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
208 except ValueError:
208 except ValueError:
209 pass
209 pass
210 self.parent = parent
210 self.parent = parent
211
211
212 def setmessage(self, message):
212 def setmessage(self, message):
213 if self.comments:
213 if self.comments:
214 self._delmsg()
214 self._delmsg()
215 self.message = [message]
215 self.message = [message]
216 self.comments += self.message
216 self.comments += self.message
217
217
218 def updateheader(self, prefixes, new):
218 def updateheader(self, prefixes, new):
219 '''Update all references to a field in the patch header.
219 '''Update all references to a field in the patch header.
220 Return whether the field is present.'''
220 Return whether the field is present.'''
221 res = False
221 res = False
222 for prefix in prefixes:
222 for prefix in prefixes:
223 for i in xrange(len(self.comments)):
223 for i in xrange(len(self.comments)):
224 if self.comments[i].startswith(prefix):
224 if self.comments[i].startswith(prefix):
225 self.comments[i] = prefix + new
225 self.comments[i] = prefix + new
226 res = True
226 res = True
227 break
227 break
228 return res
228 return res
229
229
230 def _hasheader(self, prefixes):
230 def _hasheader(self, prefixes):
231 '''Check if a header starts with any of the given prefixes.'''
231 '''Check if a header starts with any of the given prefixes.'''
232 for prefix in prefixes:
232 for prefix in prefixes:
233 for comment in self.comments:
233 for comment in self.comments:
234 if comment.startswith(prefix):
234 if comment.startswith(prefix):
235 return True
235 return True
236 return False
236 return False
237
237
238 def __str__(self):
238 def __str__(self):
239 if not self.comments:
239 if not self.comments:
240 return ''
240 return ''
241 return '\n'.join(self.comments) + '\n\n'
241 return '\n'.join(self.comments) + '\n\n'
242
242
243 def _delmsg(self):
243 def _delmsg(self):
244 '''Remove existing message, keeping the rest of the comments fields.
244 '''Remove existing message, keeping the rest of the comments fields.
245 If comments contains 'subject: ', message will prepend
245 If comments contains 'subject: ', message will prepend
246 the field and a blank line.'''
246 the field and a blank line.'''
247 if self.message:
247 if self.message:
248 subj = 'subject: ' + self.message[0].lower()
248 subj = 'subject: ' + self.message[0].lower()
249 for i in xrange(len(self.comments)):
249 for i in xrange(len(self.comments)):
250 if subj == self.comments[i].lower():
250 if subj == self.comments[i].lower():
251 del self.comments[i]
251 del self.comments[i]
252 self.message = self.message[2:]
252 self.message = self.message[2:]
253 break
253 break
254 ci = 0
254 ci = 0
255 for mi in self.message:
255 for mi in self.message:
256 while mi != self.comments[ci]:
256 while mi != self.comments[ci]:
257 ci += 1
257 ci += 1
258 del self.comments[ci]
258 del self.comments[ci]
259
259
260 def newcommit(repo, phase, *args, **kwargs):
260 def newcommit(repo, phase, *args, **kwargs):
261 """helper dedicated to ensure a commit respect mq.secret setting
261 """helper dedicated to ensure a commit respect mq.secret setting
262
262
263 It should be used instead of repo.commit inside the mq source for operation
263 It should be used instead of repo.commit inside the mq source for operation
264 creating new changeset.
264 creating new changeset.
265 """
265 """
266 if phase is None:
266 if phase is None:
267 if repo.ui.configbool('mq', 'secret', False):
267 if repo.ui.configbool('mq', 'secret', False):
268 phase = phases.secret
268 phase = phases.secret
269 if phase is not None:
269 if phase is not None:
270 backup = repo.ui.backupconfig('phases', 'new-commit')
270 backup = repo.ui.backupconfig('phases', 'new-commit')
271 # Marking the repository as committing an mq patch can be used
271 # Marking the repository as committing an mq patch can be used
272 # to optimize operations like _branchtags().
272 # to optimize operations like _branchtags().
273 repo._committingpatch = True
273 repo._committingpatch = True
274 try:
274 try:
275 if phase is not None:
275 if phase is not None:
276 repo.ui.setconfig('phases', 'new-commit', phase)
276 repo.ui.setconfig('phases', 'new-commit', phase)
277 return repo.commit(*args, **kwargs)
277 return repo.commit(*args, **kwargs)
278 finally:
278 finally:
279 repo._committingpatch = False
279 repo._committingpatch = False
280 if phase is not None:
280 if phase is not None:
281 repo.ui.restoreconfig(backup)
281 repo.ui.restoreconfig(backup)
282
282
283 class queue(object):
283 class queue(object):
284 def __init__(self, ui, path, patchdir=None):
284 def __init__(self, ui, path, patchdir=None):
285 self.basepath = path
285 self.basepath = path
286 try:
286 try:
287 fh = open(os.path.join(path, 'patches.queue'))
287 fh = open(os.path.join(path, 'patches.queue'))
288 cur = fh.read().rstrip()
288 cur = fh.read().rstrip()
289 fh.close()
289 fh.close()
290 if not cur:
290 if not cur:
291 curpath = os.path.join(path, 'patches')
291 curpath = os.path.join(path, 'patches')
292 else:
292 else:
293 curpath = os.path.join(path, 'patches-' + cur)
293 curpath = os.path.join(path, 'patches-' + cur)
294 except IOError:
294 except IOError:
295 curpath = os.path.join(path, 'patches')
295 curpath = os.path.join(path, 'patches')
296 self.path = patchdir or curpath
296 self.path = patchdir or curpath
297 self.opener = scmutil.opener(self.path)
297 self.opener = scmutil.opener(self.path)
298 self.ui = ui
298 self.ui = ui
299 self.applieddirty = False
299 self.applieddirty = False
300 self.seriesdirty = False
300 self.seriesdirty = False
301 self.added = []
301 self.added = []
302 self.seriespath = "series"
302 self.seriespath = "series"
303 self.statuspath = "status"
303 self.statuspath = "status"
304 self.guardspath = "guards"
304 self.guardspath = "guards"
305 self.activeguards = None
305 self.activeguards = None
306 self.guardsdirty = False
306 self.guardsdirty = False
307 # Handle mq.git as a bool with extended values
307 # Handle mq.git as a bool with extended values
308 try:
308 try:
309 gitmode = ui.configbool('mq', 'git', None)
309 gitmode = ui.configbool('mq', 'git', None)
310 if gitmode is None:
310 if gitmode is None:
311 raise error.ConfigError()
311 raise error.ConfigError()
312 self.gitmode = gitmode and 'yes' or 'no'
312 self.gitmode = gitmode and 'yes' or 'no'
313 except error.ConfigError:
313 except error.ConfigError:
314 self.gitmode = ui.config('mq', 'git', 'auto').lower()
314 self.gitmode = ui.config('mq', 'git', 'auto').lower()
315 self.plainmode = ui.configbool('mq', 'plain', False)
315 self.plainmode = ui.configbool('mq', 'plain', False)
316
316
317 @util.propertycache
317 @util.propertycache
318 def applied(self):
318 def applied(self):
319 def parselines(lines):
319 def parselines(lines):
320 for l in lines:
320 for l in lines:
321 entry = l.split(':', 1)
321 entry = l.split(':', 1)
322 if len(entry) > 1:
322 if len(entry) > 1:
323 n, name = entry
323 n, name = entry
324 yield statusentry(bin(n), name)
324 yield statusentry(bin(n), name)
325 elif l.strip():
325 elif l.strip():
326 self.ui.warn(_('malformated mq status line: %s\n') % entry)
326 self.ui.warn(_('malformated mq status line: %s\n') % entry)
327 # else we ignore empty lines
327 # else we ignore empty lines
328 try:
328 try:
329 lines = self.opener.read(self.statuspath).splitlines()
329 lines = self.opener.read(self.statuspath).splitlines()
330 return list(parselines(lines))
330 return list(parselines(lines))
331 except IOError, e:
331 except IOError, e:
332 if e.errno == errno.ENOENT:
332 if e.errno == errno.ENOENT:
333 return []
333 return []
334 raise
334 raise
335
335
336 @util.propertycache
336 @util.propertycache
337 def fullseries(self):
337 def fullseries(self):
338 try:
338 try:
339 return self.opener.read(self.seriespath).splitlines()
339 return self.opener.read(self.seriespath).splitlines()
340 except IOError, e:
340 except IOError, e:
341 if e.errno == errno.ENOENT:
341 if e.errno == errno.ENOENT:
342 return []
342 return []
343 raise
343 raise
344
344
345 @util.propertycache
345 @util.propertycache
346 def series(self):
346 def series(self):
347 self.parseseries()
347 self.parseseries()
348 return self.series
348 return self.series
349
349
350 @util.propertycache
350 @util.propertycache
351 def seriesguards(self):
351 def seriesguards(self):
352 self.parseseries()
352 self.parseseries()
353 return self.seriesguards
353 return self.seriesguards
354
354
355 def invalidate(self):
355 def invalidate(self):
356 for a in 'applied fullseries series seriesguards'.split():
356 for a in 'applied fullseries series seriesguards'.split():
357 if a in self.__dict__:
357 if a in self.__dict__:
358 delattr(self, a)
358 delattr(self, a)
359 self.applieddirty = False
359 self.applieddirty = False
360 self.seriesdirty = False
360 self.seriesdirty = False
361 self.guardsdirty = False
361 self.guardsdirty = False
362 self.activeguards = None
362 self.activeguards = None
363
363
364 def diffopts(self, opts={}, patchfn=None):
364 def diffopts(self, opts={}, patchfn=None):
365 diffopts = patchmod.diffopts(self.ui, opts)
365 diffopts = patchmod.diffopts(self.ui, opts)
366 if self.gitmode == 'auto':
366 if self.gitmode == 'auto':
367 diffopts.upgrade = True
367 diffopts.upgrade = True
368 elif self.gitmode == 'keep':
368 elif self.gitmode == 'keep':
369 pass
369 pass
370 elif self.gitmode in ('yes', 'no'):
370 elif self.gitmode in ('yes', 'no'):
371 diffopts.git = self.gitmode == 'yes'
371 diffopts.git = self.gitmode == 'yes'
372 else:
372 else:
373 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
373 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
374 ' got %s') % self.gitmode)
374 ' got %s') % self.gitmode)
375 if patchfn:
375 if patchfn:
376 diffopts = self.patchopts(diffopts, patchfn)
376 diffopts = self.patchopts(diffopts, patchfn)
377 return diffopts
377 return diffopts
378
378
379 def patchopts(self, diffopts, *patches):
379 def patchopts(self, diffopts, *patches):
380 """Return a copy of input diff options with git set to true if
380 """Return a copy of input diff options with git set to true if
381 referenced patch is a git patch and should be preserved as such.
381 referenced patch is a git patch and should be preserved as such.
382 """
382 """
383 diffopts = diffopts.copy()
383 diffopts = diffopts.copy()
384 if not diffopts.git and self.gitmode == 'keep':
384 if not diffopts.git and self.gitmode == 'keep':
385 for patchfn in patches:
385 for patchfn in patches:
386 patchf = self.opener(patchfn, 'r')
386 patchf = self.opener(patchfn, 'r')
387 # if the patch was a git patch, refresh it as a git patch
387 # if the patch was a git patch, refresh it as a git patch
388 for line in patchf:
388 for line in patchf:
389 if line.startswith('diff --git'):
389 if line.startswith('diff --git'):
390 diffopts.git = True
390 diffopts.git = True
391 break
391 break
392 patchf.close()
392 patchf.close()
393 return diffopts
393 return diffopts
394
394
395 def join(self, *p):
395 def join(self, *p):
396 return os.path.join(self.path, *p)
396 return os.path.join(self.path, *p)
397
397
398 def findseries(self, patch):
398 def findseries(self, patch):
399 def matchpatch(l):
399 def matchpatch(l):
400 l = l.split('#', 1)[0]
400 l = l.split('#', 1)[0]
401 return l.strip() == patch
401 return l.strip() == patch
402 for index, l in enumerate(self.fullseries):
402 for index, l in enumerate(self.fullseries):
403 if matchpatch(l):
403 if matchpatch(l):
404 return index
404 return index
405 return None
405 return None
406
406
407 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
407 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
408
408
409 def parseseries(self):
409 def parseseries(self):
410 self.series = []
410 self.series = []
411 self.seriesguards = []
411 self.seriesguards = []
412 for l in self.fullseries:
412 for l in self.fullseries:
413 h = l.find('#')
413 h = l.find('#')
414 if h == -1:
414 if h == -1:
415 patch = l
415 patch = l
416 comment = ''
416 comment = ''
417 elif h == 0:
417 elif h == 0:
418 continue
418 continue
419 else:
419 else:
420 patch = l[:h]
420 patch = l[:h]
421 comment = l[h:]
421 comment = l[h:]
422 patch = patch.strip()
422 patch = patch.strip()
423 if patch:
423 if patch:
424 if patch in self.series:
424 if patch in self.series:
425 raise util.Abort(_('%s appears more than once in %s') %
425 raise util.Abort(_('%s appears more than once in %s') %
426 (patch, self.join(self.seriespath)))
426 (patch, self.join(self.seriespath)))
427 self.series.append(patch)
427 self.series.append(patch)
428 self.seriesguards.append(self.guard_re.findall(comment))
428 self.seriesguards.append(self.guard_re.findall(comment))
429
429
430 def checkguard(self, guard):
430 def checkguard(self, guard):
431 if not guard:
431 if not guard:
432 return _('guard cannot be an empty string')
432 return _('guard cannot be an empty string')
433 bad_chars = '# \t\r\n\f'
433 bad_chars = '# \t\r\n\f'
434 first = guard[0]
434 first = guard[0]
435 if first in '-+':
435 if first in '-+':
436 return (_('guard %r starts with invalid character: %r') %
436 return (_('guard %r starts with invalid character: %r') %
437 (guard, first))
437 (guard, first))
438 for c in bad_chars:
438 for c in bad_chars:
439 if c in guard:
439 if c in guard:
440 return _('invalid character in guard %r: %r') % (guard, c)
440 return _('invalid character in guard %r: %r') % (guard, c)
441
441
442 def setactive(self, guards):
442 def setactive(self, guards):
443 for guard in guards:
443 for guard in guards:
444 bad = self.checkguard(guard)
444 bad = self.checkguard(guard)
445 if bad:
445 if bad:
446 raise util.Abort(bad)
446 raise util.Abort(bad)
447 guards = sorted(set(guards))
447 guards = sorted(set(guards))
448 self.ui.debug('active guards: %s\n' % ' '.join(guards))
448 self.ui.debug('active guards: %s\n' % ' '.join(guards))
449 self.activeguards = guards
449 self.activeguards = guards
450 self.guardsdirty = True
450 self.guardsdirty = True
451
451
452 def active(self):
452 def active(self):
453 if self.activeguards is None:
453 if self.activeguards is None:
454 self.activeguards = []
454 self.activeguards = []
455 try:
455 try:
456 guards = self.opener.read(self.guardspath).split()
456 guards = self.opener.read(self.guardspath).split()
457 except IOError, err:
457 except IOError, err:
458 if err.errno != errno.ENOENT:
458 if err.errno != errno.ENOENT:
459 raise
459 raise
460 guards = []
460 guards = []
461 for i, guard in enumerate(guards):
461 for i, guard in enumerate(guards):
462 bad = self.checkguard(guard)
462 bad = self.checkguard(guard)
463 if bad:
463 if bad:
464 self.ui.warn('%s:%d: %s\n' %
464 self.ui.warn('%s:%d: %s\n' %
465 (self.join(self.guardspath), i + 1, bad))
465 (self.join(self.guardspath), i + 1, bad))
466 else:
466 else:
467 self.activeguards.append(guard)
467 self.activeguards.append(guard)
468 return self.activeguards
468 return self.activeguards
469
469
470 def setguards(self, idx, guards):
470 def setguards(self, idx, guards):
471 for g in guards:
471 for g in guards:
472 if len(g) < 2:
472 if len(g) < 2:
473 raise util.Abort(_('guard %r too short') % g)
473 raise util.Abort(_('guard %r too short') % g)
474 if g[0] not in '-+':
474 if g[0] not in '-+':
475 raise util.Abort(_('guard %r starts with invalid char') % g)
475 raise util.Abort(_('guard %r starts with invalid char') % g)
476 bad = self.checkguard(g[1:])
476 bad = self.checkguard(g[1:])
477 if bad:
477 if bad:
478 raise util.Abort(bad)
478 raise util.Abort(bad)
479 drop = self.guard_re.sub('', self.fullseries[idx])
479 drop = self.guard_re.sub('', self.fullseries[idx])
480 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
480 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
481 self.parseseries()
481 self.parseseries()
482 self.seriesdirty = True
482 self.seriesdirty = True
483
483
484 def pushable(self, idx):
484 def pushable(self, idx):
485 if isinstance(idx, str):
485 if isinstance(idx, str):
486 idx = self.series.index(idx)
486 idx = self.series.index(idx)
487 patchguards = self.seriesguards[idx]
487 patchguards = self.seriesguards[idx]
488 if not patchguards:
488 if not patchguards:
489 return True, None
489 return True, None
490 guards = self.active()
490 guards = self.active()
491 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
491 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
492 if exactneg:
492 if exactneg:
493 return False, repr(exactneg[0])
493 return False, repr(exactneg[0])
494 pos = [g for g in patchguards if g[0] == '+']
494 pos = [g for g in patchguards if g[0] == '+']
495 exactpos = [g for g in pos if g[1:] in guards]
495 exactpos = [g for g in pos if g[1:] in guards]
496 if pos:
496 if pos:
497 if exactpos:
497 if exactpos:
498 return True, repr(exactpos[0])
498 return True, repr(exactpos[0])
499 return False, ' '.join(map(repr, pos))
499 return False, ' '.join(map(repr, pos))
500 return True, ''
500 return True, ''
501
501
502 def explainpushable(self, idx, all_patches=False):
502 def explainpushable(self, idx, all_patches=False):
503 write = all_patches and self.ui.write or self.ui.warn
503 write = all_patches and self.ui.write or self.ui.warn
504 if all_patches or self.ui.verbose:
504 if all_patches or self.ui.verbose:
505 if isinstance(idx, str):
505 if isinstance(idx, str):
506 idx = self.series.index(idx)
506 idx = self.series.index(idx)
507 pushable, why = self.pushable(idx)
507 pushable, why = self.pushable(idx)
508 if all_patches and pushable:
508 if all_patches and pushable:
509 if why is None:
509 if why is None:
510 write(_('allowing %s - no guards in effect\n') %
510 write(_('allowing %s - no guards in effect\n') %
511 self.series[idx])
511 self.series[idx])
512 else:
512 else:
513 if not why:
513 if not why:
514 write(_('allowing %s - no matching negative guards\n') %
514 write(_('allowing %s - no matching negative guards\n') %
515 self.series[idx])
515 self.series[idx])
516 else:
516 else:
517 write(_('allowing %s - guarded by %s\n') %
517 write(_('allowing %s - guarded by %s\n') %
518 (self.series[idx], why))
518 (self.series[idx], why))
519 if not pushable:
519 if not pushable:
520 if why:
520 if why:
521 write(_('skipping %s - guarded by %s\n') %
521 write(_('skipping %s - guarded by %s\n') %
522 (self.series[idx], why))
522 (self.series[idx], why))
523 else:
523 else:
524 write(_('skipping %s - no matching guards\n') %
524 write(_('skipping %s - no matching guards\n') %
525 self.series[idx])
525 self.series[idx])
526
526
527 def savedirty(self):
527 def savedirty(self):
528 def writelist(items, path):
528 def writelist(items, path):
529 fp = self.opener(path, 'w')
529 fp = self.opener(path, 'w')
530 for i in items:
530 for i in items:
531 fp.write("%s\n" % i)
531 fp.write("%s\n" % i)
532 fp.close()
532 fp.close()
533 if self.applieddirty:
533 if self.applieddirty:
534 writelist(map(str, self.applied), self.statuspath)
534 writelist(map(str, self.applied), self.statuspath)
535 self.applieddirty = False
535 self.applieddirty = False
536 if self.seriesdirty:
536 if self.seriesdirty:
537 writelist(self.fullseries, self.seriespath)
537 writelist(self.fullseries, self.seriespath)
538 self.seriesdirty = False
538 self.seriesdirty = False
539 if self.guardsdirty:
539 if self.guardsdirty:
540 writelist(self.activeguards, self.guardspath)
540 writelist(self.activeguards, self.guardspath)
541 self.guardsdirty = False
541 self.guardsdirty = False
542 if self.added:
542 if self.added:
543 qrepo = self.qrepo()
543 qrepo = self.qrepo()
544 if qrepo:
544 if qrepo:
545 qrepo[None].add(f for f in self.added if f not in qrepo[None])
545 qrepo[None].add(f for f in self.added if f not in qrepo[None])
546 self.added = []
546 self.added = []
547
547
548 def removeundo(self, repo):
548 def removeundo(self, repo):
549 undo = repo.sjoin('undo')
549 undo = repo.sjoin('undo')
550 if not os.path.exists(undo):
550 if not os.path.exists(undo):
551 return
551 return
552 try:
552 try:
553 os.unlink(undo)
553 os.unlink(undo)
554 except OSError, inst:
554 except OSError, inst:
555 self.ui.warn(_('error removing undo: %s\n') % str(inst))
555 self.ui.warn(_('error removing undo: %s\n') % str(inst))
556
556
557 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
557 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
558 fp=None, changes=None, opts={}):
558 fp=None, changes=None, opts={}):
559 stat = opts.get('stat')
559 stat = opts.get('stat')
560 m = scmutil.match(repo[node1], files, opts)
560 m = scmutil.match(repo[node1], files, opts)
561 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
561 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
562 changes, stat, fp)
562 changes, stat, fp)
563
563
564 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
564 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
565 # first try just applying the patch
565 # first try just applying the patch
566 (err, n) = self.apply(repo, [patch], update_status=False,
566 (err, n) = self.apply(repo, [patch], update_status=False,
567 strict=True, merge=rev)
567 strict=True, merge=rev)
568
568
569 if err == 0:
569 if err == 0:
570 return (err, n)
570 return (err, n)
571
571
572 if n is None:
572 if n is None:
573 raise util.Abort(_("apply failed for patch %s") % patch)
573 raise util.Abort(_("apply failed for patch %s") % patch)
574
574
575 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
575 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
576
576
577 # apply failed, strip away that rev and merge.
577 # apply failed, strip away that rev and merge.
578 hg.clean(repo, head)
578 hg.clean(repo, head)
579 self.strip(repo, [n], update=False, backup='strip')
579 self.strip(repo, [n], update=False, backup='strip')
580
580
581 ctx = repo[rev]
581 ctx = repo[rev]
582 ret = hg.merge(repo, rev)
582 ret = hg.merge(repo, rev)
583 if ret:
583 if ret:
584 raise util.Abort(_("update returned %d") % ret)
584 raise util.Abort(_("update returned %d") % ret)
585 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
585 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
586 if n is None:
586 if n is None:
587 raise util.Abort(_("repo commit failed"))
587 raise util.Abort(_("repo commit failed"))
588 try:
588 try:
589 ph = patchheader(mergeq.join(patch), self.plainmode)
589 ph = patchheader(mergeq.join(patch), self.plainmode)
590 except:
590 except:
591 raise util.Abort(_("unable to read %s") % patch)
591 raise util.Abort(_("unable to read %s") % patch)
592
592
593 diffopts = self.patchopts(diffopts, patch)
593 diffopts = self.patchopts(diffopts, patch)
594 patchf = self.opener(patch, "w")
594 patchf = self.opener(patch, "w")
595 comments = str(ph)
595 comments = str(ph)
596 if comments:
596 if comments:
597 patchf.write(comments)
597 patchf.write(comments)
598 self.printdiff(repo, diffopts, head, n, fp=patchf)
598 self.printdiff(repo, diffopts, head, n, fp=patchf)
599 patchf.close()
599 patchf.close()
600 self.removeundo(repo)
600 self.removeundo(repo)
601 return (0, n)
601 return (0, n)
602
602
603 def qparents(self, repo, rev=None):
603 def qparents(self, repo, rev=None):
604 if rev is None:
604 if rev is None:
605 (p1, p2) = repo.dirstate.parents()
605 (p1, p2) = repo.dirstate.parents()
606 if p2 == nullid:
606 if p2 == nullid:
607 return p1
607 return p1
608 if not self.applied:
608 if not self.applied:
609 return None
609 return None
610 return self.applied[-1].node
610 return self.applied[-1].node
611 p1, p2 = repo.changelog.parents(rev)
611 p1, p2 = repo.changelog.parents(rev)
612 if p2 != nullid and p2 in [x.node for x in self.applied]:
612 if p2 != nullid and p2 in [x.node for x in self.applied]:
613 return p2
613 return p2
614 return p1
614 return p1
615
615
616 def mergepatch(self, repo, mergeq, series, diffopts):
616 def mergepatch(self, repo, mergeq, series, diffopts):
617 if not self.applied:
617 if not self.applied:
618 # each of the patches merged in will have two parents. This
618 # each of the patches merged in will have two parents. This
619 # can confuse the qrefresh, qdiff, and strip code because it
619 # can confuse the qrefresh, qdiff, and strip code because it
620 # needs to know which parent is actually in the patch queue.
620 # needs to know which parent is actually in the patch queue.
621 # so, we insert a merge marker with only one parent. This way
621 # so, we insert a merge marker with only one parent. This way
622 # the first patch in the queue is never a merge patch
622 # the first patch in the queue is never a merge patch
623 #
623 #
624 pname = ".hg.patches.merge.marker"
624 pname = ".hg.patches.merge.marker"
625 n = newcommit(repo, None, '[mq]: merge marker', force=True)
625 n = newcommit(repo, None, '[mq]: merge marker', force=True)
626 self.removeundo(repo)
626 self.removeundo(repo)
627 self.applied.append(statusentry(n, pname))
627 self.applied.append(statusentry(n, pname))
628 self.applieddirty = True
628 self.applieddirty = True
629
629
630 head = self.qparents(repo)
630 head = self.qparents(repo)
631
631
632 for patch in series:
632 for patch in series:
633 patch = mergeq.lookup(patch, strict=True)
633 patch = mergeq.lookup(patch, strict=True)
634 if not patch:
634 if not patch:
635 self.ui.warn(_("patch %s does not exist\n") % patch)
635 self.ui.warn(_("patch %s does not exist\n") % patch)
636 return (1, None)
636 return (1, None)
637 pushable, reason = self.pushable(patch)
637 pushable, reason = self.pushable(patch)
638 if not pushable:
638 if not pushable:
639 self.explainpushable(patch, all_patches=True)
639 self.explainpushable(patch, all_patches=True)
640 continue
640 continue
641 info = mergeq.isapplied(patch)
641 info = mergeq.isapplied(patch)
642 if not info:
642 if not info:
643 self.ui.warn(_("patch %s is not applied\n") % patch)
643 self.ui.warn(_("patch %s is not applied\n") % patch)
644 return (1, None)
644 return (1, None)
645 rev = info[1]
645 rev = info[1]
646 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
646 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
647 if head:
647 if head:
648 self.applied.append(statusentry(head, patch))
648 self.applied.append(statusentry(head, patch))
649 self.applieddirty = True
649 self.applieddirty = True
650 if err:
650 if err:
651 return (err, head)
651 return (err, head)
652 self.savedirty()
652 self.savedirty()
653 return (0, head)
653 return (0, head)
654
654
655 def patch(self, repo, patchfile):
655 def patch(self, repo, patchfile):
656 '''Apply patchfile to the working directory.
656 '''Apply patchfile to the working directory.
657 patchfile: name of patch file'''
657 patchfile: name of patch file'''
658 files = set()
658 files = set()
659 try:
659 try:
660 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
660 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
661 files=files, eolmode=None)
661 files=files, eolmode=None)
662 return (True, list(files), fuzz)
662 return (True, list(files), fuzz)
663 except Exception, inst:
663 except Exception, inst:
664 self.ui.note(str(inst) + '\n')
664 self.ui.note(str(inst) + '\n')
665 if not self.ui.verbose:
665 if not self.ui.verbose:
666 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
666 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
667 self.ui.traceback()
667 self.ui.traceback()
668 return (False, list(files), False)
668 return (False, list(files), False)
669
669
670 def apply(self, repo, series, list=False, update_status=True,
670 def apply(self, repo, series, list=False, update_status=True,
671 strict=False, patchdir=None, merge=None, all_files=None):
671 strict=False, patchdir=None, merge=None, all_files=None):
672 wlock = lock = tr = None
672 wlock = lock = tr = None
673 try:
673 try:
674 wlock = repo.wlock()
674 wlock = repo.wlock()
675 lock = repo.lock()
675 lock = repo.lock()
676 tr = repo.transaction("qpush")
676 tr = repo.transaction("qpush")
677 try:
677 try:
678 ret = self._apply(repo, series, list, update_status,
678 ret = self._apply(repo, series, list, update_status,
679 strict, patchdir, merge, all_files=all_files)
679 strict, patchdir, merge, all_files=all_files)
680 tr.close()
680 tr.close()
681 self.savedirty()
681 self.savedirty()
682 return ret
682 return ret
683 except:
683 except:
684 try:
684 try:
685 tr.abort()
685 tr.abort()
686 finally:
686 finally:
687 repo.invalidate()
687 repo.invalidate()
688 repo.dirstate.invalidate()
688 repo.dirstate.invalidate()
689 self.invalidate()
689 self.invalidate()
690 raise
690 raise
691 finally:
691 finally:
692 release(tr, lock, wlock)
692 release(tr, lock, wlock)
693 self.removeundo(repo)
693 self.removeundo(repo)
694
694
695 def _apply(self, repo, series, list=False, update_status=True,
695 def _apply(self, repo, series, list=False, update_status=True,
696 strict=False, patchdir=None, merge=None, all_files=None):
696 strict=False, patchdir=None, merge=None, all_files=None):
697 '''returns (error, hash)
697 '''returns (error, hash)
698 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
698 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
699 # TODO unify with commands.py
699 # TODO unify with commands.py
700 if not patchdir:
700 if not patchdir:
701 patchdir = self.path
701 patchdir = self.path
702 err = 0
702 err = 0
703 n = None
703 n = None
704 for patchname in series:
704 for patchname in series:
705 pushable, reason = self.pushable(patchname)
705 pushable, reason = self.pushable(patchname)
706 if not pushable:
706 if not pushable:
707 self.explainpushable(patchname, all_patches=True)
707 self.explainpushable(patchname, all_patches=True)
708 continue
708 continue
709 self.ui.status(_("applying %s\n") % patchname)
709 self.ui.status(_("applying %s\n") % patchname)
710 pf = os.path.join(patchdir, patchname)
710 pf = os.path.join(patchdir, patchname)
711
711
712 try:
712 try:
713 ph = patchheader(self.join(patchname), self.plainmode)
713 ph = patchheader(self.join(patchname), self.plainmode)
714 except IOError:
714 except IOError:
715 self.ui.warn(_("unable to read %s\n") % patchname)
715 self.ui.warn(_("unable to read %s\n") % patchname)
716 err = 1
716 err = 1
717 break
717 break
718
718
719 message = ph.message
719 message = ph.message
720 if not message:
720 if not message:
721 # The commit message should not be translated
721 # The commit message should not be translated
722 message = "imported patch %s\n" % patchname
722 message = "imported patch %s\n" % patchname
723 else:
723 else:
724 if list:
724 if list:
725 # The commit message should not be translated
725 # The commit message should not be translated
726 message.append("\nimported patch %s" % patchname)
726 message.append("\nimported patch %s" % patchname)
727 message = '\n'.join(message)
727 message = '\n'.join(message)
728
728
729 if ph.haspatch:
729 if ph.haspatch:
730 (patcherr, files, fuzz) = self.patch(repo, pf)
730 (patcherr, files, fuzz) = self.patch(repo, pf)
731 if all_files is not None:
731 if all_files is not None:
732 all_files.update(files)
732 all_files.update(files)
733 patcherr = not patcherr
733 patcherr = not patcherr
734 else:
734 else:
735 self.ui.warn(_("patch %s is empty\n") % patchname)
735 self.ui.warn(_("patch %s is empty\n") % patchname)
736 patcherr, files, fuzz = 0, [], 0
736 patcherr, files, fuzz = 0, [], 0
737
737
738 if merge and files:
738 if merge and files:
739 # Mark as removed/merged and update dirstate parent info
739 # Mark as removed/merged and update dirstate parent info
740 removed = []
740 removed = []
741 merged = []
741 merged = []
742 for f in files:
742 for f in files:
743 if os.path.lexists(repo.wjoin(f)):
743 if os.path.lexists(repo.wjoin(f)):
744 merged.append(f)
744 merged.append(f)
745 else:
745 else:
746 removed.append(f)
746 removed.append(f)
747 for f in removed:
747 for f in removed:
748 repo.dirstate.remove(f)
748 repo.dirstate.remove(f)
749 for f in merged:
749 for f in merged:
750 repo.dirstate.merge(f)
750 repo.dirstate.merge(f)
751 p1, p2 = repo.dirstate.parents()
751 p1, p2 = repo.dirstate.parents()
752 repo.dirstate.setparents(p1, merge)
752 repo.setparents(p1, merge)
753
753
754 match = scmutil.matchfiles(repo, files or [])
754 match = scmutil.matchfiles(repo, files or [])
755 oldtip = repo['tip']
755 oldtip = repo['tip']
756 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
756 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
757 force=True)
757 force=True)
758 if repo['tip'] == oldtip:
758 if repo['tip'] == oldtip:
759 raise util.Abort(_("qpush exactly duplicates child changeset"))
759 raise util.Abort(_("qpush exactly duplicates child changeset"))
760 if n is None:
760 if n is None:
761 raise util.Abort(_("repository commit failed"))
761 raise util.Abort(_("repository commit failed"))
762
762
763 if update_status:
763 if update_status:
764 self.applied.append(statusentry(n, patchname))
764 self.applied.append(statusentry(n, patchname))
765
765
766 if patcherr:
766 if patcherr:
767 self.ui.warn(_("patch failed, rejects left in working dir\n"))
767 self.ui.warn(_("patch failed, rejects left in working dir\n"))
768 err = 2
768 err = 2
769 break
769 break
770
770
771 if fuzz and strict:
771 if fuzz and strict:
772 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
772 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
773 err = 3
773 err = 3
774 break
774 break
775 return (err, n)
775 return (err, n)
776
776
777 def _cleanup(self, patches, numrevs, keep=False):
777 def _cleanup(self, patches, numrevs, keep=False):
778 if not keep:
778 if not keep:
779 r = self.qrepo()
779 r = self.qrepo()
780 if r:
780 if r:
781 r[None].forget(patches)
781 r[None].forget(patches)
782 for p in patches:
782 for p in patches:
783 os.unlink(self.join(p))
783 os.unlink(self.join(p))
784
784
785 qfinished = []
785 qfinished = []
786 if numrevs:
786 if numrevs:
787 qfinished = self.applied[:numrevs]
787 qfinished = self.applied[:numrevs]
788 del self.applied[:numrevs]
788 del self.applied[:numrevs]
789 self.applieddirty = True
789 self.applieddirty = True
790
790
791 unknown = []
791 unknown = []
792
792
793 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
793 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
794 reverse=True):
794 reverse=True):
795 if i is not None:
795 if i is not None:
796 del self.fullseries[i]
796 del self.fullseries[i]
797 else:
797 else:
798 unknown.append(p)
798 unknown.append(p)
799
799
800 if unknown:
800 if unknown:
801 if numrevs:
801 if numrevs:
802 rev = dict((entry.name, entry.node) for entry in qfinished)
802 rev = dict((entry.name, entry.node) for entry in qfinished)
803 for p in unknown:
803 for p in unknown:
804 msg = _('revision %s refers to unknown patches: %s\n')
804 msg = _('revision %s refers to unknown patches: %s\n')
805 self.ui.warn(msg % (short(rev[p]), p))
805 self.ui.warn(msg % (short(rev[p]), p))
806 else:
806 else:
807 msg = _('unknown patches: %s\n')
807 msg = _('unknown patches: %s\n')
808 raise util.Abort(''.join(msg % p for p in unknown))
808 raise util.Abort(''.join(msg % p for p in unknown))
809
809
810 self.parseseries()
810 self.parseseries()
811 self.seriesdirty = True
811 self.seriesdirty = True
812 return [entry.node for entry in qfinished]
812 return [entry.node for entry in qfinished]
813
813
814 def _revpatches(self, repo, revs):
814 def _revpatches(self, repo, revs):
815 firstrev = repo[self.applied[0].node].rev()
815 firstrev = repo[self.applied[0].node].rev()
816 patches = []
816 patches = []
817 for i, rev in enumerate(revs):
817 for i, rev in enumerate(revs):
818
818
819 if rev < firstrev:
819 if rev < firstrev:
820 raise util.Abort(_('revision %d is not managed') % rev)
820 raise util.Abort(_('revision %d is not managed') % rev)
821
821
822 ctx = repo[rev]
822 ctx = repo[rev]
823 base = self.applied[i].node
823 base = self.applied[i].node
824 if ctx.node() != base:
824 if ctx.node() != base:
825 msg = _('cannot delete revision %d above applied patches')
825 msg = _('cannot delete revision %d above applied patches')
826 raise util.Abort(msg % rev)
826 raise util.Abort(msg % rev)
827
827
828 patch = self.applied[i].name
828 patch = self.applied[i].name
829 for fmt in ('[mq]: %s', 'imported patch %s'):
829 for fmt in ('[mq]: %s', 'imported patch %s'):
830 if ctx.description() == fmt % patch:
830 if ctx.description() == fmt % patch:
831 msg = _('patch %s finalized without changeset message\n')
831 msg = _('patch %s finalized without changeset message\n')
832 repo.ui.status(msg % patch)
832 repo.ui.status(msg % patch)
833 break
833 break
834
834
835 patches.append(patch)
835 patches.append(patch)
836 return patches
836 return patches
837
837
838 def finish(self, repo, revs):
838 def finish(self, repo, revs):
839 # Manually trigger phase computation to ensure phasedefaults is
839 # Manually trigger phase computation to ensure phasedefaults is
840 # executed before we remove the patches.
840 # executed before we remove the patches.
841 repo._phaserev
841 repo._phaserev
842 patches = self._revpatches(repo, sorted(revs))
842 patches = self._revpatches(repo, sorted(revs))
843 qfinished = self._cleanup(patches, len(patches))
843 qfinished = self._cleanup(patches, len(patches))
844 if qfinished and repo.ui.configbool('mq', 'secret', False):
844 if qfinished and repo.ui.configbool('mq', 'secret', False):
845 # only use this logic when the secret option is added
845 # only use this logic when the secret option is added
846 oldqbase = repo[qfinished[0]]
846 oldqbase = repo[qfinished[0]]
847 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
847 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
848 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
848 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
849 phases.advanceboundary(repo, tphase, qfinished)
849 phases.advanceboundary(repo, tphase, qfinished)
850
850
851 def delete(self, repo, patches, opts):
851 def delete(self, repo, patches, opts):
852 if not patches and not opts.get('rev'):
852 if not patches and not opts.get('rev'):
853 raise util.Abort(_('qdelete requires at least one revision or '
853 raise util.Abort(_('qdelete requires at least one revision or '
854 'patch name'))
854 'patch name'))
855
855
856 realpatches = []
856 realpatches = []
857 for patch in patches:
857 for patch in patches:
858 patch = self.lookup(patch, strict=True)
858 patch = self.lookup(patch, strict=True)
859 info = self.isapplied(patch)
859 info = self.isapplied(patch)
860 if info:
860 if info:
861 raise util.Abort(_("cannot delete applied patch %s") % patch)
861 raise util.Abort(_("cannot delete applied patch %s") % patch)
862 if patch not in self.series:
862 if patch not in self.series:
863 raise util.Abort(_("patch %s not in series file") % patch)
863 raise util.Abort(_("patch %s not in series file") % patch)
864 if patch not in realpatches:
864 if patch not in realpatches:
865 realpatches.append(patch)
865 realpatches.append(patch)
866
866
867 numrevs = 0
867 numrevs = 0
868 if opts.get('rev'):
868 if opts.get('rev'):
869 if not self.applied:
869 if not self.applied:
870 raise util.Abort(_('no patches applied'))
870 raise util.Abort(_('no patches applied'))
871 revs = scmutil.revrange(repo, opts.get('rev'))
871 revs = scmutil.revrange(repo, opts.get('rev'))
872 if len(revs) > 1 and revs[0] > revs[1]:
872 if len(revs) > 1 and revs[0] > revs[1]:
873 revs.reverse()
873 revs.reverse()
874 revpatches = self._revpatches(repo, revs)
874 revpatches = self._revpatches(repo, revs)
875 realpatches += revpatches
875 realpatches += revpatches
876 numrevs = len(revpatches)
876 numrevs = len(revpatches)
877
877
878 self._cleanup(realpatches, numrevs, opts.get('keep'))
878 self._cleanup(realpatches, numrevs, opts.get('keep'))
879
879
880 def checktoppatch(self, repo):
880 def checktoppatch(self, repo):
881 if self.applied:
881 if self.applied:
882 top = self.applied[-1].node
882 top = self.applied[-1].node
883 patch = self.applied[-1].name
883 patch = self.applied[-1].name
884 pp = repo.dirstate.parents()
884 pp = repo.dirstate.parents()
885 if top not in pp:
885 if top not in pp:
886 raise util.Abort(_("working directory revision is not qtip"))
886 raise util.Abort(_("working directory revision is not qtip"))
887 return top, patch
887 return top, patch
888 return None, None
888 return None, None
889
889
890 def checksubstate(self, repo):
890 def checksubstate(self, repo):
891 '''return list of subrepos at a different revision than substate.
891 '''return list of subrepos at a different revision than substate.
892 Abort if any subrepos have uncommitted changes.'''
892 Abort if any subrepos have uncommitted changes.'''
893 inclsubs = []
893 inclsubs = []
894 wctx = repo[None]
894 wctx = repo[None]
895 for s in wctx.substate:
895 for s in wctx.substate:
896 if wctx.sub(s).dirty(True):
896 if wctx.sub(s).dirty(True):
897 raise util.Abort(
897 raise util.Abort(
898 _("uncommitted changes in subrepository %s") % s)
898 _("uncommitted changes in subrepository %s") % s)
899 elif wctx.sub(s).dirty():
899 elif wctx.sub(s).dirty():
900 inclsubs.append(s)
900 inclsubs.append(s)
901 return inclsubs
901 return inclsubs
902
902
903 def localchangesfound(self, refresh=True):
903 def localchangesfound(self, refresh=True):
904 if refresh:
904 if refresh:
905 raise util.Abort(_("local changes found, refresh first"))
905 raise util.Abort(_("local changes found, refresh first"))
906 else:
906 else:
907 raise util.Abort(_("local changes found"))
907 raise util.Abort(_("local changes found"))
908
908
909 def checklocalchanges(self, repo, force=False, refresh=True):
909 def checklocalchanges(self, repo, force=False, refresh=True):
910 m, a, r, d = repo.status()[:4]
910 m, a, r, d = repo.status()[:4]
911 if (m or a or r or d) and not force:
911 if (m or a or r or d) and not force:
912 self.localchangesfound(refresh)
912 self.localchangesfound(refresh)
913 return m, a, r, d
913 return m, a, r, d
914
914
915 _reserved = ('series', 'status', 'guards', '.', '..')
915 _reserved = ('series', 'status', 'guards', '.', '..')
916 def checkreservedname(self, name):
916 def checkreservedname(self, name):
917 if name in self._reserved:
917 if name in self._reserved:
918 raise util.Abort(_('"%s" cannot be used as the name of a patch')
918 raise util.Abort(_('"%s" cannot be used as the name of a patch')
919 % name)
919 % name)
920 for prefix in ('.hg', '.mq'):
920 for prefix in ('.hg', '.mq'):
921 if name.startswith(prefix):
921 if name.startswith(prefix):
922 raise util.Abort(_('patch name cannot begin with "%s"')
922 raise util.Abort(_('patch name cannot begin with "%s"')
923 % prefix)
923 % prefix)
924 for c in ('#', ':'):
924 for c in ('#', ':'):
925 if c in name:
925 if c in name:
926 raise util.Abort(_('"%s" cannot be used in the name of a patch')
926 raise util.Abort(_('"%s" cannot be used in the name of a patch')
927 % c)
927 % c)
928
928
929 def checkpatchname(self, name, force=False):
929 def checkpatchname(self, name, force=False):
930 self.checkreservedname(name)
930 self.checkreservedname(name)
931 if not force and os.path.exists(self.join(name)):
931 if not force and os.path.exists(self.join(name)):
932 if os.path.isdir(self.join(name)):
932 if os.path.isdir(self.join(name)):
933 raise util.Abort(_('"%s" already exists as a directory')
933 raise util.Abort(_('"%s" already exists as a directory')
934 % name)
934 % name)
935 else:
935 else:
936 raise util.Abort(_('patch "%s" already exists') % name)
936 raise util.Abort(_('patch "%s" already exists') % name)
937
937
938 def new(self, repo, patchfn, *pats, **opts):
938 def new(self, repo, patchfn, *pats, **opts):
939 """options:
939 """options:
940 msg: a string or a no-argument function returning a string
940 msg: a string or a no-argument function returning a string
941 """
941 """
942 msg = opts.get('msg')
942 msg = opts.get('msg')
943 user = opts.get('user')
943 user = opts.get('user')
944 date = opts.get('date')
944 date = opts.get('date')
945 if date:
945 if date:
946 date = util.parsedate(date)
946 date = util.parsedate(date)
947 diffopts = self.diffopts({'git': opts.get('git')})
947 diffopts = self.diffopts({'git': opts.get('git')})
948 if opts.get('checkname', True):
948 if opts.get('checkname', True):
949 self.checkpatchname(patchfn)
949 self.checkpatchname(patchfn)
950 inclsubs = self.checksubstate(repo)
950 inclsubs = self.checksubstate(repo)
951 if inclsubs:
951 if inclsubs:
952 inclsubs.append('.hgsubstate')
952 inclsubs.append('.hgsubstate')
953 substatestate = repo.dirstate['.hgsubstate']
953 substatestate = repo.dirstate['.hgsubstate']
954 if opts.get('include') or opts.get('exclude') or pats:
954 if opts.get('include') or opts.get('exclude') or pats:
955 if inclsubs:
955 if inclsubs:
956 pats = list(pats or []) + inclsubs
956 pats = list(pats or []) + inclsubs
957 match = scmutil.match(repo[None], pats, opts)
957 match = scmutil.match(repo[None], pats, opts)
958 # detect missing files in pats
958 # detect missing files in pats
959 def badfn(f, msg):
959 def badfn(f, msg):
960 if f != '.hgsubstate': # .hgsubstate is auto-created
960 if f != '.hgsubstate': # .hgsubstate is auto-created
961 raise util.Abort('%s: %s' % (f, msg))
961 raise util.Abort('%s: %s' % (f, msg))
962 match.bad = badfn
962 match.bad = badfn
963 changes = repo.status(match=match)
963 changes = repo.status(match=match)
964 m, a, r, d = changes[:4]
964 m, a, r, d = changes[:4]
965 else:
965 else:
966 changes = self.checklocalchanges(repo, force=True)
966 changes = self.checklocalchanges(repo, force=True)
967 m, a, r, d = changes
967 m, a, r, d = changes
968 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
968 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
969 if len(repo[None].parents()) > 1:
969 if len(repo[None].parents()) > 1:
970 raise util.Abort(_('cannot manage merge changesets'))
970 raise util.Abort(_('cannot manage merge changesets'))
971 commitfiles = m + a + r
971 commitfiles = m + a + r
972 self.checktoppatch(repo)
972 self.checktoppatch(repo)
973 insert = self.fullseriesend()
973 insert = self.fullseriesend()
974 wlock = repo.wlock()
974 wlock = repo.wlock()
975 try:
975 try:
976 try:
976 try:
977 # if patch file write fails, abort early
977 # if patch file write fails, abort early
978 p = self.opener(patchfn, "w")
978 p = self.opener(patchfn, "w")
979 except IOError, e:
979 except IOError, e:
980 raise util.Abort(_('cannot write patch "%s": %s')
980 raise util.Abort(_('cannot write patch "%s": %s')
981 % (patchfn, e.strerror))
981 % (patchfn, e.strerror))
982 try:
982 try:
983 if self.plainmode:
983 if self.plainmode:
984 if user:
984 if user:
985 p.write("From: " + user + "\n")
985 p.write("From: " + user + "\n")
986 if not date:
986 if not date:
987 p.write("\n")
987 p.write("\n")
988 if date:
988 if date:
989 p.write("Date: %d %d\n\n" % date)
989 p.write("Date: %d %d\n\n" % date)
990 else:
990 else:
991 p.write("# HG changeset patch\n")
991 p.write("# HG changeset patch\n")
992 p.write("# Parent "
992 p.write("# Parent "
993 + hex(repo[None].p1().node()) + "\n")
993 + hex(repo[None].p1().node()) + "\n")
994 if user:
994 if user:
995 p.write("# User " + user + "\n")
995 p.write("# User " + user + "\n")
996 if date:
996 if date:
997 p.write("# Date %s %s\n\n" % date)
997 p.write("# Date %s %s\n\n" % date)
998 if util.safehasattr(msg, '__call__'):
998 if util.safehasattr(msg, '__call__'):
999 msg = msg()
999 msg = msg()
1000 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1000 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1001 n = newcommit(repo, None, commitmsg, user, date, match=match,
1001 n = newcommit(repo, None, commitmsg, user, date, match=match,
1002 force=True)
1002 force=True)
1003 if n is None:
1003 if n is None:
1004 raise util.Abort(_("repo commit failed"))
1004 raise util.Abort(_("repo commit failed"))
1005 try:
1005 try:
1006 self.fullseries[insert:insert] = [patchfn]
1006 self.fullseries[insert:insert] = [patchfn]
1007 self.applied.append(statusentry(n, patchfn))
1007 self.applied.append(statusentry(n, patchfn))
1008 self.parseseries()
1008 self.parseseries()
1009 self.seriesdirty = True
1009 self.seriesdirty = True
1010 self.applieddirty = True
1010 self.applieddirty = True
1011 if msg:
1011 if msg:
1012 msg = msg + "\n\n"
1012 msg = msg + "\n\n"
1013 p.write(msg)
1013 p.write(msg)
1014 if commitfiles:
1014 if commitfiles:
1015 parent = self.qparents(repo, n)
1015 parent = self.qparents(repo, n)
1016 if inclsubs:
1016 if inclsubs:
1017 if substatestate in 'a?':
1017 if substatestate in 'a?':
1018 changes[1].append('.hgsubstate')
1018 changes[1].append('.hgsubstate')
1019 elif substatestate in 'r':
1019 elif substatestate in 'r':
1020 changes[2].append('.hgsubstate')
1020 changes[2].append('.hgsubstate')
1021 else: # modified
1021 else: # modified
1022 changes[0].append('.hgsubstate')
1022 changes[0].append('.hgsubstate')
1023 chunks = patchmod.diff(repo, node1=parent, node2=n,
1023 chunks = patchmod.diff(repo, node1=parent, node2=n,
1024 changes=changes, opts=diffopts)
1024 changes=changes, opts=diffopts)
1025 for chunk in chunks:
1025 for chunk in chunks:
1026 p.write(chunk)
1026 p.write(chunk)
1027 p.close()
1027 p.close()
1028 r = self.qrepo()
1028 r = self.qrepo()
1029 if r:
1029 if r:
1030 r[None].add([patchfn])
1030 r[None].add([patchfn])
1031 except:
1031 except:
1032 repo.rollback()
1032 repo.rollback()
1033 raise
1033 raise
1034 except Exception:
1034 except Exception:
1035 patchpath = self.join(patchfn)
1035 patchpath = self.join(patchfn)
1036 try:
1036 try:
1037 os.unlink(patchpath)
1037 os.unlink(patchpath)
1038 except:
1038 except:
1039 self.ui.warn(_('error unlinking %s\n') % patchpath)
1039 self.ui.warn(_('error unlinking %s\n') % patchpath)
1040 raise
1040 raise
1041 self.removeundo(repo)
1041 self.removeundo(repo)
1042 finally:
1042 finally:
1043 release(wlock)
1043 release(wlock)
1044
1044
1045 def strip(self, repo, revs, update=True, backup="all", force=None):
1045 def strip(self, repo, revs, update=True, backup="all", force=None):
1046 wlock = lock = None
1046 wlock = lock = None
1047 try:
1047 try:
1048 wlock = repo.wlock()
1048 wlock = repo.wlock()
1049 lock = repo.lock()
1049 lock = repo.lock()
1050
1050
1051 if update:
1051 if update:
1052 self.checklocalchanges(repo, force=force, refresh=False)
1052 self.checklocalchanges(repo, force=force, refresh=False)
1053 urev = self.qparents(repo, revs[0])
1053 urev = self.qparents(repo, revs[0])
1054 hg.clean(repo, urev)
1054 hg.clean(repo, urev)
1055 repo.dirstate.write()
1055 repo.dirstate.write()
1056
1056
1057 repair.strip(self.ui, repo, revs, backup)
1057 repair.strip(self.ui, repo, revs, backup)
1058 finally:
1058 finally:
1059 release(lock, wlock)
1059 release(lock, wlock)
1060
1060
1061 def isapplied(self, patch):
1061 def isapplied(self, patch):
1062 """returns (index, rev, patch)"""
1062 """returns (index, rev, patch)"""
1063 for i, a in enumerate(self.applied):
1063 for i, a in enumerate(self.applied):
1064 if a.name == patch:
1064 if a.name == patch:
1065 return (i, a.node, a.name)
1065 return (i, a.node, a.name)
1066 return None
1066 return None
1067
1067
1068 # if the exact patch name does not exist, we try a few
1068 # if the exact patch name does not exist, we try a few
1069 # variations. If strict is passed, we try only #1
1069 # variations. If strict is passed, we try only #1
1070 #
1070 #
1071 # 1) a number (as string) to indicate an offset in the series file
1071 # 1) a number (as string) to indicate an offset in the series file
1072 # 2) a unique substring of the patch name was given
1072 # 2) a unique substring of the patch name was given
1073 # 3) patchname[-+]num to indicate an offset in the series file
1073 # 3) patchname[-+]num to indicate an offset in the series file
1074 def lookup(self, patch, strict=False):
1074 def lookup(self, patch, strict=False):
1075 def partialname(s):
1075 def partialname(s):
1076 if s in self.series:
1076 if s in self.series:
1077 return s
1077 return s
1078 matches = [x for x in self.series if s in x]
1078 matches = [x for x in self.series if s in x]
1079 if len(matches) > 1:
1079 if len(matches) > 1:
1080 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1080 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1081 for m in matches:
1081 for m in matches:
1082 self.ui.warn(' %s\n' % m)
1082 self.ui.warn(' %s\n' % m)
1083 return None
1083 return None
1084 if matches:
1084 if matches:
1085 return matches[0]
1085 return matches[0]
1086 if self.series and self.applied:
1086 if self.series and self.applied:
1087 if s == 'qtip':
1087 if s == 'qtip':
1088 return self.series[self.seriesend(True)-1]
1088 return self.series[self.seriesend(True)-1]
1089 if s == 'qbase':
1089 if s == 'qbase':
1090 return self.series[0]
1090 return self.series[0]
1091 return None
1091 return None
1092
1092
1093 if patch in self.series:
1093 if patch in self.series:
1094 return patch
1094 return patch
1095
1095
1096 if not os.path.isfile(self.join(patch)):
1096 if not os.path.isfile(self.join(patch)):
1097 try:
1097 try:
1098 sno = int(patch)
1098 sno = int(patch)
1099 except (ValueError, OverflowError):
1099 except (ValueError, OverflowError):
1100 pass
1100 pass
1101 else:
1101 else:
1102 if -len(self.series) <= sno < len(self.series):
1102 if -len(self.series) <= sno < len(self.series):
1103 return self.series[sno]
1103 return self.series[sno]
1104
1104
1105 if not strict:
1105 if not strict:
1106 res = partialname(patch)
1106 res = partialname(patch)
1107 if res:
1107 if res:
1108 return res
1108 return res
1109 minus = patch.rfind('-')
1109 minus = patch.rfind('-')
1110 if minus >= 0:
1110 if minus >= 0:
1111 res = partialname(patch[:minus])
1111 res = partialname(patch[:minus])
1112 if res:
1112 if res:
1113 i = self.series.index(res)
1113 i = self.series.index(res)
1114 try:
1114 try:
1115 off = int(patch[minus + 1:] or 1)
1115 off = int(patch[minus + 1:] or 1)
1116 except (ValueError, OverflowError):
1116 except (ValueError, OverflowError):
1117 pass
1117 pass
1118 else:
1118 else:
1119 if i - off >= 0:
1119 if i - off >= 0:
1120 return self.series[i - off]
1120 return self.series[i - off]
1121 plus = patch.rfind('+')
1121 plus = patch.rfind('+')
1122 if plus >= 0:
1122 if plus >= 0:
1123 res = partialname(patch[:plus])
1123 res = partialname(patch[:plus])
1124 if res:
1124 if res:
1125 i = self.series.index(res)
1125 i = self.series.index(res)
1126 try:
1126 try:
1127 off = int(patch[plus + 1:] or 1)
1127 off = int(patch[plus + 1:] or 1)
1128 except (ValueError, OverflowError):
1128 except (ValueError, OverflowError):
1129 pass
1129 pass
1130 else:
1130 else:
1131 if i + off < len(self.series):
1131 if i + off < len(self.series):
1132 return self.series[i + off]
1132 return self.series[i + off]
1133 raise util.Abort(_("patch %s not in series") % patch)
1133 raise util.Abort(_("patch %s not in series") % patch)
1134
1134
1135 def push(self, repo, patch=None, force=False, list=False,
1135 def push(self, repo, patch=None, force=False, list=False,
1136 mergeq=None, all=False, move=False, exact=False):
1136 mergeq=None, all=False, move=False, exact=False):
1137 diffopts = self.diffopts()
1137 diffopts = self.diffopts()
1138 wlock = repo.wlock()
1138 wlock = repo.wlock()
1139 try:
1139 try:
1140 heads = []
1140 heads = []
1141 for b, ls in repo.branchmap().iteritems():
1141 for b, ls in repo.branchmap().iteritems():
1142 heads += ls
1142 heads += ls
1143 if not heads:
1143 if not heads:
1144 heads = [nullid]
1144 heads = [nullid]
1145 if repo.dirstate.p1() not in heads and not exact:
1145 if repo.dirstate.p1() not in heads and not exact:
1146 self.ui.status(_("(working directory not at a head)\n"))
1146 self.ui.status(_("(working directory not at a head)\n"))
1147
1147
1148 if not self.series:
1148 if not self.series:
1149 self.ui.warn(_('no patches in series\n'))
1149 self.ui.warn(_('no patches in series\n'))
1150 return 0
1150 return 0
1151
1151
1152 # Suppose our series file is: A B C and the current 'top'
1152 # Suppose our series file is: A B C and the current 'top'
1153 # patch is B. qpush C should be performed (moving forward)
1153 # patch is B. qpush C should be performed (moving forward)
1154 # qpush B is a NOP (no change) qpush A is an error (can't
1154 # qpush B is a NOP (no change) qpush A is an error (can't
1155 # go backwards with qpush)
1155 # go backwards with qpush)
1156 if patch:
1156 if patch:
1157 patch = self.lookup(patch)
1157 patch = self.lookup(patch)
1158 info = self.isapplied(patch)
1158 info = self.isapplied(patch)
1159 if info and info[0] >= len(self.applied) - 1:
1159 if info and info[0] >= len(self.applied) - 1:
1160 self.ui.warn(
1160 self.ui.warn(
1161 _('qpush: %s is already at the top\n') % patch)
1161 _('qpush: %s is already at the top\n') % patch)
1162 return 0
1162 return 0
1163
1163
1164 pushable, reason = self.pushable(patch)
1164 pushable, reason = self.pushable(patch)
1165 if pushable:
1165 if pushable:
1166 if self.series.index(patch) < self.seriesend():
1166 if self.series.index(patch) < self.seriesend():
1167 raise util.Abort(
1167 raise util.Abort(
1168 _("cannot push to a previous patch: %s") % patch)
1168 _("cannot push to a previous patch: %s") % patch)
1169 else:
1169 else:
1170 if reason:
1170 if reason:
1171 reason = _('guarded by %s') % reason
1171 reason = _('guarded by %s') % reason
1172 else:
1172 else:
1173 reason = _('no matching guards')
1173 reason = _('no matching guards')
1174 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1174 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1175 return 1
1175 return 1
1176 elif all:
1176 elif all:
1177 patch = self.series[-1]
1177 patch = self.series[-1]
1178 if self.isapplied(patch):
1178 if self.isapplied(patch):
1179 self.ui.warn(_('all patches are currently applied\n'))
1179 self.ui.warn(_('all patches are currently applied\n'))
1180 return 0
1180 return 0
1181
1181
1182 # Following the above example, starting at 'top' of B:
1182 # Following the above example, starting at 'top' of B:
1183 # qpush should be performed (pushes C), but a subsequent
1183 # qpush should be performed (pushes C), but a subsequent
1184 # qpush without an argument is an error (nothing to
1184 # qpush without an argument is an error (nothing to
1185 # apply). This allows a loop of "...while hg qpush..." to
1185 # apply). This allows a loop of "...while hg qpush..." to
1186 # work as it detects an error when done
1186 # work as it detects an error when done
1187 start = self.seriesend()
1187 start = self.seriesend()
1188 if start == len(self.series):
1188 if start == len(self.series):
1189 self.ui.warn(_('patch series already fully applied\n'))
1189 self.ui.warn(_('patch series already fully applied\n'))
1190 return 1
1190 return 1
1191 if not force:
1191 if not force:
1192 self.checklocalchanges(repo, refresh=self.applied)
1192 self.checklocalchanges(repo, refresh=self.applied)
1193
1193
1194 if exact:
1194 if exact:
1195 if move:
1195 if move:
1196 raise util.Abort(_("cannot use --exact and --move together"))
1196 raise util.Abort(_("cannot use --exact and --move together"))
1197 if self.applied:
1197 if self.applied:
1198 raise util.Abort(_("cannot push --exact with applied patches"))
1198 raise util.Abort(_("cannot push --exact with applied patches"))
1199 root = self.series[start]
1199 root = self.series[start]
1200 target = patchheader(self.join(root), self.plainmode).parent
1200 target = patchheader(self.join(root), self.plainmode).parent
1201 if not target:
1201 if not target:
1202 raise util.Abort(
1202 raise util.Abort(
1203 _("%s does not have a parent recorded") % root)
1203 _("%s does not have a parent recorded") % root)
1204 if not repo[target] == repo['.']:
1204 if not repo[target] == repo['.']:
1205 hg.update(repo, target)
1205 hg.update(repo, target)
1206
1206
1207 if move:
1207 if move:
1208 if not patch:
1208 if not patch:
1209 raise util.Abort(_("please specify the patch to move"))
1209 raise util.Abort(_("please specify the patch to move"))
1210 for fullstart, rpn in enumerate(self.fullseries):
1210 for fullstart, rpn in enumerate(self.fullseries):
1211 # strip markers for patch guards
1211 # strip markers for patch guards
1212 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1212 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1213 break
1213 break
1214 for i, rpn in enumerate(self.fullseries[fullstart:]):
1214 for i, rpn in enumerate(self.fullseries[fullstart:]):
1215 # strip markers for patch guards
1215 # strip markers for patch guards
1216 if self.guard_re.split(rpn, 1)[0] == patch:
1216 if self.guard_re.split(rpn, 1)[0] == patch:
1217 break
1217 break
1218 index = fullstart + i
1218 index = fullstart + i
1219 assert index < len(self.fullseries)
1219 assert index < len(self.fullseries)
1220 fullpatch = self.fullseries[index]
1220 fullpatch = self.fullseries[index]
1221 del self.fullseries[index]
1221 del self.fullseries[index]
1222 self.fullseries.insert(fullstart, fullpatch)
1222 self.fullseries.insert(fullstart, fullpatch)
1223 self.parseseries()
1223 self.parseseries()
1224 self.seriesdirty = True
1224 self.seriesdirty = True
1225
1225
1226 self.applieddirty = True
1226 self.applieddirty = True
1227 if start > 0:
1227 if start > 0:
1228 self.checktoppatch(repo)
1228 self.checktoppatch(repo)
1229 if not patch:
1229 if not patch:
1230 patch = self.series[start]
1230 patch = self.series[start]
1231 end = start + 1
1231 end = start + 1
1232 else:
1232 else:
1233 end = self.series.index(patch, start) + 1
1233 end = self.series.index(patch, start) + 1
1234
1234
1235 s = self.series[start:end]
1235 s = self.series[start:end]
1236 all_files = set()
1236 all_files = set()
1237 try:
1237 try:
1238 if mergeq:
1238 if mergeq:
1239 ret = self.mergepatch(repo, mergeq, s, diffopts)
1239 ret = self.mergepatch(repo, mergeq, s, diffopts)
1240 else:
1240 else:
1241 ret = self.apply(repo, s, list, all_files=all_files)
1241 ret = self.apply(repo, s, list, all_files=all_files)
1242 except:
1242 except:
1243 self.ui.warn(_('cleaning up working directory...'))
1243 self.ui.warn(_('cleaning up working directory...'))
1244 node = repo.dirstate.p1()
1244 node = repo.dirstate.p1()
1245 hg.revert(repo, node, None)
1245 hg.revert(repo, node, None)
1246 # only remove unknown files that we know we touched or
1246 # only remove unknown files that we know we touched or
1247 # created while patching
1247 # created while patching
1248 for f in all_files:
1248 for f in all_files:
1249 if f not in repo.dirstate:
1249 if f not in repo.dirstate:
1250 try:
1250 try:
1251 util.unlinkpath(repo.wjoin(f))
1251 util.unlinkpath(repo.wjoin(f))
1252 except OSError, inst:
1252 except OSError, inst:
1253 if inst.errno != errno.ENOENT:
1253 if inst.errno != errno.ENOENT:
1254 raise
1254 raise
1255 self.ui.warn(_('done\n'))
1255 self.ui.warn(_('done\n'))
1256 raise
1256 raise
1257
1257
1258 if not self.applied:
1258 if not self.applied:
1259 return ret[0]
1259 return ret[0]
1260 top = self.applied[-1].name
1260 top = self.applied[-1].name
1261 if ret[0] and ret[0] > 1:
1261 if ret[0] and ret[0] > 1:
1262 msg = _("errors during apply, please fix and refresh %s\n")
1262 msg = _("errors during apply, please fix and refresh %s\n")
1263 self.ui.write(msg % top)
1263 self.ui.write(msg % top)
1264 else:
1264 else:
1265 self.ui.write(_("now at: %s\n") % top)
1265 self.ui.write(_("now at: %s\n") % top)
1266 return ret[0]
1266 return ret[0]
1267
1267
1268 finally:
1268 finally:
1269 wlock.release()
1269 wlock.release()
1270
1270
1271 def pop(self, repo, patch=None, force=False, update=True, all=False):
1271 def pop(self, repo, patch=None, force=False, update=True, all=False):
1272 wlock = repo.wlock()
1272 wlock = repo.wlock()
1273 try:
1273 try:
1274 if patch:
1274 if patch:
1275 # index, rev, patch
1275 # index, rev, patch
1276 info = self.isapplied(patch)
1276 info = self.isapplied(patch)
1277 if not info:
1277 if not info:
1278 patch = self.lookup(patch)
1278 patch = self.lookup(patch)
1279 info = self.isapplied(patch)
1279 info = self.isapplied(patch)
1280 if not info:
1280 if not info:
1281 raise util.Abort(_("patch %s is not applied") % patch)
1281 raise util.Abort(_("patch %s is not applied") % patch)
1282
1282
1283 if not self.applied:
1283 if not self.applied:
1284 # Allow qpop -a to work repeatedly,
1284 # Allow qpop -a to work repeatedly,
1285 # but not qpop without an argument
1285 # but not qpop without an argument
1286 self.ui.warn(_("no patches applied\n"))
1286 self.ui.warn(_("no patches applied\n"))
1287 return not all
1287 return not all
1288
1288
1289 if all:
1289 if all:
1290 start = 0
1290 start = 0
1291 elif patch:
1291 elif patch:
1292 start = info[0] + 1
1292 start = info[0] + 1
1293 else:
1293 else:
1294 start = len(self.applied) - 1
1294 start = len(self.applied) - 1
1295
1295
1296 if start >= len(self.applied):
1296 if start >= len(self.applied):
1297 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1297 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1298 return
1298 return
1299
1299
1300 if not update:
1300 if not update:
1301 parents = repo.dirstate.parents()
1301 parents = repo.dirstate.parents()
1302 rr = [x.node for x in self.applied]
1302 rr = [x.node for x in self.applied]
1303 for p in parents:
1303 for p in parents:
1304 if p in rr:
1304 if p in rr:
1305 self.ui.warn(_("qpop: forcing dirstate update\n"))
1305 self.ui.warn(_("qpop: forcing dirstate update\n"))
1306 update = True
1306 update = True
1307 else:
1307 else:
1308 parents = [p.node() for p in repo[None].parents()]
1308 parents = [p.node() for p in repo[None].parents()]
1309 needupdate = False
1309 needupdate = False
1310 for entry in self.applied[start:]:
1310 for entry in self.applied[start:]:
1311 if entry.node in parents:
1311 if entry.node in parents:
1312 needupdate = True
1312 needupdate = True
1313 break
1313 break
1314 update = needupdate
1314 update = needupdate
1315
1315
1316 if not force and update:
1316 if not force and update:
1317 self.checklocalchanges(repo)
1317 self.checklocalchanges(repo)
1318
1318
1319 self.applieddirty = True
1319 self.applieddirty = True
1320 end = len(self.applied)
1320 end = len(self.applied)
1321 rev = self.applied[start].node
1321 rev = self.applied[start].node
1322 if update:
1322 if update:
1323 top = self.checktoppatch(repo)[0]
1323 top = self.checktoppatch(repo)[0]
1324
1324
1325 try:
1325 try:
1326 heads = repo.changelog.heads(rev)
1326 heads = repo.changelog.heads(rev)
1327 except error.LookupError:
1327 except error.LookupError:
1328 node = short(rev)
1328 node = short(rev)
1329 raise util.Abort(_('trying to pop unknown node %s') % node)
1329 raise util.Abort(_('trying to pop unknown node %s') % node)
1330
1330
1331 if heads != [self.applied[-1].node]:
1331 if heads != [self.applied[-1].node]:
1332 raise util.Abort(_("popping would remove a revision not "
1332 raise util.Abort(_("popping would remove a revision not "
1333 "managed by this patch queue"))
1333 "managed by this patch queue"))
1334 if not repo[self.applied[-1].node].mutable():
1334 if not repo[self.applied[-1].node].mutable():
1335 raise util.Abort(
1335 raise util.Abort(
1336 _("popping would remove an immutable revision"),
1336 _("popping would remove an immutable revision"),
1337 hint=_('see "hg help phases" for details'))
1337 hint=_('see "hg help phases" for details'))
1338
1338
1339 # we know there are no local changes, so we can make a simplified
1339 # we know there are no local changes, so we can make a simplified
1340 # form of hg.update.
1340 # form of hg.update.
1341 if update:
1341 if update:
1342 qp = self.qparents(repo, rev)
1342 qp = self.qparents(repo, rev)
1343 ctx = repo[qp]
1343 ctx = repo[qp]
1344 m, a, r, d = repo.status(qp, top)[:4]
1344 m, a, r, d = repo.status(qp, top)[:4]
1345 if d:
1345 if d:
1346 raise util.Abort(_("deletions found between repo revs"))
1346 raise util.Abort(_("deletions found between repo revs"))
1347 for f in a:
1347 for f in a:
1348 try:
1348 try:
1349 util.unlinkpath(repo.wjoin(f))
1349 util.unlinkpath(repo.wjoin(f))
1350 except OSError, e:
1350 except OSError, e:
1351 if e.errno != errno.ENOENT:
1351 if e.errno != errno.ENOENT:
1352 raise
1352 raise
1353 repo.dirstate.drop(f)
1353 repo.dirstate.drop(f)
1354 for f in m + r:
1354 for f in m + r:
1355 fctx = ctx[f]
1355 fctx = ctx[f]
1356 repo.wwrite(f, fctx.data(), fctx.flags())
1356 repo.wwrite(f, fctx.data(), fctx.flags())
1357 repo.dirstate.normal(f)
1357 repo.dirstate.normal(f)
1358 repo.dirstate.setparents(qp, nullid)
1358 repo.setparents(qp, nullid)
1359 for patch in reversed(self.applied[start:end]):
1359 for patch in reversed(self.applied[start:end]):
1360 self.ui.status(_("popping %s\n") % patch.name)
1360 self.ui.status(_("popping %s\n") % patch.name)
1361 del self.applied[start:end]
1361 del self.applied[start:end]
1362 self.strip(repo, [rev], update=False, backup='strip')
1362 self.strip(repo, [rev], update=False, backup='strip')
1363 if self.applied:
1363 if self.applied:
1364 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1364 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1365 else:
1365 else:
1366 self.ui.write(_("patch queue now empty\n"))
1366 self.ui.write(_("patch queue now empty\n"))
1367 finally:
1367 finally:
1368 wlock.release()
1368 wlock.release()
1369
1369
1370 def diff(self, repo, pats, opts):
1370 def diff(self, repo, pats, opts):
1371 top, patch = self.checktoppatch(repo)
1371 top, patch = self.checktoppatch(repo)
1372 if not top:
1372 if not top:
1373 self.ui.write(_("no patches applied\n"))
1373 self.ui.write(_("no patches applied\n"))
1374 return
1374 return
1375 qp = self.qparents(repo, top)
1375 qp = self.qparents(repo, top)
1376 if opts.get('reverse'):
1376 if opts.get('reverse'):
1377 node1, node2 = None, qp
1377 node1, node2 = None, qp
1378 else:
1378 else:
1379 node1, node2 = qp, None
1379 node1, node2 = qp, None
1380 diffopts = self.diffopts(opts, patch)
1380 diffopts = self.diffopts(opts, patch)
1381 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1381 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1382
1382
1383 def refresh(self, repo, pats=None, **opts):
1383 def refresh(self, repo, pats=None, **opts):
1384 if not self.applied:
1384 if not self.applied:
1385 self.ui.write(_("no patches applied\n"))
1385 self.ui.write(_("no patches applied\n"))
1386 return 1
1386 return 1
1387 msg = opts.get('msg', '').rstrip()
1387 msg = opts.get('msg', '').rstrip()
1388 newuser = opts.get('user')
1388 newuser = opts.get('user')
1389 newdate = opts.get('date')
1389 newdate = opts.get('date')
1390 if newdate:
1390 if newdate:
1391 newdate = '%d %d' % util.parsedate(newdate)
1391 newdate = '%d %d' % util.parsedate(newdate)
1392 wlock = repo.wlock()
1392 wlock = repo.wlock()
1393
1393
1394 try:
1394 try:
1395 self.checktoppatch(repo)
1395 self.checktoppatch(repo)
1396 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1396 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1397 if repo.changelog.heads(top) != [top]:
1397 if repo.changelog.heads(top) != [top]:
1398 raise util.Abort(_("cannot refresh a revision with children"))
1398 raise util.Abort(_("cannot refresh a revision with children"))
1399 if not repo[top].mutable():
1399 if not repo[top].mutable():
1400 raise util.Abort(_("cannot refresh immutable revision"),
1400 raise util.Abort(_("cannot refresh immutable revision"),
1401 hint=_('see "hg help phases" for details'))
1401 hint=_('see "hg help phases" for details'))
1402
1402
1403 inclsubs = self.checksubstate(repo)
1403 inclsubs = self.checksubstate(repo)
1404
1404
1405 cparents = repo.changelog.parents(top)
1405 cparents = repo.changelog.parents(top)
1406 patchparent = self.qparents(repo, top)
1406 patchparent = self.qparents(repo, top)
1407 ph = patchheader(self.join(patchfn), self.plainmode)
1407 ph = patchheader(self.join(patchfn), self.plainmode)
1408 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1408 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1409 if msg:
1409 if msg:
1410 ph.setmessage(msg)
1410 ph.setmessage(msg)
1411 if newuser:
1411 if newuser:
1412 ph.setuser(newuser)
1412 ph.setuser(newuser)
1413 if newdate:
1413 if newdate:
1414 ph.setdate(newdate)
1414 ph.setdate(newdate)
1415 ph.setparent(hex(patchparent))
1415 ph.setparent(hex(patchparent))
1416
1416
1417 # only commit new patch when write is complete
1417 # only commit new patch when write is complete
1418 patchf = self.opener(patchfn, 'w', atomictemp=True)
1418 patchf = self.opener(patchfn, 'w', atomictemp=True)
1419
1419
1420 comments = str(ph)
1420 comments = str(ph)
1421 if comments:
1421 if comments:
1422 patchf.write(comments)
1422 patchf.write(comments)
1423
1423
1424 # update the dirstate in place, strip off the qtip commit
1424 # update the dirstate in place, strip off the qtip commit
1425 # and then commit.
1425 # and then commit.
1426 #
1426 #
1427 # this should really read:
1427 # this should really read:
1428 # mm, dd, aa = repo.status(top, patchparent)[:3]
1428 # mm, dd, aa = repo.status(top, patchparent)[:3]
1429 # but we do it backwards to take advantage of manifest/chlog
1429 # but we do it backwards to take advantage of manifest/chlog
1430 # caching against the next repo.status call
1430 # caching against the next repo.status call
1431 mm, aa, dd = repo.status(patchparent, top)[:3]
1431 mm, aa, dd = repo.status(patchparent, top)[:3]
1432 changes = repo.changelog.read(top)
1432 changes = repo.changelog.read(top)
1433 man = repo.manifest.read(changes[0])
1433 man = repo.manifest.read(changes[0])
1434 aaa = aa[:]
1434 aaa = aa[:]
1435 matchfn = scmutil.match(repo[None], pats, opts)
1435 matchfn = scmutil.match(repo[None], pats, opts)
1436 # in short mode, we only diff the files included in the
1436 # in short mode, we only diff the files included in the
1437 # patch already plus specified files
1437 # patch already plus specified files
1438 if opts.get('short'):
1438 if opts.get('short'):
1439 # if amending a patch, we start with existing
1439 # if amending a patch, we start with existing
1440 # files plus specified files - unfiltered
1440 # files plus specified files - unfiltered
1441 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1441 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1442 # filter with inc/exl options
1442 # filter with inc/exl options
1443 matchfn = scmutil.match(repo[None], opts=opts)
1443 matchfn = scmutil.match(repo[None], opts=opts)
1444 else:
1444 else:
1445 match = scmutil.matchall(repo)
1445 match = scmutil.matchall(repo)
1446 m, a, r, d = repo.status(match=match)[:4]
1446 m, a, r, d = repo.status(match=match)[:4]
1447 mm = set(mm)
1447 mm = set(mm)
1448 aa = set(aa)
1448 aa = set(aa)
1449 dd = set(dd)
1449 dd = set(dd)
1450
1450
1451 # we might end up with files that were added between
1451 # we might end up with files that were added between
1452 # qtip and the dirstate parent, but then changed in the
1452 # qtip and the dirstate parent, but then changed in the
1453 # local dirstate. in this case, we want them to only
1453 # local dirstate. in this case, we want them to only
1454 # show up in the added section
1454 # show up in the added section
1455 for x in m:
1455 for x in m:
1456 if x not in aa:
1456 if x not in aa:
1457 mm.add(x)
1457 mm.add(x)
1458 # we might end up with files added by the local dirstate that
1458 # we might end up with files added by the local dirstate that
1459 # were deleted by the patch. In this case, they should only
1459 # were deleted by the patch. In this case, they should only
1460 # show up in the changed section.
1460 # show up in the changed section.
1461 for x in a:
1461 for x in a:
1462 if x in dd:
1462 if x in dd:
1463 dd.remove(x)
1463 dd.remove(x)
1464 mm.add(x)
1464 mm.add(x)
1465 else:
1465 else:
1466 aa.add(x)
1466 aa.add(x)
1467 # make sure any files deleted in the local dirstate
1467 # make sure any files deleted in the local dirstate
1468 # are not in the add or change column of the patch
1468 # are not in the add or change column of the patch
1469 forget = []
1469 forget = []
1470 for x in d + r:
1470 for x in d + r:
1471 if x in aa:
1471 if x in aa:
1472 aa.remove(x)
1472 aa.remove(x)
1473 forget.append(x)
1473 forget.append(x)
1474 continue
1474 continue
1475 else:
1475 else:
1476 mm.discard(x)
1476 mm.discard(x)
1477 dd.add(x)
1477 dd.add(x)
1478
1478
1479 m = list(mm)
1479 m = list(mm)
1480 r = list(dd)
1480 r = list(dd)
1481 a = list(aa)
1481 a = list(aa)
1482 c = [filter(matchfn, l) for l in (m, a, r)]
1482 c = [filter(matchfn, l) for l in (m, a, r)]
1483 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1483 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1484 chunks = patchmod.diff(repo, patchparent, match=match,
1484 chunks = patchmod.diff(repo, patchparent, match=match,
1485 changes=c, opts=diffopts)
1485 changes=c, opts=diffopts)
1486 for chunk in chunks:
1486 for chunk in chunks:
1487 patchf.write(chunk)
1487 patchf.write(chunk)
1488
1488
1489 try:
1489 try:
1490 if diffopts.git or diffopts.upgrade:
1490 if diffopts.git or diffopts.upgrade:
1491 copies = {}
1491 copies = {}
1492 for dst in a:
1492 for dst in a:
1493 src = repo.dirstate.copied(dst)
1493 src = repo.dirstate.copied(dst)
1494 # during qfold, the source file for copies may
1494 # during qfold, the source file for copies may
1495 # be removed. Treat this as a simple add.
1495 # be removed. Treat this as a simple add.
1496 if src is not None and src in repo.dirstate:
1496 if src is not None and src in repo.dirstate:
1497 copies.setdefault(src, []).append(dst)
1497 copies.setdefault(src, []).append(dst)
1498 repo.dirstate.add(dst)
1498 repo.dirstate.add(dst)
1499 # remember the copies between patchparent and qtip
1499 # remember the copies between patchparent and qtip
1500 for dst in aaa:
1500 for dst in aaa:
1501 f = repo.file(dst)
1501 f = repo.file(dst)
1502 src = f.renamed(man[dst])
1502 src = f.renamed(man[dst])
1503 if src:
1503 if src:
1504 copies.setdefault(src[0], []).extend(
1504 copies.setdefault(src[0], []).extend(
1505 copies.get(dst, []))
1505 copies.get(dst, []))
1506 if dst in a:
1506 if dst in a:
1507 copies[src[0]].append(dst)
1507 copies[src[0]].append(dst)
1508 # we can't copy a file created by the patch itself
1508 # we can't copy a file created by the patch itself
1509 if dst in copies:
1509 if dst in copies:
1510 del copies[dst]
1510 del copies[dst]
1511 for src, dsts in copies.iteritems():
1511 for src, dsts in copies.iteritems():
1512 for dst in dsts:
1512 for dst in dsts:
1513 repo.dirstate.copy(src, dst)
1513 repo.dirstate.copy(src, dst)
1514 else:
1514 else:
1515 for dst in a:
1515 for dst in a:
1516 repo.dirstate.add(dst)
1516 repo.dirstate.add(dst)
1517 # Drop useless copy information
1517 # Drop useless copy information
1518 for f in list(repo.dirstate.copies()):
1518 for f in list(repo.dirstate.copies()):
1519 repo.dirstate.copy(None, f)
1519 repo.dirstate.copy(None, f)
1520 for f in r:
1520 for f in r:
1521 repo.dirstate.remove(f)
1521 repo.dirstate.remove(f)
1522 # if the patch excludes a modified file, mark that
1522 # if the patch excludes a modified file, mark that
1523 # file with mtime=0 so status can see it.
1523 # file with mtime=0 so status can see it.
1524 mm = []
1524 mm = []
1525 for i in xrange(len(m)-1, -1, -1):
1525 for i in xrange(len(m)-1, -1, -1):
1526 if not matchfn(m[i]):
1526 if not matchfn(m[i]):
1527 mm.append(m[i])
1527 mm.append(m[i])
1528 del m[i]
1528 del m[i]
1529 for f in m:
1529 for f in m:
1530 repo.dirstate.normal(f)
1530 repo.dirstate.normal(f)
1531 for f in mm:
1531 for f in mm:
1532 repo.dirstate.normallookup(f)
1532 repo.dirstate.normallookup(f)
1533 for f in forget:
1533 for f in forget:
1534 repo.dirstate.drop(f)
1534 repo.dirstate.drop(f)
1535
1535
1536 if not msg:
1536 if not msg:
1537 if not ph.message:
1537 if not ph.message:
1538 message = "[mq]: %s\n" % patchfn
1538 message = "[mq]: %s\n" % patchfn
1539 else:
1539 else:
1540 message = "\n".join(ph.message)
1540 message = "\n".join(ph.message)
1541 else:
1541 else:
1542 message = msg
1542 message = msg
1543
1543
1544 user = ph.user or changes[1]
1544 user = ph.user or changes[1]
1545
1545
1546 oldphase = repo[top].phase()
1546 oldphase = repo[top].phase()
1547
1547
1548 # assumes strip can roll itself back if interrupted
1548 # assumes strip can roll itself back if interrupted
1549 repo.dirstate.setparents(*cparents)
1549 repo.setparents(*cparents)
1550 self.applied.pop()
1550 self.applied.pop()
1551 self.applieddirty = True
1551 self.applieddirty = True
1552 self.strip(repo, [top], update=False,
1552 self.strip(repo, [top], update=False,
1553 backup='strip')
1553 backup='strip')
1554 except:
1554 except:
1555 repo.dirstate.invalidate()
1555 repo.dirstate.invalidate()
1556 raise
1556 raise
1557
1557
1558 try:
1558 try:
1559 # might be nice to attempt to roll back strip after this
1559 # might be nice to attempt to roll back strip after this
1560
1560
1561 # Ensure we create a new changeset in the same phase than
1561 # Ensure we create a new changeset in the same phase than
1562 # the old one.
1562 # the old one.
1563 n = newcommit(repo, oldphase, message, user, ph.date,
1563 n = newcommit(repo, oldphase, message, user, ph.date,
1564 match=match, force=True)
1564 match=match, force=True)
1565 # only write patch after a successful commit
1565 # only write patch after a successful commit
1566 patchf.close()
1566 patchf.close()
1567 self.applied.append(statusentry(n, patchfn))
1567 self.applied.append(statusentry(n, patchfn))
1568 except:
1568 except:
1569 ctx = repo[cparents[0]]
1569 ctx = repo[cparents[0]]
1570 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1570 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1571 self.savedirty()
1571 self.savedirty()
1572 self.ui.warn(_('refresh interrupted while patch was popped! '
1572 self.ui.warn(_('refresh interrupted while patch was popped! '
1573 '(revert --all, qpush to recover)\n'))
1573 '(revert --all, qpush to recover)\n'))
1574 raise
1574 raise
1575 finally:
1575 finally:
1576 wlock.release()
1576 wlock.release()
1577 self.removeundo(repo)
1577 self.removeundo(repo)
1578
1578
1579 def init(self, repo, create=False):
1579 def init(self, repo, create=False):
1580 if not create and os.path.isdir(self.path):
1580 if not create and os.path.isdir(self.path):
1581 raise util.Abort(_("patch queue directory already exists"))
1581 raise util.Abort(_("patch queue directory already exists"))
1582 try:
1582 try:
1583 os.mkdir(self.path)
1583 os.mkdir(self.path)
1584 except OSError, inst:
1584 except OSError, inst:
1585 if inst.errno != errno.EEXIST or not create:
1585 if inst.errno != errno.EEXIST or not create:
1586 raise
1586 raise
1587 if create:
1587 if create:
1588 return self.qrepo(create=True)
1588 return self.qrepo(create=True)
1589
1589
1590 def unapplied(self, repo, patch=None):
1590 def unapplied(self, repo, patch=None):
1591 if patch and patch not in self.series:
1591 if patch and patch not in self.series:
1592 raise util.Abort(_("patch %s is not in series file") % patch)
1592 raise util.Abort(_("patch %s is not in series file") % patch)
1593 if not patch:
1593 if not patch:
1594 start = self.seriesend()
1594 start = self.seriesend()
1595 else:
1595 else:
1596 start = self.series.index(patch) + 1
1596 start = self.series.index(patch) + 1
1597 unapplied = []
1597 unapplied = []
1598 for i in xrange(start, len(self.series)):
1598 for i in xrange(start, len(self.series)):
1599 pushable, reason = self.pushable(i)
1599 pushable, reason = self.pushable(i)
1600 if pushable:
1600 if pushable:
1601 unapplied.append((i, self.series[i]))
1601 unapplied.append((i, self.series[i]))
1602 self.explainpushable(i)
1602 self.explainpushable(i)
1603 return unapplied
1603 return unapplied
1604
1604
1605 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1605 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1606 summary=False):
1606 summary=False):
1607 def displayname(pfx, patchname, state):
1607 def displayname(pfx, patchname, state):
1608 if pfx:
1608 if pfx:
1609 self.ui.write(pfx)
1609 self.ui.write(pfx)
1610 if summary:
1610 if summary:
1611 ph = patchheader(self.join(patchname), self.plainmode)
1611 ph = patchheader(self.join(patchname), self.plainmode)
1612 msg = ph.message and ph.message[0] or ''
1612 msg = ph.message and ph.message[0] or ''
1613 if self.ui.formatted():
1613 if self.ui.formatted():
1614 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1614 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1615 if width > 0:
1615 if width > 0:
1616 msg = util.ellipsis(msg, width)
1616 msg = util.ellipsis(msg, width)
1617 else:
1617 else:
1618 msg = ''
1618 msg = ''
1619 self.ui.write(patchname, label='qseries.' + state)
1619 self.ui.write(patchname, label='qseries.' + state)
1620 self.ui.write(': ')
1620 self.ui.write(': ')
1621 self.ui.write(msg, label='qseries.message.' + state)
1621 self.ui.write(msg, label='qseries.message.' + state)
1622 else:
1622 else:
1623 self.ui.write(patchname, label='qseries.' + state)
1623 self.ui.write(patchname, label='qseries.' + state)
1624 self.ui.write('\n')
1624 self.ui.write('\n')
1625
1625
1626 applied = set([p.name for p in self.applied])
1626 applied = set([p.name for p in self.applied])
1627 if length is None:
1627 if length is None:
1628 length = len(self.series) - start
1628 length = len(self.series) - start
1629 if not missing:
1629 if not missing:
1630 if self.ui.verbose:
1630 if self.ui.verbose:
1631 idxwidth = len(str(start + length - 1))
1631 idxwidth = len(str(start + length - 1))
1632 for i in xrange(start, start + length):
1632 for i in xrange(start, start + length):
1633 patch = self.series[i]
1633 patch = self.series[i]
1634 if patch in applied:
1634 if patch in applied:
1635 char, state = 'A', 'applied'
1635 char, state = 'A', 'applied'
1636 elif self.pushable(i)[0]:
1636 elif self.pushable(i)[0]:
1637 char, state = 'U', 'unapplied'
1637 char, state = 'U', 'unapplied'
1638 else:
1638 else:
1639 char, state = 'G', 'guarded'
1639 char, state = 'G', 'guarded'
1640 pfx = ''
1640 pfx = ''
1641 if self.ui.verbose:
1641 if self.ui.verbose:
1642 pfx = '%*d %s ' % (idxwidth, i, char)
1642 pfx = '%*d %s ' % (idxwidth, i, char)
1643 elif status and status != char:
1643 elif status and status != char:
1644 continue
1644 continue
1645 displayname(pfx, patch, state)
1645 displayname(pfx, patch, state)
1646 else:
1646 else:
1647 msng_list = []
1647 msng_list = []
1648 for root, dirs, files in os.walk(self.path):
1648 for root, dirs, files in os.walk(self.path):
1649 d = root[len(self.path) + 1:]
1649 d = root[len(self.path) + 1:]
1650 for f in files:
1650 for f in files:
1651 fl = os.path.join(d, f)
1651 fl = os.path.join(d, f)
1652 if (fl not in self.series and
1652 if (fl not in self.series and
1653 fl not in (self.statuspath, self.seriespath,
1653 fl not in (self.statuspath, self.seriespath,
1654 self.guardspath)
1654 self.guardspath)
1655 and not fl.startswith('.')):
1655 and not fl.startswith('.')):
1656 msng_list.append(fl)
1656 msng_list.append(fl)
1657 for x in sorted(msng_list):
1657 for x in sorted(msng_list):
1658 pfx = self.ui.verbose and ('D ') or ''
1658 pfx = self.ui.verbose and ('D ') or ''
1659 displayname(pfx, x, 'missing')
1659 displayname(pfx, x, 'missing')
1660
1660
1661 def issaveline(self, l):
1661 def issaveline(self, l):
1662 if l.name == '.hg.patches.save.line':
1662 if l.name == '.hg.patches.save.line':
1663 return True
1663 return True
1664
1664
1665 def qrepo(self, create=False):
1665 def qrepo(self, create=False):
1666 ui = self.ui.copy()
1666 ui = self.ui.copy()
1667 ui.setconfig('paths', 'default', '', overlay=False)
1667 ui.setconfig('paths', 'default', '', overlay=False)
1668 ui.setconfig('paths', 'default-push', '', overlay=False)
1668 ui.setconfig('paths', 'default-push', '', overlay=False)
1669 if create or os.path.isdir(self.join(".hg")):
1669 if create or os.path.isdir(self.join(".hg")):
1670 return hg.repository(ui, path=self.path, create=create)
1670 return hg.repository(ui, path=self.path, create=create)
1671
1671
1672 def restore(self, repo, rev, delete=None, qupdate=None):
1672 def restore(self, repo, rev, delete=None, qupdate=None):
1673 desc = repo[rev].description().strip()
1673 desc = repo[rev].description().strip()
1674 lines = desc.splitlines()
1674 lines = desc.splitlines()
1675 i = 0
1675 i = 0
1676 datastart = None
1676 datastart = None
1677 series = []
1677 series = []
1678 applied = []
1678 applied = []
1679 qpp = None
1679 qpp = None
1680 for i, line in enumerate(lines):
1680 for i, line in enumerate(lines):
1681 if line == 'Patch Data:':
1681 if line == 'Patch Data:':
1682 datastart = i + 1
1682 datastart = i + 1
1683 elif line.startswith('Dirstate:'):
1683 elif line.startswith('Dirstate:'):
1684 l = line.rstrip()
1684 l = line.rstrip()
1685 l = l[10:].split(' ')
1685 l = l[10:].split(' ')
1686 qpp = [bin(x) for x in l]
1686 qpp = [bin(x) for x in l]
1687 elif datastart is not None:
1687 elif datastart is not None:
1688 l = line.rstrip()
1688 l = line.rstrip()
1689 n, name = l.split(':', 1)
1689 n, name = l.split(':', 1)
1690 if n:
1690 if n:
1691 applied.append(statusentry(bin(n), name))
1691 applied.append(statusentry(bin(n), name))
1692 else:
1692 else:
1693 series.append(l)
1693 series.append(l)
1694 if datastart is None:
1694 if datastart is None:
1695 self.ui.warn(_("No saved patch data found\n"))
1695 self.ui.warn(_("No saved patch data found\n"))
1696 return 1
1696 return 1
1697 self.ui.warn(_("restoring status: %s\n") % lines[0])
1697 self.ui.warn(_("restoring status: %s\n") % lines[0])
1698 self.fullseries = series
1698 self.fullseries = series
1699 self.applied = applied
1699 self.applied = applied
1700 self.parseseries()
1700 self.parseseries()
1701 self.seriesdirty = True
1701 self.seriesdirty = True
1702 self.applieddirty = True
1702 self.applieddirty = True
1703 heads = repo.changelog.heads()
1703 heads = repo.changelog.heads()
1704 if delete:
1704 if delete:
1705 if rev not in heads:
1705 if rev not in heads:
1706 self.ui.warn(_("save entry has children, leaving it alone\n"))
1706 self.ui.warn(_("save entry has children, leaving it alone\n"))
1707 else:
1707 else:
1708 self.ui.warn(_("removing save entry %s\n") % short(rev))
1708 self.ui.warn(_("removing save entry %s\n") % short(rev))
1709 pp = repo.dirstate.parents()
1709 pp = repo.dirstate.parents()
1710 if rev in pp:
1710 if rev in pp:
1711 update = True
1711 update = True
1712 else:
1712 else:
1713 update = False
1713 update = False
1714 self.strip(repo, [rev], update=update, backup='strip')
1714 self.strip(repo, [rev], update=update, backup='strip')
1715 if qpp:
1715 if qpp:
1716 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1716 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1717 (short(qpp[0]), short(qpp[1])))
1717 (short(qpp[0]), short(qpp[1])))
1718 if qupdate:
1718 if qupdate:
1719 self.ui.status(_("updating queue directory\n"))
1719 self.ui.status(_("updating queue directory\n"))
1720 r = self.qrepo()
1720 r = self.qrepo()
1721 if not r:
1721 if not r:
1722 self.ui.warn(_("Unable to load queue repository\n"))
1722 self.ui.warn(_("Unable to load queue repository\n"))
1723 return 1
1723 return 1
1724 hg.clean(r, qpp[0])
1724 hg.clean(r, qpp[0])
1725
1725
1726 def save(self, repo, msg=None):
1726 def save(self, repo, msg=None):
1727 if not self.applied:
1727 if not self.applied:
1728 self.ui.warn(_("save: no patches applied, exiting\n"))
1728 self.ui.warn(_("save: no patches applied, exiting\n"))
1729 return 1
1729 return 1
1730 if self.issaveline(self.applied[-1]):
1730 if self.issaveline(self.applied[-1]):
1731 self.ui.warn(_("status is already saved\n"))
1731 self.ui.warn(_("status is already saved\n"))
1732 return 1
1732 return 1
1733
1733
1734 if not msg:
1734 if not msg:
1735 msg = _("hg patches saved state")
1735 msg = _("hg patches saved state")
1736 else:
1736 else:
1737 msg = "hg patches: " + msg.rstrip('\r\n')
1737 msg = "hg patches: " + msg.rstrip('\r\n')
1738 r = self.qrepo()
1738 r = self.qrepo()
1739 if r:
1739 if r:
1740 pp = r.dirstate.parents()
1740 pp = r.dirstate.parents()
1741 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1741 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1742 msg += "\n\nPatch Data:\n"
1742 msg += "\n\nPatch Data:\n"
1743 msg += ''.join('%s\n' % x for x in self.applied)
1743 msg += ''.join('%s\n' % x for x in self.applied)
1744 msg += ''.join(':%s\n' % x for x in self.fullseries)
1744 msg += ''.join(':%s\n' % x for x in self.fullseries)
1745 n = repo.commit(msg, force=True)
1745 n = repo.commit(msg, force=True)
1746 if not n:
1746 if not n:
1747 self.ui.warn(_("repo commit failed\n"))
1747 self.ui.warn(_("repo commit failed\n"))
1748 return 1
1748 return 1
1749 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1749 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1750 self.applieddirty = True
1750 self.applieddirty = True
1751 self.removeundo(repo)
1751 self.removeundo(repo)
1752
1752
1753 def fullseriesend(self):
1753 def fullseriesend(self):
1754 if self.applied:
1754 if self.applied:
1755 p = self.applied[-1].name
1755 p = self.applied[-1].name
1756 end = self.findseries(p)
1756 end = self.findseries(p)
1757 if end is None:
1757 if end is None:
1758 return len(self.fullseries)
1758 return len(self.fullseries)
1759 return end + 1
1759 return end + 1
1760 return 0
1760 return 0
1761
1761
1762 def seriesend(self, all_patches=False):
1762 def seriesend(self, all_patches=False):
1763 """If all_patches is False, return the index of the next pushable patch
1763 """If all_patches is False, return the index of the next pushable patch
1764 in the series, or the series length. If all_patches is True, return the
1764 in the series, or the series length. If all_patches is True, return the
1765 index of the first patch past the last applied one.
1765 index of the first patch past the last applied one.
1766 """
1766 """
1767 end = 0
1767 end = 0
1768 def next(start):
1768 def next(start):
1769 if all_patches or start >= len(self.series):
1769 if all_patches or start >= len(self.series):
1770 return start
1770 return start
1771 for i in xrange(start, len(self.series)):
1771 for i in xrange(start, len(self.series)):
1772 p, reason = self.pushable(i)
1772 p, reason = self.pushable(i)
1773 if p:
1773 if p:
1774 return i
1774 return i
1775 self.explainpushable(i)
1775 self.explainpushable(i)
1776 return len(self.series)
1776 return len(self.series)
1777 if self.applied:
1777 if self.applied:
1778 p = self.applied[-1].name
1778 p = self.applied[-1].name
1779 try:
1779 try:
1780 end = self.series.index(p)
1780 end = self.series.index(p)
1781 except ValueError:
1781 except ValueError:
1782 return 0
1782 return 0
1783 return next(end + 1)
1783 return next(end + 1)
1784 return next(end)
1784 return next(end)
1785
1785
1786 def appliedname(self, index):
1786 def appliedname(self, index):
1787 pname = self.applied[index].name
1787 pname = self.applied[index].name
1788 if not self.ui.verbose:
1788 if not self.ui.verbose:
1789 p = pname
1789 p = pname
1790 else:
1790 else:
1791 p = str(self.series.index(pname)) + " " + pname
1791 p = str(self.series.index(pname)) + " " + pname
1792 return p
1792 return p
1793
1793
1794 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1794 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1795 force=None, git=False):
1795 force=None, git=False):
1796 def checkseries(patchname):
1796 def checkseries(patchname):
1797 if patchname in self.series:
1797 if patchname in self.series:
1798 raise util.Abort(_('patch %s is already in the series file')
1798 raise util.Abort(_('patch %s is already in the series file')
1799 % patchname)
1799 % patchname)
1800
1800
1801 if rev:
1801 if rev:
1802 if files:
1802 if files:
1803 raise util.Abort(_('option "-r" not valid when importing '
1803 raise util.Abort(_('option "-r" not valid when importing '
1804 'files'))
1804 'files'))
1805 rev = scmutil.revrange(repo, rev)
1805 rev = scmutil.revrange(repo, rev)
1806 rev.sort(reverse=True)
1806 rev.sort(reverse=True)
1807 if (len(files) > 1 or len(rev) > 1) and patchname:
1807 if (len(files) > 1 or len(rev) > 1) and patchname:
1808 raise util.Abort(_('option "-n" not valid when importing multiple '
1808 raise util.Abort(_('option "-n" not valid when importing multiple '
1809 'patches'))
1809 'patches'))
1810 imported = []
1810 imported = []
1811 if rev:
1811 if rev:
1812 # If mq patches are applied, we can only import revisions
1812 # If mq patches are applied, we can only import revisions
1813 # that form a linear path to qbase.
1813 # that form a linear path to qbase.
1814 # Otherwise, they should form a linear path to a head.
1814 # Otherwise, they should form a linear path to a head.
1815 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1815 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1816 if len(heads) > 1:
1816 if len(heads) > 1:
1817 raise util.Abort(_('revision %d is the root of more than one '
1817 raise util.Abort(_('revision %d is the root of more than one '
1818 'branch') % rev[-1])
1818 'branch') % rev[-1])
1819 if self.applied:
1819 if self.applied:
1820 base = repo.changelog.node(rev[0])
1820 base = repo.changelog.node(rev[0])
1821 if base in [n.node for n in self.applied]:
1821 if base in [n.node for n in self.applied]:
1822 raise util.Abort(_('revision %d is already managed')
1822 raise util.Abort(_('revision %d is already managed')
1823 % rev[0])
1823 % rev[0])
1824 if heads != [self.applied[-1].node]:
1824 if heads != [self.applied[-1].node]:
1825 raise util.Abort(_('revision %d is not the parent of '
1825 raise util.Abort(_('revision %d is not the parent of '
1826 'the queue') % rev[0])
1826 'the queue') % rev[0])
1827 base = repo.changelog.rev(self.applied[0].node)
1827 base = repo.changelog.rev(self.applied[0].node)
1828 lastparent = repo.changelog.parentrevs(base)[0]
1828 lastparent = repo.changelog.parentrevs(base)[0]
1829 else:
1829 else:
1830 if heads != [repo.changelog.node(rev[0])]:
1830 if heads != [repo.changelog.node(rev[0])]:
1831 raise util.Abort(_('revision %d has unmanaged children')
1831 raise util.Abort(_('revision %d has unmanaged children')
1832 % rev[0])
1832 % rev[0])
1833 lastparent = None
1833 lastparent = None
1834
1834
1835 diffopts = self.diffopts({'git': git})
1835 diffopts = self.diffopts({'git': git})
1836 for r in rev:
1836 for r in rev:
1837 if not repo[r].mutable():
1837 if not repo[r].mutable():
1838 raise util.Abort(_('revision %d is not mutable') % r,
1838 raise util.Abort(_('revision %d is not mutable') % r,
1839 hint=_('see "hg help phases" for details'))
1839 hint=_('see "hg help phases" for details'))
1840 p1, p2 = repo.changelog.parentrevs(r)
1840 p1, p2 = repo.changelog.parentrevs(r)
1841 n = repo.changelog.node(r)
1841 n = repo.changelog.node(r)
1842 if p2 != nullrev:
1842 if p2 != nullrev:
1843 raise util.Abort(_('cannot import merge revision %d') % r)
1843 raise util.Abort(_('cannot import merge revision %d') % r)
1844 if lastparent and lastparent != r:
1844 if lastparent and lastparent != r:
1845 raise util.Abort(_('revision %d is not the parent of %d')
1845 raise util.Abort(_('revision %d is not the parent of %d')
1846 % (r, lastparent))
1846 % (r, lastparent))
1847 lastparent = p1
1847 lastparent = p1
1848
1848
1849 if not patchname:
1849 if not patchname:
1850 patchname = normname('%d.diff' % r)
1850 patchname = normname('%d.diff' % r)
1851 checkseries(patchname)
1851 checkseries(patchname)
1852 self.checkpatchname(patchname, force)
1852 self.checkpatchname(patchname, force)
1853 self.fullseries.insert(0, patchname)
1853 self.fullseries.insert(0, patchname)
1854
1854
1855 patchf = self.opener(patchname, "w")
1855 patchf = self.opener(patchname, "w")
1856 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1856 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1857 patchf.close()
1857 patchf.close()
1858
1858
1859 se = statusentry(n, patchname)
1859 se = statusentry(n, patchname)
1860 self.applied.insert(0, se)
1860 self.applied.insert(0, se)
1861
1861
1862 self.added.append(patchname)
1862 self.added.append(patchname)
1863 imported.append(patchname)
1863 imported.append(patchname)
1864 patchname = None
1864 patchname = None
1865 if rev and repo.ui.configbool('mq', 'secret', False):
1865 if rev and repo.ui.configbool('mq', 'secret', False):
1866 # if we added anything with --rev, we must move the secret root
1866 # if we added anything with --rev, we must move the secret root
1867 phases.retractboundary(repo, phases.secret, [n])
1867 phases.retractboundary(repo, phases.secret, [n])
1868 self.parseseries()
1868 self.parseseries()
1869 self.applieddirty = True
1869 self.applieddirty = True
1870 self.seriesdirty = True
1870 self.seriesdirty = True
1871
1871
1872 for i, filename in enumerate(files):
1872 for i, filename in enumerate(files):
1873 if existing:
1873 if existing:
1874 if filename == '-':
1874 if filename == '-':
1875 raise util.Abort(_('-e is incompatible with import from -'))
1875 raise util.Abort(_('-e is incompatible with import from -'))
1876 filename = normname(filename)
1876 filename = normname(filename)
1877 self.checkreservedname(filename)
1877 self.checkreservedname(filename)
1878 originpath = self.join(filename)
1878 originpath = self.join(filename)
1879 if not os.path.isfile(originpath):
1879 if not os.path.isfile(originpath):
1880 raise util.Abort(_("patch %s does not exist") % filename)
1880 raise util.Abort(_("patch %s does not exist") % filename)
1881
1881
1882 if patchname:
1882 if patchname:
1883 self.checkpatchname(patchname, force)
1883 self.checkpatchname(patchname, force)
1884
1884
1885 self.ui.write(_('renaming %s to %s\n')
1885 self.ui.write(_('renaming %s to %s\n')
1886 % (filename, patchname))
1886 % (filename, patchname))
1887 util.rename(originpath, self.join(patchname))
1887 util.rename(originpath, self.join(patchname))
1888 else:
1888 else:
1889 patchname = filename
1889 patchname = filename
1890
1890
1891 else:
1891 else:
1892 if filename == '-' and not patchname:
1892 if filename == '-' and not patchname:
1893 raise util.Abort(_('need --name to import a patch from -'))
1893 raise util.Abort(_('need --name to import a patch from -'))
1894 elif not patchname:
1894 elif not patchname:
1895 patchname = normname(os.path.basename(filename.rstrip('/')))
1895 patchname = normname(os.path.basename(filename.rstrip('/')))
1896 self.checkpatchname(patchname, force)
1896 self.checkpatchname(patchname, force)
1897 try:
1897 try:
1898 if filename == '-':
1898 if filename == '-':
1899 text = self.ui.fin.read()
1899 text = self.ui.fin.read()
1900 else:
1900 else:
1901 fp = url.open(self.ui, filename)
1901 fp = url.open(self.ui, filename)
1902 text = fp.read()
1902 text = fp.read()
1903 fp.close()
1903 fp.close()
1904 except (OSError, IOError):
1904 except (OSError, IOError):
1905 raise util.Abort(_("unable to read file %s") % filename)
1905 raise util.Abort(_("unable to read file %s") % filename)
1906 patchf = self.opener(patchname, "w")
1906 patchf = self.opener(patchname, "w")
1907 patchf.write(text)
1907 patchf.write(text)
1908 patchf.close()
1908 patchf.close()
1909 if not force:
1909 if not force:
1910 checkseries(patchname)
1910 checkseries(patchname)
1911 if patchname not in self.series:
1911 if patchname not in self.series:
1912 index = self.fullseriesend() + i
1912 index = self.fullseriesend() + i
1913 self.fullseries[index:index] = [patchname]
1913 self.fullseries[index:index] = [patchname]
1914 self.parseseries()
1914 self.parseseries()
1915 self.seriesdirty = True
1915 self.seriesdirty = True
1916 self.ui.warn(_("adding %s to series file\n") % patchname)
1916 self.ui.warn(_("adding %s to series file\n") % patchname)
1917 self.added.append(patchname)
1917 self.added.append(patchname)
1918 imported.append(patchname)
1918 imported.append(patchname)
1919 patchname = None
1919 patchname = None
1920
1920
1921 self.removeundo(repo)
1921 self.removeundo(repo)
1922 return imported
1922 return imported
1923
1923
1924 @command("qdelete|qremove|qrm",
1924 @command("qdelete|qremove|qrm",
1925 [('k', 'keep', None, _('keep patch file')),
1925 [('k', 'keep', None, _('keep patch file')),
1926 ('r', 'rev', [],
1926 ('r', 'rev', [],
1927 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1927 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1928 _('hg qdelete [-k] [PATCH]...'))
1928 _('hg qdelete [-k] [PATCH]...'))
1929 def delete(ui, repo, *patches, **opts):
1929 def delete(ui, repo, *patches, **opts):
1930 """remove patches from queue
1930 """remove patches from queue
1931
1931
1932 The patches must not be applied, and at least one patch is required. Exact
1932 The patches must not be applied, and at least one patch is required. Exact
1933 patch identifiers must be given. With -k/--keep, the patch files are
1933 patch identifiers must be given. With -k/--keep, the patch files are
1934 preserved in the patch directory.
1934 preserved in the patch directory.
1935
1935
1936 To stop managing a patch and move it into permanent history,
1936 To stop managing a patch and move it into permanent history,
1937 use the :hg:`qfinish` command."""
1937 use the :hg:`qfinish` command."""
1938 q = repo.mq
1938 q = repo.mq
1939 q.delete(repo, patches, opts)
1939 q.delete(repo, patches, opts)
1940 q.savedirty()
1940 q.savedirty()
1941 return 0
1941 return 0
1942
1942
1943 @command("qapplied",
1943 @command("qapplied",
1944 [('1', 'last', None, _('show only the preceding applied patch'))
1944 [('1', 'last', None, _('show only the preceding applied patch'))
1945 ] + seriesopts,
1945 ] + seriesopts,
1946 _('hg qapplied [-1] [-s] [PATCH]'))
1946 _('hg qapplied [-1] [-s] [PATCH]'))
1947 def applied(ui, repo, patch=None, **opts):
1947 def applied(ui, repo, patch=None, **opts):
1948 """print the patches already applied
1948 """print the patches already applied
1949
1949
1950 Returns 0 on success."""
1950 Returns 0 on success."""
1951
1951
1952 q = repo.mq
1952 q = repo.mq
1953
1953
1954 if patch:
1954 if patch:
1955 if patch not in q.series:
1955 if patch not in q.series:
1956 raise util.Abort(_("patch %s is not in series file") % patch)
1956 raise util.Abort(_("patch %s is not in series file") % patch)
1957 end = q.series.index(patch) + 1
1957 end = q.series.index(patch) + 1
1958 else:
1958 else:
1959 end = q.seriesend(True)
1959 end = q.seriesend(True)
1960
1960
1961 if opts.get('last') and not end:
1961 if opts.get('last') and not end:
1962 ui.write(_("no patches applied\n"))
1962 ui.write(_("no patches applied\n"))
1963 return 1
1963 return 1
1964 elif opts.get('last') and end == 1:
1964 elif opts.get('last') and end == 1:
1965 ui.write(_("only one patch applied\n"))
1965 ui.write(_("only one patch applied\n"))
1966 return 1
1966 return 1
1967 elif opts.get('last'):
1967 elif opts.get('last'):
1968 start = end - 2
1968 start = end - 2
1969 end = 1
1969 end = 1
1970 else:
1970 else:
1971 start = 0
1971 start = 0
1972
1972
1973 q.qseries(repo, length=end, start=start, status='A',
1973 q.qseries(repo, length=end, start=start, status='A',
1974 summary=opts.get('summary'))
1974 summary=opts.get('summary'))
1975
1975
1976
1976
1977 @command("qunapplied",
1977 @command("qunapplied",
1978 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1978 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1979 _('hg qunapplied [-1] [-s] [PATCH]'))
1979 _('hg qunapplied [-1] [-s] [PATCH]'))
1980 def unapplied(ui, repo, patch=None, **opts):
1980 def unapplied(ui, repo, patch=None, **opts):
1981 """print the patches not yet applied
1981 """print the patches not yet applied
1982
1982
1983 Returns 0 on success."""
1983 Returns 0 on success."""
1984
1984
1985 q = repo.mq
1985 q = repo.mq
1986 if patch:
1986 if patch:
1987 if patch not in q.series:
1987 if patch not in q.series:
1988 raise util.Abort(_("patch %s is not in series file") % patch)
1988 raise util.Abort(_("patch %s is not in series file") % patch)
1989 start = q.series.index(patch) + 1
1989 start = q.series.index(patch) + 1
1990 else:
1990 else:
1991 start = q.seriesend(True)
1991 start = q.seriesend(True)
1992
1992
1993 if start == len(q.series) and opts.get('first'):
1993 if start == len(q.series) and opts.get('first'):
1994 ui.write(_("all patches applied\n"))
1994 ui.write(_("all patches applied\n"))
1995 return 1
1995 return 1
1996
1996
1997 length = opts.get('first') and 1 or None
1997 length = opts.get('first') and 1 or None
1998 q.qseries(repo, start=start, length=length, status='U',
1998 q.qseries(repo, start=start, length=length, status='U',
1999 summary=opts.get('summary'))
1999 summary=opts.get('summary'))
2000
2000
2001 @command("qimport",
2001 @command("qimport",
2002 [('e', 'existing', None, _('import file in patch directory')),
2002 [('e', 'existing', None, _('import file in patch directory')),
2003 ('n', 'name', '',
2003 ('n', 'name', '',
2004 _('name of patch file'), _('NAME')),
2004 _('name of patch file'), _('NAME')),
2005 ('f', 'force', None, _('overwrite existing files')),
2005 ('f', 'force', None, _('overwrite existing files')),
2006 ('r', 'rev', [],
2006 ('r', 'rev', [],
2007 _('place existing revisions under mq control'), _('REV')),
2007 _('place existing revisions under mq control'), _('REV')),
2008 ('g', 'git', None, _('use git extended diff format')),
2008 ('g', 'git', None, _('use git extended diff format')),
2009 ('P', 'push', None, _('qpush after importing'))],
2009 ('P', 'push', None, _('qpush after importing'))],
2010 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
2010 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
2011 def qimport(ui, repo, *filename, **opts):
2011 def qimport(ui, repo, *filename, **opts):
2012 """import a patch or existing changeset
2012 """import a patch or existing changeset
2013
2013
2014 The patch is inserted into the series after the last applied
2014 The patch is inserted into the series after the last applied
2015 patch. If no patches have been applied, qimport prepends the patch
2015 patch. If no patches have been applied, qimport prepends the patch
2016 to the series.
2016 to the series.
2017
2017
2018 The patch will have the same name as its source file unless you
2018 The patch will have the same name as its source file unless you
2019 give it a new one with -n/--name.
2019 give it a new one with -n/--name.
2020
2020
2021 You can register an existing patch inside the patch directory with
2021 You can register an existing patch inside the patch directory with
2022 the -e/--existing flag.
2022 the -e/--existing flag.
2023
2023
2024 With -f/--force, an existing patch of the same name will be
2024 With -f/--force, an existing patch of the same name will be
2025 overwritten.
2025 overwritten.
2026
2026
2027 An existing changeset may be placed under mq control with -r/--rev
2027 An existing changeset may be placed under mq control with -r/--rev
2028 (e.g. qimport --rev tip -n patch will place tip under mq control).
2028 (e.g. qimport --rev tip -n patch will place tip under mq control).
2029 With -g/--git, patches imported with --rev will use the git diff
2029 With -g/--git, patches imported with --rev will use the git diff
2030 format. See the diffs help topic for information on why this is
2030 format. See the diffs help topic for information on why this is
2031 important for preserving rename/copy information and permission
2031 important for preserving rename/copy information and permission
2032 changes. Use :hg:`qfinish` to remove changesets from mq control.
2032 changes. Use :hg:`qfinish` to remove changesets from mq control.
2033
2033
2034 To import a patch from standard input, pass - as the patch file.
2034 To import a patch from standard input, pass - as the patch file.
2035 When importing from standard input, a patch name must be specified
2035 When importing from standard input, a patch name must be specified
2036 using the --name flag.
2036 using the --name flag.
2037
2037
2038 To import an existing patch while renaming it::
2038 To import an existing patch while renaming it::
2039
2039
2040 hg qimport -e existing-patch -n new-name
2040 hg qimport -e existing-patch -n new-name
2041
2041
2042 Returns 0 if import succeeded.
2042 Returns 0 if import succeeded.
2043 """
2043 """
2044 lock = repo.lock() # cause this may move phase
2044 lock = repo.lock() # cause this may move phase
2045 try:
2045 try:
2046 q = repo.mq
2046 q = repo.mq
2047 try:
2047 try:
2048 imported = q.qimport(
2048 imported = q.qimport(
2049 repo, filename, patchname=opts.get('name'),
2049 repo, filename, patchname=opts.get('name'),
2050 existing=opts.get('existing'), force=opts.get('force'),
2050 existing=opts.get('existing'), force=opts.get('force'),
2051 rev=opts.get('rev'), git=opts.get('git'))
2051 rev=opts.get('rev'), git=opts.get('git'))
2052 finally:
2052 finally:
2053 q.savedirty()
2053 q.savedirty()
2054
2054
2055
2055
2056 if imported and opts.get('push') and not opts.get('rev'):
2056 if imported and opts.get('push') and not opts.get('rev'):
2057 return q.push(repo, imported[-1])
2057 return q.push(repo, imported[-1])
2058 finally:
2058 finally:
2059 lock.release()
2059 lock.release()
2060 return 0
2060 return 0
2061
2061
2062 def qinit(ui, repo, create):
2062 def qinit(ui, repo, create):
2063 """initialize a new queue repository
2063 """initialize a new queue repository
2064
2064
2065 This command also creates a series file for ordering patches, and
2065 This command also creates a series file for ordering patches, and
2066 an mq-specific .hgignore file in the queue repository, to exclude
2066 an mq-specific .hgignore file in the queue repository, to exclude
2067 the status and guards files (these contain mostly transient state).
2067 the status and guards files (these contain mostly transient state).
2068
2068
2069 Returns 0 if initialization succeeded."""
2069 Returns 0 if initialization succeeded."""
2070 q = repo.mq
2070 q = repo.mq
2071 r = q.init(repo, create)
2071 r = q.init(repo, create)
2072 q.savedirty()
2072 q.savedirty()
2073 if r:
2073 if r:
2074 if not os.path.exists(r.wjoin('.hgignore')):
2074 if not os.path.exists(r.wjoin('.hgignore')):
2075 fp = r.wopener('.hgignore', 'w')
2075 fp = r.wopener('.hgignore', 'w')
2076 fp.write('^\\.hg\n')
2076 fp.write('^\\.hg\n')
2077 fp.write('^\\.mq\n')
2077 fp.write('^\\.mq\n')
2078 fp.write('syntax: glob\n')
2078 fp.write('syntax: glob\n')
2079 fp.write('status\n')
2079 fp.write('status\n')
2080 fp.write('guards\n')
2080 fp.write('guards\n')
2081 fp.close()
2081 fp.close()
2082 if not os.path.exists(r.wjoin('series')):
2082 if not os.path.exists(r.wjoin('series')):
2083 r.wopener('series', 'w').close()
2083 r.wopener('series', 'w').close()
2084 r[None].add(['.hgignore', 'series'])
2084 r[None].add(['.hgignore', 'series'])
2085 commands.add(ui, r)
2085 commands.add(ui, r)
2086 return 0
2086 return 0
2087
2087
2088 @command("^qinit",
2088 @command("^qinit",
2089 [('c', 'create-repo', None, _('create queue repository'))],
2089 [('c', 'create-repo', None, _('create queue repository'))],
2090 _('hg qinit [-c]'))
2090 _('hg qinit [-c]'))
2091 def init(ui, repo, **opts):
2091 def init(ui, repo, **opts):
2092 """init a new queue repository (DEPRECATED)
2092 """init a new queue repository (DEPRECATED)
2093
2093
2094 The queue repository is unversioned by default. If
2094 The queue repository is unversioned by default. If
2095 -c/--create-repo is specified, qinit will create a separate nested
2095 -c/--create-repo is specified, qinit will create a separate nested
2096 repository for patches (qinit -c may also be run later to convert
2096 repository for patches (qinit -c may also be run later to convert
2097 an unversioned patch repository into a versioned one). You can use
2097 an unversioned patch repository into a versioned one). You can use
2098 qcommit to commit changes to this queue repository.
2098 qcommit to commit changes to this queue repository.
2099
2099
2100 This command is deprecated. Without -c, it's implied by other relevant
2100 This command is deprecated. Without -c, it's implied by other relevant
2101 commands. With -c, use :hg:`init --mq` instead."""
2101 commands. With -c, use :hg:`init --mq` instead."""
2102 return qinit(ui, repo, create=opts.get('create_repo'))
2102 return qinit(ui, repo, create=opts.get('create_repo'))
2103
2103
2104 @command("qclone",
2104 @command("qclone",
2105 [('', 'pull', None, _('use pull protocol to copy metadata')),
2105 [('', 'pull', None, _('use pull protocol to copy metadata')),
2106 ('U', 'noupdate', None, _('do not update the new working directories')),
2106 ('U', 'noupdate', None, _('do not update the new working directories')),
2107 ('', 'uncompressed', None,
2107 ('', 'uncompressed', None,
2108 _('use uncompressed transfer (fast over LAN)')),
2108 _('use uncompressed transfer (fast over LAN)')),
2109 ('p', 'patches', '',
2109 ('p', 'patches', '',
2110 _('location of source patch repository'), _('REPO')),
2110 _('location of source patch repository'), _('REPO')),
2111 ] + commands.remoteopts,
2111 ] + commands.remoteopts,
2112 _('hg qclone [OPTION]... SOURCE [DEST]'))
2112 _('hg qclone [OPTION]... SOURCE [DEST]'))
2113 def clone(ui, source, dest=None, **opts):
2113 def clone(ui, source, dest=None, **opts):
2114 '''clone main and patch repository at same time
2114 '''clone main and patch repository at same time
2115
2115
2116 If source is local, destination will have no patches applied. If
2116 If source is local, destination will have no patches applied. If
2117 source is remote, this command can not check if patches are
2117 source is remote, this command can not check if patches are
2118 applied in source, so cannot guarantee that patches are not
2118 applied in source, so cannot guarantee that patches are not
2119 applied in destination. If you clone remote repository, be sure
2119 applied in destination. If you clone remote repository, be sure
2120 before that it has no patches applied.
2120 before that it has no patches applied.
2121
2121
2122 Source patch repository is looked for in <src>/.hg/patches by
2122 Source patch repository is looked for in <src>/.hg/patches by
2123 default. Use -p <url> to change.
2123 default. Use -p <url> to change.
2124
2124
2125 The patch directory must be a nested Mercurial repository, as
2125 The patch directory must be a nested Mercurial repository, as
2126 would be created by :hg:`init --mq`.
2126 would be created by :hg:`init --mq`.
2127
2127
2128 Return 0 on success.
2128 Return 0 on success.
2129 '''
2129 '''
2130 def patchdir(repo):
2130 def patchdir(repo):
2131 """compute a patch repo url from a repo object"""
2131 """compute a patch repo url from a repo object"""
2132 url = repo.url()
2132 url = repo.url()
2133 if url.endswith('/'):
2133 if url.endswith('/'):
2134 url = url[:-1]
2134 url = url[:-1]
2135 return url + '/.hg/patches'
2135 return url + '/.hg/patches'
2136
2136
2137 # main repo (destination and sources)
2137 # main repo (destination and sources)
2138 if dest is None:
2138 if dest is None:
2139 dest = hg.defaultdest(source)
2139 dest = hg.defaultdest(source)
2140 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2140 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2141
2141
2142 # patches repo (source only)
2142 # patches repo (source only)
2143 if opts.get('patches'):
2143 if opts.get('patches'):
2144 patchespath = ui.expandpath(opts.get('patches'))
2144 patchespath = ui.expandpath(opts.get('patches'))
2145 else:
2145 else:
2146 patchespath = patchdir(sr)
2146 patchespath = patchdir(sr)
2147 try:
2147 try:
2148 hg.repository(ui, patchespath)
2148 hg.repository(ui, patchespath)
2149 except error.RepoError:
2149 except error.RepoError:
2150 raise util.Abort(_('versioned patch repository not found'
2150 raise util.Abort(_('versioned patch repository not found'
2151 ' (see init --mq)'))
2151 ' (see init --mq)'))
2152 qbase, destrev = None, None
2152 qbase, destrev = None, None
2153 if sr.local():
2153 if sr.local():
2154 if sr.mq.applied and sr[qbase].phase() != phases.secret:
2154 if sr.mq.applied and sr[qbase].phase() != phases.secret:
2155 qbase = sr.mq.applied[0].node
2155 qbase = sr.mq.applied[0].node
2156 if not hg.islocal(dest):
2156 if not hg.islocal(dest):
2157 heads = set(sr.heads())
2157 heads = set(sr.heads())
2158 destrev = list(heads.difference(sr.heads(qbase)))
2158 destrev = list(heads.difference(sr.heads(qbase)))
2159 destrev.append(sr.changelog.parents(qbase)[0])
2159 destrev.append(sr.changelog.parents(qbase)[0])
2160 elif sr.capable('lookup'):
2160 elif sr.capable('lookup'):
2161 try:
2161 try:
2162 qbase = sr.lookup('qbase')
2162 qbase = sr.lookup('qbase')
2163 except error.RepoError:
2163 except error.RepoError:
2164 pass
2164 pass
2165
2165
2166 ui.note(_('cloning main repository\n'))
2166 ui.note(_('cloning main repository\n'))
2167 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2167 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2168 pull=opts.get('pull'),
2168 pull=opts.get('pull'),
2169 rev=destrev,
2169 rev=destrev,
2170 update=False,
2170 update=False,
2171 stream=opts.get('uncompressed'))
2171 stream=opts.get('uncompressed'))
2172
2172
2173 ui.note(_('cloning patch repository\n'))
2173 ui.note(_('cloning patch repository\n'))
2174 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2174 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2175 pull=opts.get('pull'), update=not opts.get('noupdate'),
2175 pull=opts.get('pull'), update=not opts.get('noupdate'),
2176 stream=opts.get('uncompressed'))
2176 stream=opts.get('uncompressed'))
2177
2177
2178 if dr.local():
2178 if dr.local():
2179 if qbase:
2179 if qbase:
2180 ui.note(_('stripping applied patches from destination '
2180 ui.note(_('stripping applied patches from destination '
2181 'repository\n'))
2181 'repository\n'))
2182 dr.mq.strip(dr, [qbase], update=False, backup=None)
2182 dr.mq.strip(dr, [qbase], update=False, backup=None)
2183 if not opts.get('noupdate'):
2183 if not opts.get('noupdate'):
2184 ui.note(_('updating destination repository\n'))
2184 ui.note(_('updating destination repository\n'))
2185 hg.update(dr, dr.changelog.tip())
2185 hg.update(dr, dr.changelog.tip())
2186
2186
2187 @command("qcommit|qci",
2187 @command("qcommit|qci",
2188 commands.table["^commit|ci"][1],
2188 commands.table["^commit|ci"][1],
2189 _('hg qcommit [OPTION]... [FILE]...'))
2189 _('hg qcommit [OPTION]... [FILE]...'))
2190 def commit(ui, repo, *pats, **opts):
2190 def commit(ui, repo, *pats, **opts):
2191 """commit changes in the queue repository (DEPRECATED)
2191 """commit changes in the queue repository (DEPRECATED)
2192
2192
2193 This command is deprecated; use :hg:`commit --mq` instead."""
2193 This command is deprecated; use :hg:`commit --mq` instead."""
2194 q = repo.mq
2194 q = repo.mq
2195 r = q.qrepo()
2195 r = q.qrepo()
2196 if not r:
2196 if not r:
2197 raise util.Abort('no queue repository')
2197 raise util.Abort('no queue repository')
2198 commands.commit(r.ui, r, *pats, **opts)
2198 commands.commit(r.ui, r, *pats, **opts)
2199
2199
2200 @command("qseries",
2200 @command("qseries",
2201 [('m', 'missing', None, _('print patches not in series')),
2201 [('m', 'missing', None, _('print patches not in series')),
2202 ] + seriesopts,
2202 ] + seriesopts,
2203 _('hg qseries [-ms]'))
2203 _('hg qseries [-ms]'))
2204 def series(ui, repo, **opts):
2204 def series(ui, repo, **opts):
2205 """print the entire series file
2205 """print the entire series file
2206
2206
2207 Returns 0 on success."""
2207 Returns 0 on success."""
2208 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2208 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2209 return 0
2209 return 0
2210
2210
2211 @command("qtop", seriesopts, _('hg qtop [-s]'))
2211 @command("qtop", seriesopts, _('hg qtop [-s]'))
2212 def top(ui, repo, **opts):
2212 def top(ui, repo, **opts):
2213 """print the name of the current patch
2213 """print the name of the current patch
2214
2214
2215 Returns 0 on success."""
2215 Returns 0 on success."""
2216 q = repo.mq
2216 q = repo.mq
2217 t = q.applied and q.seriesend(True) or 0
2217 t = q.applied and q.seriesend(True) or 0
2218 if t:
2218 if t:
2219 q.qseries(repo, start=t - 1, length=1, status='A',
2219 q.qseries(repo, start=t - 1, length=1, status='A',
2220 summary=opts.get('summary'))
2220 summary=opts.get('summary'))
2221 else:
2221 else:
2222 ui.write(_("no patches applied\n"))
2222 ui.write(_("no patches applied\n"))
2223 return 1
2223 return 1
2224
2224
2225 @command("qnext", seriesopts, _('hg qnext [-s]'))
2225 @command("qnext", seriesopts, _('hg qnext [-s]'))
2226 def next(ui, repo, **opts):
2226 def next(ui, repo, **opts):
2227 """print the name of the next pushable patch
2227 """print the name of the next pushable patch
2228
2228
2229 Returns 0 on success."""
2229 Returns 0 on success."""
2230 q = repo.mq
2230 q = repo.mq
2231 end = q.seriesend()
2231 end = q.seriesend()
2232 if end == len(q.series):
2232 if end == len(q.series):
2233 ui.write(_("all patches applied\n"))
2233 ui.write(_("all patches applied\n"))
2234 return 1
2234 return 1
2235 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2235 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2236
2236
2237 @command("qprev", seriesopts, _('hg qprev [-s]'))
2237 @command("qprev", seriesopts, _('hg qprev [-s]'))
2238 def prev(ui, repo, **opts):
2238 def prev(ui, repo, **opts):
2239 """print the name of the preceding applied patch
2239 """print the name of the preceding applied patch
2240
2240
2241 Returns 0 on success."""
2241 Returns 0 on success."""
2242 q = repo.mq
2242 q = repo.mq
2243 l = len(q.applied)
2243 l = len(q.applied)
2244 if l == 1:
2244 if l == 1:
2245 ui.write(_("only one patch applied\n"))
2245 ui.write(_("only one patch applied\n"))
2246 return 1
2246 return 1
2247 if not l:
2247 if not l:
2248 ui.write(_("no patches applied\n"))
2248 ui.write(_("no patches applied\n"))
2249 return 1
2249 return 1
2250 idx = q.series.index(q.applied[-2].name)
2250 idx = q.series.index(q.applied[-2].name)
2251 q.qseries(repo, start=idx, length=1, status='A',
2251 q.qseries(repo, start=idx, length=1, status='A',
2252 summary=opts.get('summary'))
2252 summary=opts.get('summary'))
2253
2253
2254 def setupheaderopts(ui, opts):
2254 def setupheaderopts(ui, opts):
2255 if not opts.get('user') and opts.get('currentuser'):
2255 if not opts.get('user') and opts.get('currentuser'):
2256 opts['user'] = ui.username()
2256 opts['user'] = ui.username()
2257 if not opts.get('date') and opts.get('currentdate'):
2257 if not opts.get('date') and opts.get('currentdate'):
2258 opts['date'] = "%d %d" % util.makedate()
2258 opts['date'] = "%d %d" % util.makedate()
2259
2259
2260 @command("^qnew",
2260 @command("^qnew",
2261 [('e', 'edit', None, _('edit commit message')),
2261 [('e', 'edit', None, _('edit commit message')),
2262 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2262 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2263 ('g', 'git', None, _('use git extended diff format')),
2263 ('g', 'git', None, _('use git extended diff format')),
2264 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2264 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2265 ('u', 'user', '',
2265 ('u', 'user', '',
2266 _('add "From: <USER>" to patch'), _('USER')),
2266 _('add "From: <USER>" to patch'), _('USER')),
2267 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2267 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2268 ('d', 'date', '',
2268 ('d', 'date', '',
2269 _('add "Date: <DATE>" to patch'), _('DATE'))
2269 _('add "Date: <DATE>" to patch'), _('DATE'))
2270 ] + commands.walkopts + commands.commitopts,
2270 ] + commands.walkopts + commands.commitopts,
2271 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2271 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2272 def new(ui, repo, patch, *args, **opts):
2272 def new(ui, repo, patch, *args, **opts):
2273 """create a new patch
2273 """create a new patch
2274
2274
2275 qnew creates a new patch on top of the currently-applied patch (if
2275 qnew creates a new patch on top of the currently-applied patch (if
2276 any). The patch will be initialized with any outstanding changes
2276 any). The patch will be initialized with any outstanding changes
2277 in the working directory. You may also use -I/--include,
2277 in the working directory. You may also use -I/--include,
2278 -X/--exclude, and/or a list of files after the patch name to add
2278 -X/--exclude, and/or a list of files after the patch name to add
2279 only changes to matching files to the new patch, leaving the rest
2279 only changes to matching files to the new patch, leaving the rest
2280 as uncommitted modifications.
2280 as uncommitted modifications.
2281
2281
2282 -u/--user and -d/--date can be used to set the (given) user and
2282 -u/--user and -d/--date can be used to set the (given) user and
2283 date, respectively. -U/--currentuser and -D/--currentdate set user
2283 date, respectively. -U/--currentuser and -D/--currentdate set user
2284 to current user and date to current date.
2284 to current user and date to current date.
2285
2285
2286 -e/--edit, -m/--message or -l/--logfile set the patch header as
2286 -e/--edit, -m/--message or -l/--logfile set the patch header as
2287 well as the commit message. If none is specified, the header is
2287 well as the commit message. If none is specified, the header is
2288 empty and the commit message is '[mq]: PATCH'.
2288 empty and the commit message is '[mq]: PATCH'.
2289
2289
2290 Use the -g/--git option to keep the patch in the git extended diff
2290 Use the -g/--git option to keep the patch in the git extended diff
2291 format. Read the diffs help topic for more information on why this
2291 format. Read the diffs help topic for more information on why this
2292 is important for preserving permission changes and copy/rename
2292 is important for preserving permission changes and copy/rename
2293 information.
2293 information.
2294
2294
2295 Returns 0 on successful creation of a new patch.
2295 Returns 0 on successful creation of a new patch.
2296 """
2296 """
2297 msg = cmdutil.logmessage(ui, opts)
2297 msg = cmdutil.logmessage(ui, opts)
2298 def getmsg():
2298 def getmsg():
2299 return ui.edit(msg, opts.get('user') or ui.username())
2299 return ui.edit(msg, opts.get('user') or ui.username())
2300 q = repo.mq
2300 q = repo.mq
2301 opts['msg'] = msg
2301 opts['msg'] = msg
2302 if opts.get('edit'):
2302 if opts.get('edit'):
2303 opts['msg'] = getmsg
2303 opts['msg'] = getmsg
2304 else:
2304 else:
2305 opts['msg'] = msg
2305 opts['msg'] = msg
2306 setupheaderopts(ui, opts)
2306 setupheaderopts(ui, opts)
2307 q.new(repo, patch, *args, **opts)
2307 q.new(repo, patch, *args, **opts)
2308 q.savedirty()
2308 q.savedirty()
2309 return 0
2309 return 0
2310
2310
2311 @command("^qrefresh",
2311 @command("^qrefresh",
2312 [('e', 'edit', None, _('edit commit message')),
2312 [('e', 'edit', None, _('edit commit message')),
2313 ('g', 'git', None, _('use git extended diff format')),
2313 ('g', 'git', None, _('use git extended diff format')),
2314 ('s', 'short', None,
2314 ('s', 'short', None,
2315 _('refresh only files already in the patch and specified files')),
2315 _('refresh only files already in the patch and specified files')),
2316 ('U', 'currentuser', None,
2316 ('U', 'currentuser', None,
2317 _('add/update author field in patch with current user')),
2317 _('add/update author field in patch with current user')),
2318 ('u', 'user', '',
2318 ('u', 'user', '',
2319 _('add/update author field in patch with given user'), _('USER')),
2319 _('add/update author field in patch with given user'), _('USER')),
2320 ('D', 'currentdate', None,
2320 ('D', 'currentdate', None,
2321 _('add/update date field in patch with current date')),
2321 _('add/update date field in patch with current date')),
2322 ('d', 'date', '',
2322 ('d', 'date', '',
2323 _('add/update date field in patch with given date'), _('DATE'))
2323 _('add/update date field in patch with given date'), _('DATE'))
2324 ] + commands.walkopts + commands.commitopts,
2324 ] + commands.walkopts + commands.commitopts,
2325 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2325 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2326 def refresh(ui, repo, *pats, **opts):
2326 def refresh(ui, repo, *pats, **opts):
2327 """update the current patch
2327 """update the current patch
2328
2328
2329 If any file patterns are provided, the refreshed patch will
2329 If any file patterns are provided, the refreshed patch will
2330 contain only the modifications that match those patterns; the
2330 contain only the modifications that match those patterns; the
2331 remaining modifications will remain in the working directory.
2331 remaining modifications will remain in the working directory.
2332
2332
2333 If -s/--short is specified, files currently included in the patch
2333 If -s/--short is specified, files currently included in the patch
2334 will be refreshed just like matched files and remain in the patch.
2334 will be refreshed just like matched files and remain in the patch.
2335
2335
2336 If -e/--edit is specified, Mercurial will start your configured editor for
2336 If -e/--edit is specified, Mercurial will start your configured editor for
2337 you to enter a message. In case qrefresh fails, you will find a backup of
2337 you to enter a message. In case qrefresh fails, you will find a backup of
2338 your message in ``.hg/last-message.txt``.
2338 your message in ``.hg/last-message.txt``.
2339
2339
2340 hg add/remove/copy/rename work as usual, though you might want to
2340 hg add/remove/copy/rename work as usual, though you might want to
2341 use git-style patches (-g/--git or [diff] git=1) to track copies
2341 use git-style patches (-g/--git or [diff] git=1) to track copies
2342 and renames. See the diffs help topic for more information on the
2342 and renames. See the diffs help topic for more information on the
2343 git diff format.
2343 git diff format.
2344
2344
2345 Returns 0 on success.
2345 Returns 0 on success.
2346 """
2346 """
2347 q = repo.mq
2347 q = repo.mq
2348 message = cmdutil.logmessage(ui, opts)
2348 message = cmdutil.logmessage(ui, opts)
2349 if opts.get('edit'):
2349 if opts.get('edit'):
2350 if not q.applied:
2350 if not q.applied:
2351 ui.write(_("no patches applied\n"))
2351 ui.write(_("no patches applied\n"))
2352 return 1
2352 return 1
2353 if message:
2353 if message:
2354 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2354 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2355 patch = q.applied[-1].name
2355 patch = q.applied[-1].name
2356 ph = patchheader(q.join(patch), q.plainmode)
2356 ph = patchheader(q.join(patch), q.plainmode)
2357 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2357 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2358 # We don't want to lose the patch message if qrefresh fails (issue2062)
2358 # We don't want to lose the patch message if qrefresh fails (issue2062)
2359 repo.savecommitmessage(message)
2359 repo.savecommitmessage(message)
2360 setupheaderopts(ui, opts)
2360 setupheaderopts(ui, opts)
2361 wlock = repo.wlock()
2361 wlock = repo.wlock()
2362 try:
2362 try:
2363 ret = q.refresh(repo, pats, msg=message, **opts)
2363 ret = q.refresh(repo, pats, msg=message, **opts)
2364 q.savedirty()
2364 q.savedirty()
2365 return ret
2365 return ret
2366 finally:
2366 finally:
2367 wlock.release()
2367 wlock.release()
2368
2368
2369 @command("^qdiff",
2369 @command("^qdiff",
2370 commands.diffopts + commands.diffopts2 + commands.walkopts,
2370 commands.diffopts + commands.diffopts2 + commands.walkopts,
2371 _('hg qdiff [OPTION]... [FILE]...'))
2371 _('hg qdiff [OPTION]... [FILE]...'))
2372 def diff(ui, repo, *pats, **opts):
2372 def diff(ui, repo, *pats, **opts):
2373 """diff of the current patch and subsequent modifications
2373 """diff of the current patch and subsequent modifications
2374
2374
2375 Shows a diff which includes the current patch as well as any
2375 Shows a diff which includes the current patch as well as any
2376 changes which have been made in the working directory since the
2376 changes which have been made in the working directory since the
2377 last refresh (thus showing what the current patch would become
2377 last refresh (thus showing what the current patch would become
2378 after a qrefresh).
2378 after a qrefresh).
2379
2379
2380 Use :hg:`diff` if you only want to see the changes made since the
2380 Use :hg:`diff` if you only want to see the changes made since the
2381 last qrefresh, or :hg:`export qtip` if you want to see changes
2381 last qrefresh, or :hg:`export qtip` if you want to see changes
2382 made by the current patch without including changes made since the
2382 made by the current patch without including changes made since the
2383 qrefresh.
2383 qrefresh.
2384
2384
2385 Returns 0 on success.
2385 Returns 0 on success.
2386 """
2386 """
2387 repo.mq.diff(repo, pats, opts)
2387 repo.mq.diff(repo, pats, opts)
2388 return 0
2388 return 0
2389
2389
2390 @command('qfold',
2390 @command('qfold',
2391 [('e', 'edit', None, _('edit patch header')),
2391 [('e', 'edit', None, _('edit patch header')),
2392 ('k', 'keep', None, _('keep folded patch files')),
2392 ('k', 'keep', None, _('keep folded patch files')),
2393 ] + commands.commitopts,
2393 ] + commands.commitopts,
2394 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2394 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2395 def fold(ui, repo, *files, **opts):
2395 def fold(ui, repo, *files, **opts):
2396 """fold the named patches into the current patch
2396 """fold the named patches into the current patch
2397
2397
2398 Patches must not yet be applied. Each patch will be successively
2398 Patches must not yet be applied. Each patch will be successively
2399 applied to the current patch in the order given. If all the
2399 applied to the current patch in the order given. If all the
2400 patches apply successfully, the current patch will be refreshed
2400 patches apply successfully, the current patch will be refreshed
2401 with the new cumulative patch, and the folded patches will be
2401 with the new cumulative patch, and the folded patches will be
2402 deleted. With -k/--keep, the folded patch files will not be
2402 deleted. With -k/--keep, the folded patch files will not be
2403 removed afterwards.
2403 removed afterwards.
2404
2404
2405 The header for each folded patch will be concatenated with the
2405 The header for each folded patch will be concatenated with the
2406 current patch header, separated by a line of ``* * *``.
2406 current patch header, separated by a line of ``* * *``.
2407
2407
2408 Returns 0 on success."""
2408 Returns 0 on success."""
2409 q = repo.mq
2409 q = repo.mq
2410 if not files:
2410 if not files:
2411 raise util.Abort(_('qfold requires at least one patch name'))
2411 raise util.Abort(_('qfold requires at least one patch name'))
2412 if not q.checktoppatch(repo)[0]:
2412 if not q.checktoppatch(repo)[0]:
2413 raise util.Abort(_('no patches applied'))
2413 raise util.Abort(_('no patches applied'))
2414 q.checklocalchanges(repo)
2414 q.checklocalchanges(repo)
2415
2415
2416 message = cmdutil.logmessage(ui, opts)
2416 message = cmdutil.logmessage(ui, opts)
2417 if opts.get('edit'):
2417 if opts.get('edit'):
2418 if message:
2418 if message:
2419 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2419 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2420
2420
2421 parent = q.lookup('qtip')
2421 parent = q.lookup('qtip')
2422 patches = []
2422 patches = []
2423 messages = []
2423 messages = []
2424 for f in files:
2424 for f in files:
2425 p = q.lookup(f)
2425 p = q.lookup(f)
2426 if p in patches or p == parent:
2426 if p in patches or p == parent:
2427 ui.warn(_('Skipping already folded patch %s\n') % p)
2427 ui.warn(_('Skipping already folded patch %s\n') % p)
2428 if q.isapplied(p):
2428 if q.isapplied(p):
2429 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2429 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2430 patches.append(p)
2430 patches.append(p)
2431
2431
2432 for p in patches:
2432 for p in patches:
2433 if not message:
2433 if not message:
2434 ph = patchheader(q.join(p), q.plainmode)
2434 ph = patchheader(q.join(p), q.plainmode)
2435 if ph.message:
2435 if ph.message:
2436 messages.append(ph.message)
2436 messages.append(ph.message)
2437 pf = q.join(p)
2437 pf = q.join(p)
2438 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2438 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2439 if not patchsuccess:
2439 if not patchsuccess:
2440 raise util.Abort(_('error folding patch %s') % p)
2440 raise util.Abort(_('error folding patch %s') % p)
2441
2441
2442 if not message:
2442 if not message:
2443 ph = patchheader(q.join(parent), q.plainmode)
2443 ph = patchheader(q.join(parent), q.plainmode)
2444 message, user = ph.message, ph.user
2444 message, user = ph.message, ph.user
2445 for msg in messages:
2445 for msg in messages:
2446 message.append('* * *')
2446 message.append('* * *')
2447 message.extend(msg)
2447 message.extend(msg)
2448 message = '\n'.join(message)
2448 message = '\n'.join(message)
2449
2449
2450 if opts.get('edit'):
2450 if opts.get('edit'):
2451 message = ui.edit(message, user or ui.username())
2451 message = ui.edit(message, user or ui.username())
2452
2452
2453 diffopts = q.patchopts(q.diffopts(), *patches)
2453 diffopts = q.patchopts(q.diffopts(), *patches)
2454 wlock = repo.wlock()
2454 wlock = repo.wlock()
2455 try:
2455 try:
2456 q.refresh(repo, msg=message, git=diffopts.git)
2456 q.refresh(repo, msg=message, git=diffopts.git)
2457 q.delete(repo, patches, opts)
2457 q.delete(repo, patches, opts)
2458 q.savedirty()
2458 q.savedirty()
2459 finally:
2459 finally:
2460 wlock.release()
2460 wlock.release()
2461
2461
2462 @command("qgoto",
2462 @command("qgoto",
2463 [('f', 'force', None, _('overwrite any local changes'))],
2463 [('f', 'force', None, _('overwrite any local changes'))],
2464 _('hg qgoto [OPTION]... PATCH'))
2464 _('hg qgoto [OPTION]... PATCH'))
2465 def goto(ui, repo, patch, **opts):
2465 def goto(ui, repo, patch, **opts):
2466 '''push or pop patches until named patch is at top of stack
2466 '''push or pop patches until named patch is at top of stack
2467
2467
2468 Returns 0 on success.'''
2468 Returns 0 on success.'''
2469 q = repo.mq
2469 q = repo.mq
2470 patch = q.lookup(patch)
2470 patch = q.lookup(patch)
2471 if q.isapplied(patch):
2471 if q.isapplied(patch):
2472 ret = q.pop(repo, patch, force=opts.get('force'))
2472 ret = q.pop(repo, patch, force=opts.get('force'))
2473 else:
2473 else:
2474 ret = q.push(repo, patch, force=opts.get('force'))
2474 ret = q.push(repo, patch, force=opts.get('force'))
2475 q.savedirty()
2475 q.savedirty()
2476 return ret
2476 return ret
2477
2477
2478 @command("qguard",
2478 @command("qguard",
2479 [('l', 'list', None, _('list all patches and guards')),
2479 [('l', 'list', None, _('list all patches and guards')),
2480 ('n', 'none', None, _('drop all guards'))],
2480 ('n', 'none', None, _('drop all guards'))],
2481 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2481 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2482 def guard(ui, repo, *args, **opts):
2482 def guard(ui, repo, *args, **opts):
2483 '''set or print guards for a patch
2483 '''set or print guards for a patch
2484
2484
2485 Guards control whether a patch can be pushed. A patch with no
2485 Guards control whether a patch can be pushed. A patch with no
2486 guards is always pushed. A patch with a positive guard ("+foo") is
2486 guards is always pushed. A patch with a positive guard ("+foo") is
2487 pushed only if the :hg:`qselect` command has activated it. A patch with
2487 pushed only if the :hg:`qselect` command has activated it. A patch with
2488 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2488 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2489 has activated it.
2489 has activated it.
2490
2490
2491 With no arguments, print the currently active guards.
2491 With no arguments, print the currently active guards.
2492 With arguments, set guards for the named patch.
2492 With arguments, set guards for the named patch.
2493
2493
2494 .. note::
2494 .. note::
2495 Specifying negative guards now requires '--'.
2495 Specifying negative guards now requires '--'.
2496
2496
2497 To set guards on another patch::
2497 To set guards on another patch::
2498
2498
2499 hg qguard other.patch -- +2.6.17 -stable
2499 hg qguard other.patch -- +2.6.17 -stable
2500
2500
2501 Returns 0 on success.
2501 Returns 0 on success.
2502 '''
2502 '''
2503 def status(idx):
2503 def status(idx):
2504 guards = q.seriesguards[idx] or ['unguarded']
2504 guards = q.seriesguards[idx] or ['unguarded']
2505 if q.series[idx] in applied:
2505 if q.series[idx] in applied:
2506 state = 'applied'
2506 state = 'applied'
2507 elif q.pushable(idx)[0]:
2507 elif q.pushable(idx)[0]:
2508 state = 'unapplied'
2508 state = 'unapplied'
2509 else:
2509 else:
2510 state = 'guarded'
2510 state = 'guarded'
2511 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2511 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2512 ui.write('%s: ' % ui.label(q.series[idx], label))
2512 ui.write('%s: ' % ui.label(q.series[idx], label))
2513
2513
2514 for i, guard in enumerate(guards):
2514 for i, guard in enumerate(guards):
2515 if guard.startswith('+'):
2515 if guard.startswith('+'):
2516 ui.write(guard, label='qguard.positive')
2516 ui.write(guard, label='qguard.positive')
2517 elif guard.startswith('-'):
2517 elif guard.startswith('-'):
2518 ui.write(guard, label='qguard.negative')
2518 ui.write(guard, label='qguard.negative')
2519 else:
2519 else:
2520 ui.write(guard, label='qguard.unguarded')
2520 ui.write(guard, label='qguard.unguarded')
2521 if i != len(guards) - 1:
2521 if i != len(guards) - 1:
2522 ui.write(' ')
2522 ui.write(' ')
2523 ui.write('\n')
2523 ui.write('\n')
2524 q = repo.mq
2524 q = repo.mq
2525 applied = set(p.name for p in q.applied)
2525 applied = set(p.name for p in q.applied)
2526 patch = None
2526 patch = None
2527 args = list(args)
2527 args = list(args)
2528 if opts.get('list'):
2528 if opts.get('list'):
2529 if args or opts.get('none'):
2529 if args or opts.get('none'):
2530 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2530 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2531 for i in xrange(len(q.series)):
2531 for i in xrange(len(q.series)):
2532 status(i)
2532 status(i)
2533 return
2533 return
2534 if not args or args[0][0:1] in '-+':
2534 if not args or args[0][0:1] in '-+':
2535 if not q.applied:
2535 if not q.applied:
2536 raise util.Abort(_('no patches applied'))
2536 raise util.Abort(_('no patches applied'))
2537 patch = q.applied[-1].name
2537 patch = q.applied[-1].name
2538 if patch is None and args[0][0:1] not in '-+':
2538 if patch is None and args[0][0:1] not in '-+':
2539 patch = args.pop(0)
2539 patch = args.pop(0)
2540 if patch is None:
2540 if patch is None:
2541 raise util.Abort(_('no patch to work with'))
2541 raise util.Abort(_('no patch to work with'))
2542 if args or opts.get('none'):
2542 if args or opts.get('none'):
2543 idx = q.findseries(patch)
2543 idx = q.findseries(patch)
2544 if idx is None:
2544 if idx is None:
2545 raise util.Abort(_('no patch named %s') % patch)
2545 raise util.Abort(_('no patch named %s') % patch)
2546 q.setguards(idx, args)
2546 q.setguards(idx, args)
2547 q.savedirty()
2547 q.savedirty()
2548 else:
2548 else:
2549 status(q.series.index(q.lookup(patch)))
2549 status(q.series.index(q.lookup(patch)))
2550
2550
2551 @command("qheader", [], _('hg qheader [PATCH]'))
2551 @command("qheader", [], _('hg qheader [PATCH]'))
2552 def header(ui, repo, patch=None):
2552 def header(ui, repo, patch=None):
2553 """print the header of the topmost or specified patch
2553 """print the header of the topmost or specified patch
2554
2554
2555 Returns 0 on success."""
2555 Returns 0 on success."""
2556 q = repo.mq
2556 q = repo.mq
2557
2557
2558 if patch:
2558 if patch:
2559 patch = q.lookup(patch)
2559 patch = q.lookup(patch)
2560 else:
2560 else:
2561 if not q.applied:
2561 if not q.applied:
2562 ui.write(_('no patches applied\n'))
2562 ui.write(_('no patches applied\n'))
2563 return 1
2563 return 1
2564 patch = q.lookup('qtip')
2564 patch = q.lookup('qtip')
2565 ph = patchheader(q.join(patch), q.plainmode)
2565 ph = patchheader(q.join(patch), q.plainmode)
2566
2566
2567 ui.write('\n'.join(ph.message) + '\n')
2567 ui.write('\n'.join(ph.message) + '\n')
2568
2568
2569 def lastsavename(path):
2569 def lastsavename(path):
2570 (directory, base) = os.path.split(path)
2570 (directory, base) = os.path.split(path)
2571 names = os.listdir(directory)
2571 names = os.listdir(directory)
2572 namere = re.compile("%s.([0-9]+)" % base)
2572 namere = re.compile("%s.([0-9]+)" % base)
2573 maxindex = None
2573 maxindex = None
2574 maxname = None
2574 maxname = None
2575 for f in names:
2575 for f in names:
2576 m = namere.match(f)
2576 m = namere.match(f)
2577 if m:
2577 if m:
2578 index = int(m.group(1))
2578 index = int(m.group(1))
2579 if maxindex is None or index > maxindex:
2579 if maxindex is None or index > maxindex:
2580 maxindex = index
2580 maxindex = index
2581 maxname = f
2581 maxname = f
2582 if maxname:
2582 if maxname:
2583 return (os.path.join(directory, maxname), maxindex)
2583 return (os.path.join(directory, maxname), maxindex)
2584 return (None, None)
2584 return (None, None)
2585
2585
2586 def savename(path):
2586 def savename(path):
2587 (last, index) = lastsavename(path)
2587 (last, index) = lastsavename(path)
2588 if last is None:
2588 if last is None:
2589 index = 0
2589 index = 0
2590 newpath = path + ".%d" % (index + 1)
2590 newpath = path + ".%d" % (index + 1)
2591 return newpath
2591 return newpath
2592
2592
2593 @command("^qpush",
2593 @command("^qpush",
2594 [('f', 'force', None, _('apply on top of local changes')),
2594 [('f', 'force', None, _('apply on top of local changes')),
2595 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2595 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2596 ('l', 'list', None, _('list patch name in commit text')),
2596 ('l', 'list', None, _('list patch name in commit text')),
2597 ('a', 'all', None, _('apply all patches')),
2597 ('a', 'all', None, _('apply all patches')),
2598 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2598 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2599 ('n', 'name', '',
2599 ('n', 'name', '',
2600 _('merge queue name (DEPRECATED)'), _('NAME')),
2600 _('merge queue name (DEPRECATED)'), _('NAME')),
2601 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2601 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2602 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2602 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2603 def push(ui, repo, patch=None, **opts):
2603 def push(ui, repo, patch=None, **opts):
2604 """push the next patch onto the stack
2604 """push the next patch onto the stack
2605
2605
2606 When -f/--force is applied, all local changes in patched files
2606 When -f/--force is applied, all local changes in patched files
2607 will be lost.
2607 will be lost.
2608
2608
2609 Return 0 on success.
2609 Return 0 on success.
2610 """
2610 """
2611 q = repo.mq
2611 q = repo.mq
2612 mergeq = None
2612 mergeq = None
2613
2613
2614 if opts.get('merge'):
2614 if opts.get('merge'):
2615 if opts.get('name'):
2615 if opts.get('name'):
2616 newpath = repo.join(opts.get('name'))
2616 newpath = repo.join(opts.get('name'))
2617 else:
2617 else:
2618 newpath, i = lastsavename(q.path)
2618 newpath, i = lastsavename(q.path)
2619 if not newpath:
2619 if not newpath:
2620 ui.warn(_("no saved queues found, please use -n\n"))
2620 ui.warn(_("no saved queues found, please use -n\n"))
2621 return 1
2621 return 1
2622 mergeq = queue(ui, repo.path, newpath)
2622 mergeq = queue(ui, repo.path, newpath)
2623 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2623 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2624 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2624 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2625 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2625 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2626 exact=opts.get('exact'))
2626 exact=opts.get('exact'))
2627 return ret
2627 return ret
2628
2628
2629 @command("^qpop",
2629 @command("^qpop",
2630 [('a', 'all', None, _('pop all patches')),
2630 [('a', 'all', None, _('pop all patches')),
2631 ('n', 'name', '',
2631 ('n', 'name', '',
2632 _('queue name to pop (DEPRECATED)'), _('NAME')),
2632 _('queue name to pop (DEPRECATED)'), _('NAME')),
2633 ('f', 'force', None, _('forget any local changes to patched files'))],
2633 ('f', 'force', None, _('forget any local changes to patched files'))],
2634 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2634 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2635 def pop(ui, repo, patch=None, **opts):
2635 def pop(ui, repo, patch=None, **opts):
2636 """pop the current patch off the stack
2636 """pop the current patch off the stack
2637
2637
2638 By default, pops off the top of the patch stack. If given a patch
2638 By default, pops off the top of the patch stack. If given a patch
2639 name, keeps popping off patches until the named patch is at the
2639 name, keeps popping off patches until the named patch is at the
2640 top of the stack.
2640 top of the stack.
2641
2641
2642 Return 0 on success.
2642 Return 0 on success.
2643 """
2643 """
2644 localupdate = True
2644 localupdate = True
2645 if opts.get('name'):
2645 if opts.get('name'):
2646 q = queue(ui, repo.path, repo.join(opts.get('name')))
2646 q = queue(ui, repo.path, repo.join(opts.get('name')))
2647 ui.warn(_('using patch queue: %s\n') % q.path)
2647 ui.warn(_('using patch queue: %s\n') % q.path)
2648 localupdate = False
2648 localupdate = False
2649 else:
2649 else:
2650 q = repo.mq
2650 q = repo.mq
2651 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2651 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2652 all=opts.get('all'))
2652 all=opts.get('all'))
2653 q.savedirty()
2653 q.savedirty()
2654 return ret
2654 return ret
2655
2655
2656 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2656 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2657 def rename(ui, repo, patch, name=None, **opts):
2657 def rename(ui, repo, patch, name=None, **opts):
2658 """rename a patch
2658 """rename a patch
2659
2659
2660 With one argument, renames the current patch to PATCH1.
2660 With one argument, renames the current patch to PATCH1.
2661 With two arguments, renames PATCH1 to PATCH2.
2661 With two arguments, renames PATCH1 to PATCH2.
2662
2662
2663 Returns 0 on success."""
2663 Returns 0 on success."""
2664 q = repo.mq
2664 q = repo.mq
2665 if not name:
2665 if not name:
2666 name = patch
2666 name = patch
2667 patch = None
2667 patch = None
2668
2668
2669 if patch:
2669 if patch:
2670 patch = q.lookup(patch)
2670 patch = q.lookup(patch)
2671 else:
2671 else:
2672 if not q.applied:
2672 if not q.applied:
2673 ui.write(_('no patches applied\n'))
2673 ui.write(_('no patches applied\n'))
2674 return
2674 return
2675 patch = q.lookup('qtip')
2675 patch = q.lookup('qtip')
2676 absdest = q.join(name)
2676 absdest = q.join(name)
2677 if os.path.isdir(absdest):
2677 if os.path.isdir(absdest):
2678 name = normname(os.path.join(name, os.path.basename(patch)))
2678 name = normname(os.path.join(name, os.path.basename(patch)))
2679 absdest = q.join(name)
2679 absdest = q.join(name)
2680 q.checkpatchname(name)
2680 q.checkpatchname(name)
2681
2681
2682 ui.note(_('renaming %s to %s\n') % (patch, name))
2682 ui.note(_('renaming %s to %s\n') % (patch, name))
2683 i = q.findseries(patch)
2683 i = q.findseries(patch)
2684 guards = q.guard_re.findall(q.fullseries[i])
2684 guards = q.guard_re.findall(q.fullseries[i])
2685 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2685 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2686 q.parseseries()
2686 q.parseseries()
2687 q.seriesdirty = True
2687 q.seriesdirty = True
2688
2688
2689 info = q.isapplied(patch)
2689 info = q.isapplied(patch)
2690 if info:
2690 if info:
2691 q.applied[info[0]] = statusentry(info[1], name)
2691 q.applied[info[0]] = statusentry(info[1], name)
2692 q.applieddirty = True
2692 q.applieddirty = True
2693
2693
2694 destdir = os.path.dirname(absdest)
2694 destdir = os.path.dirname(absdest)
2695 if not os.path.isdir(destdir):
2695 if not os.path.isdir(destdir):
2696 os.makedirs(destdir)
2696 os.makedirs(destdir)
2697 util.rename(q.join(patch), absdest)
2697 util.rename(q.join(patch), absdest)
2698 r = q.qrepo()
2698 r = q.qrepo()
2699 if r and patch in r.dirstate:
2699 if r and patch in r.dirstate:
2700 wctx = r[None]
2700 wctx = r[None]
2701 wlock = r.wlock()
2701 wlock = r.wlock()
2702 try:
2702 try:
2703 if r.dirstate[patch] == 'a':
2703 if r.dirstate[patch] == 'a':
2704 r.dirstate.drop(patch)
2704 r.dirstate.drop(patch)
2705 r.dirstate.add(name)
2705 r.dirstate.add(name)
2706 else:
2706 else:
2707 wctx.copy(patch, name)
2707 wctx.copy(patch, name)
2708 wctx.forget([patch])
2708 wctx.forget([patch])
2709 finally:
2709 finally:
2710 wlock.release()
2710 wlock.release()
2711
2711
2712 q.savedirty()
2712 q.savedirty()
2713
2713
2714 @command("qrestore",
2714 @command("qrestore",
2715 [('d', 'delete', None, _('delete save entry')),
2715 [('d', 'delete', None, _('delete save entry')),
2716 ('u', 'update', None, _('update queue working directory'))],
2716 ('u', 'update', None, _('update queue working directory'))],
2717 _('hg qrestore [-d] [-u] REV'))
2717 _('hg qrestore [-d] [-u] REV'))
2718 def restore(ui, repo, rev, **opts):
2718 def restore(ui, repo, rev, **opts):
2719 """restore the queue state saved by a revision (DEPRECATED)
2719 """restore the queue state saved by a revision (DEPRECATED)
2720
2720
2721 This command is deprecated, use :hg:`rebase` instead."""
2721 This command is deprecated, use :hg:`rebase` instead."""
2722 rev = repo.lookup(rev)
2722 rev = repo.lookup(rev)
2723 q = repo.mq
2723 q = repo.mq
2724 q.restore(repo, rev, delete=opts.get('delete'),
2724 q.restore(repo, rev, delete=opts.get('delete'),
2725 qupdate=opts.get('update'))
2725 qupdate=opts.get('update'))
2726 q.savedirty()
2726 q.savedirty()
2727 return 0
2727 return 0
2728
2728
2729 @command("qsave",
2729 @command("qsave",
2730 [('c', 'copy', None, _('copy patch directory')),
2730 [('c', 'copy', None, _('copy patch directory')),
2731 ('n', 'name', '',
2731 ('n', 'name', '',
2732 _('copy directory name'), _('NAME')),
2732 _('copy directory name'), _('NAME')),
2733 ('e', 'empty', None, _('clear queue status file')),
2733 ('e', 'empty', None, _('clear queue status file')),
2734 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2734 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2735 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2735 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2736 def save(ui, repo, **opts):
2736 def save(ui, repo, **opts):
2737 """save current queue state (DEPRECATED)
2737 """save current queue state (DEPRECATED)
2738
2738
2739 This command is deprecated, use :hg:`rebase` instead."""
2739 This command is deprecated, use :hg:`rebase` instead."""
2740 q = repo.mq
2740 q = repo.mq
2741 message = cmdutil.logmessage(ui, opts)
2741 message = cmdutil.logmessage(ui, opts)
2742 ret = q.save(repo, msg=message)
2742 ret = q.save(repo, msg=message)
2743 if ret:
2743 if ret:
2744 return ret
2744 return ret
2745 q.savedirty() # save to .hg/patches before copying
2745 q.savedirty() # save to .hg/patches before copying
2746 if opts.get('copy'):
2746 if opts.get('copy'):
2747 path = q.path
2747 path = q.path
2748 if opts.get('name'):
2748 if opts.get('name'):
2749 newpath = os.path.join(q.basepath, opts.get('name'))
2749 newpath = os.path.join(q.basepath, opts.get('name'))
2750 if os.path.exists(newpath):
2750 if os.path.exists(newpath):
2751 if not os.path.isdir(newpath):
2751 if not os.path.isdir(newpath):
2752 raise util.Abort(_('destination %s exists and is not '
2752 raise util.Abort(_('destination %s exists and is not '
2753 'a directory') % newpath)
2753 'a directory') % newpath)
2754 if not opts.get('force'):
2754 if not opts.get('force'):
2755 raise util.Abort(_('destination %s exists, '
2755 raise util.Abort(_('destination %s exists, '
2756 'use -f to force') % newpath)
2756 'use -f to force') % newpath)
2757 else:
2757 else:
2758 newpath = savename(path)
2758 newpath = savename(path)
2759 ui.warn(_("copy %s to %s\n") % (path, newpath))
2759 ui.warn(_("copy %s to %s\n") % (path, newpath))
2760 util.copyfiles(path, newpath)
2760 util.copyfiles(path, newpath)
2761 if opts.get('empty'):
2761 if opts.get('empty'):
2762 del q.applied[:]
2762 del q.applied[:]
2763 q.applieddirty = True
2763 q.applieddirty = True
2764 q.savedirty()
2764 q.savedirty()
2765 return 0
2765 return 0
2766
2766
2767 @command("strip",
2767 @command("strip",
2768 [
2768 [
2769 ('r', 'rev', [], _('strip specified revision (optional, '
2769 ('r', 'rev', [], _('strip specified revision (optional, '
2770 'can specify revisions without this '
2770 'can specify revisions without this '
2771 'option)'), _('REV')),
2771 'option)'), _('REV')),
2772 ('f', 'force', None, _('force removal of changesets, discard '
2772 ('f', 'force', None, _('force removal of changesets, discard '
2773 'uncommitted changes (no backup)')),
2773 'uncommitted changes (no backup)')),
2774 ('b', 'backup', None, _('bundle only changesets with local revision'
2774 ('b', 'backup', None, _('bundle only changesets with local revision'
2775 ' number greater than REV which are not'
2775 ' number greater than REV which are not'
2776 ' descendants of REV (DEPRECATED)')),
2776 ' descendants of REV (DEPRECATED)')),
2777 ('', 'no-backup', None, _('no backups')),
2777 ('', 'no-backup', None, _('no backups')),
2778 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2778 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2779 ('n', '', None, _('ignored (DEPRECATED)')),
2779 ('n', '', None, _('ignored (DEPRECATED)')),
2780 ('k', 'keep', None, _("do not modify working copy during strip"))],
2780 ('k', 'keep', None, _("do not modify working copy during strip"))],
2781 _('hg strip [-k] [-f] [-n] REV...'))
2781 _('hg strip [-k] [-f] [-n] REV...'))
2782 def strip(ui, repo, *revs, **opts):
2782 def strip(ui, repo, *revs, **opts):
2783 """strip changesets and all their descendants from the repository
2783 """strip changesets and all their descendants from the repository
2784
2784
2785 The strip command removes the specified changesets and all their
2785 The strip command removes the specified changesets and all their
2786 descendants. If the working directory has uncommitted changes, the
2786 descendants. If the working directory has uncommitted changes, the
2787 operation is aborted unless the --force flag is supplied, in which
2787 operation is aborted unless the --force flag is supplied, in which
2788 case changes will be discarded.
2788 case changes will be discarded.
2789
2789
2790 If a parent of the working directory is stripped, then the working
2790 If a parent of the working directory is stripped, then the working
2791 directory will automatically be updated to the most recent
2791 directory will automatically be updated to the most recent
2792 available ancestor of the stripped parent after the operation
2792 available ancestor of the stripped parent after the operation
2793 completes.
2793 completes.
2794
2794
2795 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2795 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2796 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2796 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2797 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2797 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2798 where BUNDLE is the bundle file created by the strip. Note that
2798 where BUNDLE is the bundle file created by the strip. Note that
2799 the local revision numbers will in general be different after the
2799 the local revision numbers will in general be different after the
2800 restore.
2800 restore.
2801
2801
2802 Use the --no-backup option to discard the backup bundle once the
2802 Use the --no-backup option to discard the backup bundle once the
2803 operation completes.
2803 operation completes.
2804
2804
2805 Return 0 on success.
2805 Return 0 on success.
2806 """
2806 """
2807 backup = 'all'
2807 backup = 'all'
2808 if opts.get('backup'):
2808 if opts.get('backup'):
2809 backup = 'strip'
2809 backup = 'strip'
2810 elif opts.get('no_backup') or opts.get('nobackup'):
2810 elif opts.get('no_backup') or opts.get('nobackup'):
2811 backup = 'none'
2811 backup = 'none'
2812
2812
2813 cl = repo.changelog
2813 cl = repo.changelog
2814 revs = list(revs) + opts.get('rev')
2814 revs = list(revs) + opts.get('rev')
2815 revs = set(scmutil.revrange(repo, revs))
2815 revs = set(scmutil.revrange(repo, revs))
2816 if not revs:
2816 if not revs:
2817 raise util.Abort(_('empty revision set'))
2817 raise util.Abort(_('empty revision set'))
2818
2818
2819 descendants = set(cl.descendants(*revs))
2819 descendants = set(cl.descendants(*revs))
2820 strippedrevs = revs.union(descendants)
2820 strippedrevs = revs.union(descendants)
2821 roots = revs.difference(descendants)
2821 roots = revs.difference(descendants)
2822
2822
2823 update = False
2823 update = False
2824 # if one of the wdir parent is stripped we'll need
2824 # if one of the wdir parent is stripped we'll need
2825 # to update away to an earlier revision
2825 # to update away to an earlier revision
2826 for p in repo.dirstate.parents():
2826 for p in repo.dirstate.parents():
2827 if p != nullid and cl.rev(p) in strippedrevs:
2827 if p != nullid and cl.rev(p) in strippedrevs:
2828 update = True
2828 update = True
2829 break
2829 break
2830
2830
2831 rootnodes = set(cl.node(r) for r in roots)
2831 rootnodes = set(cl.node(r) for r in roots)
2832
2832
2833 q = repo.mq
2833 q = repo.mq
2834 if q.applied:
2834 if q.applied:
2835 # refresh queue state if we're about to strip
2835 # refresh queue state if we're about to strip
2836 # applied patches
2836 # applied patches
2837 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2837 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2838 q.applieddirty = True
2838 q.applieddirty = True
2839 start = 0
2839 start = 0
2840 end = len(q.applied)
2840 end = len(q.applied)
2841 for i, statusentry in enumerate(q.applied):
2841 for i, statusentry in enumerate(q.applied):
2842 if statusentry.node in rootnodes:
2842 if statusentry.node in rootnodes:
2843 # if one of the stripped roots is an applied
2843 # if one of the stripped roots is an applied
2844 # patch, only part of the queue is stripped
2844 # patch, only part of the queue is stripped
2845 start = i
2845 start = i
2846 break
2846 break
2847 del q.applied[start:end]
2847 del q.applied[start:end]
2848 q.savedirty()
2848 q.savedirty()
2849
2849
2850 revs = list(rootnodes)
2850 revs = list(rootnodes)
2851 if update and opts.get('keep'):
2851 if update and opts.get('keep'):
2852 wlock = repo.wlock()
2852 wlock = repo.wlock()
2853 try:
2853 try:
2854 urev = repo.mq.qparents(repo, revs[0])
2854 urev = repo.mq.qparents(repo, revs[0])
2855 repo.dirstate.rebuild(urev, repo[urev].manifest())
2855 repo.dirstate.rebuild(urev, repo[urev].manifest())
2856 repo.dirstate.write()
2856 repo.dirstate.write()
2857 update = False
2857 update = False
2858 finally:
2858 finally:
2859 wlock.release()
2859 wlock.release()
2860
2860
2861 repo.mq.strip(repo, revs, backup=backup, update=update,
2861 repo.mq.strip(repo, revs, backup=backup, update=update,
2862 force=opts.get('force'))
2862 force=opts.get('force'))
2863 return 0
2863 return 0
2864
2864
2865 @command("qselect",
2865 @command("qselect",
2866 [('n', 'none', None, _('disable all guards')),
2866 [('n', 'none', None, _('disable all guards')),
2867 ('s', 'series', None, _('list all guards in series file')),
2867 ('s', 'series', None, _('list all guards in series file')),
2868 ('', 'pop', None, _('pop to before first guarded applied patch')),
2868 ('', 'pop', None, _('pop to before first guarded applied patch')),
2869 ('', 'reapply', None, _('pop, then reapply patches'))],
2869 ('', 'reapply', None, _('pop, then reapply patches'))],
2870 _('hg qselect [OPTION]... [GUARD]...'))
2870 _('hg qselect [OPTION]... [GUARD]...'))
2871 def select(ui, repo, *args, **opts):
2871 def select(ui, repo, *args, **opts):
2872 '''set or print guarded patches to push
2872 '''set or print guarded patches to push
2873
2873
2874 Use the :hg:`qguard` command to set or print guards on patch, then use
2874 Use the :hg:`qguard` command to set or print guards on patch, then use
2875 qselect to tell mq which guards to use. A patch will be pushed if
2875 qselect to tell mq which guards to use. A patch will be pushed if
2876 it has no guards or any positive guards match the currently
2876 it has no guards or any positive guards match the currently
2877 selected guard, but will not be pushed if any negative guards
2877 selected guard, but will not be pushed if any negative guards
2878 match the current guard. For example::
2878 match the current guard. For example::
2879
2879
2880 qguard foo.patch -- -stable (negative guard)
2880 qguard foo.patch -- -stable (negative guard)
2881 qguard bar.patch +stable (positive guard)
2881 qguard bar.patch +stable (positive guard)
2882 qselect stable
2882 qselect stable
2883
2883
2884 This activates the "stable" guard. mq will skip foo.patch (because
2884 This activates the "stable" guard. mq will skip foo.patch (because
2885 it has a negative match) but push bar.patch (because it has a
2885 it has a negative match) but push bar.patch (because it has a
2886 positive match).
2886 positive match).
2887
2887
2888 With no arguments, prints the currently active guards.
2888 With no arguments, prints the currently active guards.
2889 With one argument, sets the active guard.
2889 With one argument, sets the active guard.
2890
2890
2891 Use -n/--none to deactivate guards (no other arguments needed).
2891 Use -n/--none to deactivate guards (no other arguments needed).
2892 When no guards are active, patches with positive guards are
2892 When no guards are active, patches with positive guards are
2893 skipped and patches with negative guards are pushed.
2893 skipped and patches with negative guards are pushed.
2894
2894
2895 qselect can change the guards on applied patches. It does not pop
2895 qselect can change the guards on applied patches. It does not pop
2896 guarded patches by default. Use --pop to pop back to the last
2896 guarded patches by default. Use --pop to pop back to the last
2897 applied patch that is not guarded. Use --reapply (which implies
2897 applied patch that is not guarded. Use --reapply (which implies
2898 --pop) to push back to the current patch afterwards, but skip
2898 --pop) to push back to the current patch afterwards, but skip
2899 guarded patches.
2899 guarded patches.
2900
2900
2901 Use -s/--series to print a list of all guards in the series file
2901 Use -s/--series to print a list of all guards in the series file
2902 (no other arguments needed). Use -v for more information.
2902 (no other arguments needed). Use -v for more information.
2903
2903
2904 Returns 0 on success.'''
2904 Returns 0 on success.'''
2905
2905
2906 q = repo.mq
2906 q = repo.mq
2907 guards = q.active()
2907 guards = q.active()
2908 if args or opts.get('none'):
2908 if args or opts.get('none'):
2909 old_unapplied = q.unapplied(repo)
2909 old_unapplied = q.unapplied(repo)
2910 old_guarded = [i for i in xrange(len(q.applied)) if
2910 old_guarded = [i for i in xrange(len(q.applied)) if
2911 not q.pushable(i)[0]]
2911 not q.pushable(i)[0]]
2912 q.setactive(args)
2912 q.setactive(args)
2913 q.savedirty()
2913 q.savedirty()
2914 if not args:
2914 if not args:
2915 ui.status(_('guards deactivated\n'))
2915 ui.status(_('guards deactivated\n'))
2916 if not opts.get('pop') and not opts.get('reapply'):
2916 if not opts.get('pop') and not opts.get('reapply'):
2917 unapplied = q.unapplied(repo)
2917 unapplied = q.unapplied(repo)
2918 guarded = [i for i in xrange(len(q.applied))
2918 guarded = [i for i in xrange(len(q.applied))
2919 if not q.pushable(i)[0]]
2919 if not q.pushable(i)[0]]
2920 if len(unapplied) != len(old_unapplied):
2920 if len(unapplied) != len(old_unapplied):
2921 ui.status(_('number of unguarded, unapplied patches has '
2921 ui.status(_('number of unguarded, unapplied patches has '
2922 'changed from %d to %d\n') %
2922 'changed from %d to %d\n') %
2923 (len(old_unapplied), len(unapplied)))
2923 (len(old_unapplied), len(unapplied)))
2924 if len(guarded) != len(old_guarded):
2924 if len(guarded) != len(old_guarded):
2925 ui.status(_('number of guarded, applied patches has changed '
2925 ui.status(_('number of guarded, applied patches has changed '
2926 'from %d to %d\n') %
2926 'from %d to %d\n') %
2927 (len(old_guarded), len(guarded)))
2927 (len(old_guarded), len(guarded)))
2928 elif opts.get('series'):
2928 elif opts.get('series'):
2929 guards = {}
2929 guards = {}
2930 noguards = 0
2930 noguards = 0
2931 for gs in q.seriesguards:
2931 for gs in q.seriesguards:
2932 if not gs:
2932 if not gs:
2933 noguards += 1
2933 noguards += 1
2934 for g in gs:
2934 for g in gs:
2935 guards.setdefault(g, 0)
2935 guards.setdefault(g, 0)
2936 guards[g] += 1
2936 guards[g] += 1
2937 if ui.verbose:
2937 if ui.verbose:
2938 guards['NONE'] = noguards
2938 guards['NONE'] = noguards
2939 guards = guards.items()
2939 guards = guards.items()
2940 guards.sort(key=lambda x: x[0][1:])
2940 guards.sort(key=lambda x: x[0][1:])
2941 if guards:
2941 if guards:
2942 ui.note(_('guards in series file:\n'))
2942 ui.note(_('guards in series file:\n'))
2943 for guard, count in guards:
2943 for guard, count in guards:
2944 ui.note('%2d ' % count)
2944 ui.note('%2d ' % count)
2945 ui.write(guard, '\n')
2945 ui.write(guard, '\n')
2946 else:
2946 else:
2947 ui.note(_('no guards in series file\n'))
2947 ui.note(_('no guards in series file\n'))
2948 else:
2948 else:
2949 if guards:
2949 if guards:
2950 ui.note(_('active guards:\n'))
2950 ui.note(_('active guards:\n'))
2951 for g in guards:
2951 for g in guards:
2952 ui.write(g, '\n')
2952 ui.write(g, '\n')
2953 else:
2953 else:
2954 ui.write(_('no active guards\n'))
2954 ui.write(_('no active guards\n'))
2955 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2955 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2956 popped = False
2956 popped = False
2957 if opts.get('pop') or opts.get('reapply'):
2957 if opts.get('pop') or opts.get('reapply'):
2958 for i in xrange(len(q.applied)):
2958 for i in xrange(len(q.applied)):
2959 pushable, reason = q.pushable(i)
2959 pushable, reason = q.pushable(i)
2960 if not pushable:
2960 if not pushable:
2961 ui.status(_('popping guarded patches\n'))
2961 ui.status(_('popping guarded patches\n'))
2962 popped = True
2962 popped = True
2963 if i == 0:
2963 if i == 0:
2964 q.pop(repo, all=True)
2964 q.pop(repo, all=True)
2965 else:
2965 else:
2966 q.pop(repo, str(i - 1))
2966 q.pop(repo, str(i - 1))
2967 break
2967 break
2968 if popped:
2968 if popped:
2969 try:
2969 try:
2970 if reapply:
2970 if reapply:
2971 ui.status(_('reapplying unguarded patches\n'))
2971 ui.status(_('reapplying unguarded patches\n'))
2972 q.push(repo, reapply)
2972 q.push(repo, reapply)
2973 finally:
2973 finally:
2974 q.savedirty()
2974 q.savedirty()
2975
2975
2976 @command("qfinish",
2976 @command("qfinish",
2977 [('a', 'applied', None, _('finish all applied changesets'))],
2977 [('a', 'applied', None, _('finish all applied changesets'))],
2978 _('hg qfinish [-a] [REV]...'))
2978 _('hg qfinish [-a] [REV]...'))
2979 def finish(ui, repo, *revrange, **opts):
2979 def finish(ui, repo, *revrange, **opts):
2980 """move applied patches into repository history
2980 """move applied patches into repository history
2981
2981
2982 Finishes the specified revisions (corresponding to applied
2982 Finishes the specified revisions (corresponding to applied
2983 patches) by moving them out of mq control into regular repository
2983 patches) by moving them out of mq control into regular repository
2984 history.
2984 history.
2985
2985
2986 Accepts a revision range or the -a/--applied option. If --applied
2986 Accepts a revision range or the -a/--applied option. If --applied
2987 is specified, all applied mq revisions are removed from mq
2987 is specified, all applied mq revisions are removed from mq
2988 control. Otherwise, the given revisions must be at the base of the
2988 control. Otherwise, the given revisions must be at the base of the
2989 stack of applied patches.
2989 stack of applied patches.
2990
2990
2991 This can be especially useful if your changes have been applied to
2991 This can be especially useful if your changes have been applied to
2992 an upstream repository, or if you are about to push your changes
2992 an upstream repository, or if you are about to push your changes
2993 to upstream.
2993 to upstream.
2994
2994
2995 Returns 0 on success.
2995 Returns 0 on success.
2996 """
2996 """
2997 if not opts.get('applied') and not revrange:
2997 if not opts.get('applied') and not revrange:
2998 raise util.Abort(_('no revisions specified'))
2998 raise util.Abort(_('no revisions specified'))
2999 elif opts.get('applied'):
2999 elif opts.get('applied'):
3000 revrange = ('qbase::qtip',) + revrange
3000 revrange = ('qbase::qtip',) + revrange
3001
3001
3002 q = repo.mq
3002 q = repo.mq
3003 if not q.applied:
3003 if not q.applied:
3004 ui.status(_('no patches applied\n'))
3004 ui.status(_('no patches applied\n'))
3005 return 0
3005 return 0
3006
3006
3007 revs = scmutil.revrange(repo, revrange)
3007 revs = scmutil.revrange(repo, revrange)
3008 if repo['.'].rev() in revs and repo[None].files():
3008 if repo['.'].rev() in revs and repo[None].files():
3009 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3009 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3010 # queue.finish may changes phases but leave the responsability to lock the
3010 # queue.finish may changes phases but leave the responsability to lock the
3011 # repo to the caller to avoid deadlock with wlock. This command code is
3011 # repo to the caller to avoid deadlock with wlock. This command code is
3012 # responsability for this locking.
3012 # responsability for this locking.
3013 lock = repo.lock()
3013 lock = repo.lock()
3014 try:
3014 try:
3015 q.finish(repo, revs)
3015 q.finish(repo, revs)
3016 q.savedirty()
3016 q.savedirty()
3017 finally:
3017 finally:
3018 lock.release()
3018 lock.release()
3019 return 0
3019 return 0
3020
3020
3021 @command("qqueue",
3021 @command("qqueue",
3022 [('l', 'list', False, _('list all available queues')),
3022 [('l', 'list', False, _('list all available queues')),
3023 ('', 'active', False, _('print name of active queue')),
3023 ('', 'active', False, _('print name of active queue')),
3024 ('c', 'create', False, _('create new queue')),
3024 ('c', 'create', False, _('create new queue')),
3025 ('', 'rename', False, _('rename active queue')),
3025 ('', 'rename', False, _('rename active queue')),
3026 ('', 'delete', False, _('delete reference to queue')),
3026 ('', 'delete', False, _('delete reference to queue')),
3027 ('', 'purge', False, _('delete queue, and remove patch dir')),
3027 ('', 'purge', False, _('delete queue, and remove patch dir')),
3028 ],
3028 ],
3029 _('[OPTION] [QUEUE]'))
3029 _('[OPTION] [QUEUE]'))
3030 def qqueue(ui, repo, name=None, **opts):
3030 def qqueue(ui, repo, name=None, **opts):
3031 '''manage multiple patch queues
3031 '''manage multiple patch queues
3032
3032
3033 Supports switching between different patch queues, as well as creating
3033 Supports switching between different patch queues, as well as creating
3034 new patch queues and deleting existing ones.
3034 new patch queues and deleting existing ones.
3035
3035
3036 Omitting a queue name or specifying -l/--list will show you the registered
3036 Omitting a queue name or specifying -l/--list will show you the registered
3037 queues - by default the "normal" patches queue is registered. The currently
3037 queues - by default the "normal" patches queue is registered. The currently
3038 active queue will be marked with "(active)". Specifying --active will print
3038 active queue will be marked with "(active)". Specifying --active will print
3039 only the name of the active queue.
3039 only the name of the active queue.
3040
3040
3041 To create a new queue, use -c/--create. The queue is automatically made
3041 To create a new queue, use -c/--create. The queue is automatically made
3042 active, except in the case where there are applied patches from the
3042 active, except in the case where there are applied patches from the
3043 currently active queue in the repository. Then the queue will only be
3043 currently active queue in the repository. Then the queue will only be
3044 created and switching will fail.
3044 created and switching will fail.
3045
3045
3046 To delete an existing queue, use --delete. You cannot delete the currently
3046 To delete an existing queue, use --delete. You cannot delete the currently
3047 active queue.
3047 active queue.
3048
3048
3049 Returns 0 on success.
3049 Returns 0 on success.
3050 '''
3050 '''
3051 q = repo.mq
3051 q = repo.mq
3052 _defaultqueue = 'patches'
3052 _defaultqueue = 'patches'
3053 _allqueues = 'patches.queues'
3053 _allqueues = 'patches.queues'
3054 _activequeue = 'patches.queue'
3054 _activequeue = 'patches.queue'
3055
3055
3056 def _getcurrent():
3056 def _getcurrent():
3057 cur = os.path.basename(q.path)
3057 cur = os.path.basename(q.path)
3058 if cur.startswith('patches-'):
3058 if cur.startswith('patches-'):
3059 cur = cur[8:]
3059 cur = cur[8:]
3060 return cur
3060 return cur
3061
3061
3062 def _noqueues():
3062 def _noqueues():
3063 try:
3063 try:
3064 fh = repo.opener(_allqueues, 'r')
3064 fh = repo.opener(_allqueues, 'r')
3065 fh.close()
3065 fh.close()
3066 except IOError:
3066 except IOError:
3067 return True
3067 return True
3068
3068
3069 return False
3069 return False
3070
3070
3071 def _getqueues():
3071 def _getqueues():
3072 current = _getcurrent()
3072 current = _getcurrent()
3073
3073
3074 try:
3074 try:
3075 fh = repo.opener(_allqueues, 'r')
3075 fh = repo.opener(_allqueues, 'r')
3076 queues = [queue.strip() for queue in fh if queue.strip()]
3076 queues = [queue.strip() for queue in fh if queue.strip()]
3077 fh.close()
3077 fh.close()
3078 if current not in queues:
3078 if current not in queues:
3079 queues.append(current)
3079 queues.append(current)
3080 except IOError:
3080 except IOError:
3081 queues = [_defaultqueue]
3081 queues = [_defaultqueue]
3082
3082
3083 return sorted(queues)
3083 return sorted(queues)
3084
3084
3085 def _setactive(name):
3085 def _setactive(name):
3086 if q.applied:
3086 if q.applied:
3087 raise util.Abort(_('patches applied - cannot set new queue active'))
3087 raise util.Abort(_('patches applied - cannot set new queue active'))
3088 _setactivenocheck(name)
3088 _setactivenocheck(name)
3089
3089
3090 def _setactivenocheck(name):
3090 def _setactivenocheck(name):
3091 fh = repo.opener(_activequeue, 'w')
3091 fh = repo.opener(_activequeue, 'w')
3092 if name != 'patches':
3092 if name != 'patches':
3093 fh.write(name)
3093 fh.write(name)
3094 fh.close()
3094 fh.close()
3095
3095
3096 def _addqueue(name):
3096 def _addqueue(name):
3097 fh = repo.opener(_allqueues, 'a')
3097 fh = repo.opener(_allqueues, 'a')
3098 fh.write('%s\n' % (name,))
3098 fh.write('%s\n' % (name,))
3099 fh.close()
3099 fh.close()
3100
3100
3101 def _queuedir(name):
3101 def _queuedir(name):
3102 if name == 'patches':
3102 if name == 'patches':
3103 return repo.join('patches')
3103 return repo.join('patches')
3104 else:
3104 else:
3105 return repo.join('patches-' + name)
3105 return repo.join('patches-' + name)
3106
3106
3107 def _validname(name):
3107 def _validname(name):
3108 for n in name:
3108 for n in name:
3109 if n in ':\\/.':
3109 if n in ':\\/.':
3110 return False
3110 return False
3111 return True
3111 return True
3112
3112
3113 def _delete(name):
3113 def _delete(name):
3114 if name not in existing:
3114 if name not in existing:
3115 raise util.Abort(_('cannot delete queue that does not exist'))
3115 raise util.Abort(_('cannot delete queue that does not exist'))
3116
3116
3117 current = _getcurrent()
3117 current = _getcurrent()
3118
3118
3119 if name == current:
3119 if name == current:
3120 raise util.Abort(_('cannot delete currently active queue'))
3120 raise util.Abort(_('cannot delete currently active queue'))
3121
3121
3122 fh = repo.opener('patches.queues.new', 'w')
3122 fh = repo.opener('patches.queues.new', 'w')
3123 for queue in existing:
3123 for queue in existing:
3124 if queue == name:
3124 if queue == name:
3125 continue
3125 continue
3126 fh.write('%s\n' % (queue,))
3126 fh.write('%s\n' % (queue,))
3127 fh.close()
3127 fh.close()
3128 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3128 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3129
3129
3130 if not name or opts.get('list') or opts.get('active'):
3130 if not name or opts.get('list') or opts.get('active'):
3131 current = _getcurrent()
3131 current = _getcurrent()
3132 if opts.get('active'):
3132 if opts.get('active'):
3133 ui.write('%s\n' % (current,))
3133 ui.write('%s\n' % (current,))
3134 return
3134 return
3135 for queue in _getqueues():
3135 for queue in _getqueues():
3136 ui.write('%s' % (queue,))
3136 ui.write('%s' % (queue,))
3137 if queue == current and not ui.quiet:
3137 if queue == current and not ui.quiet:
3138 ui.write(_(' (active)\n'))
3138 ui.write(_(' (active)\n'))
3139 else:
3139 else:
3140 ui.write('\n')
3140 ui.write('\n')
3141 return
3141 return
3142
3142
3143 if not _validname(name):
3143 if not _validname(name):
3144 raise util.Abort(
3144 raise util.Abort(
3145 _('invalid queue name, may not contain the characters ":\\/."'))
3145 _('invalid queue name, may not contain the characters ":\\/."'))
3146
3146
3147 existing = _getqueues()
3147 existing = _getqueues()
3148
3148
3149 if opts.get('create'):
3149 if opts.get('create'):
3150 if name in existing:
3150 if name in existing:
3151 raise util.Abort(_('queue "%s" already exists') % name)
3151 raise util.Abort(_('queue "%s" already exists') % name)
3152 if _noqueues():
3152 if _noqueues():
3153 _addqueue(_defaultqueue)
3153 _addqueue(_defaultqueue)
3154 _addqueue(name)
3154 _addqueue(name)
3155 _setactive(name)
3155 _setactive(name)
3156 elif opts.get('rename'):
3156 elif opts.get('rename'):
3157 current = _getcurrent()
3157 current = _getcurrent()
3158 if name == current:
3158 if name == current:
3159 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3159 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3160 if name in existing:
3160 if name in existing:
3161 raise util.Abort(_('queue "%s" already exists') % name)
3161 raise util.Abort(_('queue "%s" already exists') % name)
3162
3162
3163 olddir = _queuedir(current)
3163 olddir = _queuedir(current)
3164 newdir = _queuedir(name)
3164 newdir = _queuedir(name)
3165
3165
3166 if os.path.exists(newdir):
3166 if os.path.exists(newdir):
3167 raise util.Abort(_('non-queue directory "%s" already exists') %
3167 raise util.Abort(_('non-queue directory "%s" already exists') %
3168 newdir)
3168 newdir)
3169
3169
3170 fh = repo.opener('patches.queues.new', 'w')
3170 fh = repo.opener('patches.queues.new', 'w')
3171 for queue in existing:
3171 for queue in existing:
3172 if queue == current:
3172 if queue == current:
3173 fh.write('%s\n' % (name,))
3173 fh.write('%s\n' % (name,))
3174 if os.path.exists(olddir):
3174 if os.path.exists(olddir):
3175 util.rename(olddir, newdir)
3175 util.rename(olddir, newdir)
3176 else:
3176 else:
3177 fh.write('%s\n' % (queue,))
3177 fh.write('%s\n' % (queue,))
3178 fh.close()
3178 fh.close()
3179 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3179 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3180 _setactivenocheck(name)
3180 _setactivenocheck(name)
3181 elif opts.get('delete'):
3181 elif opts.get('delete'):
3182 _delete(name)
3182 _delete(name)
3183 elif opts.get('purge'):
3183 elif opts.get('purge'):
3184 if name in existing:
3184 if name in existing:
3185 _delete(name)
3185 _delete(name)
3186 qdir = _queuedir(name)
3186 qdir = _queuedir(name)
3187 if os.path.exists(qdir):
3187 if os.path.exists(qdir):
3188 shutil.rmtree(qdir)
3188 shutil.rmtree(qdir)
3189 else:
3189 else:
3190 if name not in existing:
3190 if name not in existing:
3191 raise util.Abort(_('use --create to create a new queue'))
3191 raise util.Abort(_('use --create to create a new queue'))
3192 _setactive(name)
3192 _setactive(name)
3193
3193
3194 def mqphasedefaults(repo, roots):
3194 def mqphasedefaults(repo, roots):
3195 """callback used to set mq changeset as secret when no phase data exists"""
3195 """callback used to set mq changeset as secret when no phase data exists"""
3196 if repo.mq.applied:
3196 if repo.mq.applied:
3197 if repo.ui.configbool('mq', 'secret', False):
3197 if repo.ui.configbool('mq', 'secret', False):
3198 mqphase = phases.secret
3198 mqphase = phases.secret
3199 else:
3199 else:
3200 mqphase = phases.draft
3200 mqphase = phases.draft
3201 qbase = repo[repo.mq.applied[0].node]
3201 qbase = repo[repo.mq.applied[0].node]
3202 roots[mqphase].add(qbase.node())
3202 roots[mqphase].add(qbase.node())
3203 return roots
3203 return roots
3204
3204
3205 def reposetup(ui, repo):
3205 def reposetup(ui, repo):
3206 class mqrepo(repo.__class__):
3206 class mqrepo(repo.__class__):
3207 @util.propertycache
3207 @util.propertycache
3208 def mq(self):
3208 def mq(self):
3209 return queue(self.ui, self.path)
3209 return queue(self.ui, self.path)
3210
3210
3211 def abortifwdirpatched(self, errmsg, force=False):
3211 def abortifwdirpatched(self, errmsg, force=False):
3212 if self.mq.applied and not force:
3212 if self.mq.applied and not force:
3213 parents = self.dirstate.parents()
3213 parents = self.dirstate.parents()
3214 patches = [s.node for s in self.mq.applied]
3214 patches = [s.node for s in self.mq.applied]
3215 if parents[0] in patches or parents[1] in patches:
3215 if parents[0] in patches or parents[1] in patches:
3216 raise util.Abort(errmsg)
3216 raise util.Abort(errmsg)
3217
3217
3218 def commit(self, text="", user=None, date=None, match=None,
3218 def commit(self, text="", user=None, date=None, match=None,
3219 force=False, editor=False, extra={}):
3219 force=False, editor=False, extra={}):
3220 self.abortifwdirpatched(
3220 self.abortifwdirpatched(
3221 _('cannot commit over an applied mq patch'),
3221 _('cannot commit over an applied mq patch'),
3222 force)
3222 force)
3223
3223
3224 return super(mqrepo, self).commit(text, user, date, match, force,
3224 return super(mqrepo, self).commit(text, user, date, match, force,
3225 editor, extra)
3225 editor, extra)
3226
3226
3227 def checkpush(self, force, revs):
3227 def checkpush(self, force, revs):
3228 if self.mq.applied and not force:
3228 if self.mq.applied and not force:
3229 outapplied = [e.node for e in self.mq.applied]
3229 outapplied = [e.node for e in self.mq.applied]
3230 if revs:
3230 if revs:
3231 # Assume applied patches have no non-patch descendants and
3231 # Assume applied patches have no non-patch descendants and
3232 # are not on remote already. Filtering any changeset not
3232 # are not on remote already. Filtering any changeset not
3233 # pushed.
3233 # pushed.
3234 heads = set(revs)
3234 heads = set(revs)
3235 for node in reversed(outapplied):
3235 for node in reversed(outapplied):
3236 if node in heads:
3236 if node in heads:
3237 break
3237 break
3238 else:
3238 else:
3239 outapplied.pop()
3239 outapplied.pop()
3240 # looking for pushed and shared changeset
3240 # looking for pushed and shared changeset
3241 for node in outapplied:
3241 for node in outapplied:
3242 if repo[node].phase() < phases.secret:
3242 if repo[node].phase() < phases.secret:
3243 raise util.Abort(_('source has mq patches applied'))
3243 raise util.Abort(_('source has mq patches applied'))
3244 # no non-secret patches pushed
3244 # no non-secret patches pushed
3245 super(mqrepo, self).checkpush(force, revs)
3245 super(mqrepo, self).checkpush(force, revs)
3246
3246
3247 def _findtags(self):
3247 def _findtags(self):
3248 '''augment tags from base class with patch tags'''
3248 '''augment tags from base class with patch tags'''
3249 result = super(mqrepo, self)._findtags()
3249 result = super(mqrepo, self)._findtags()
3250
3250
3251 q = self.mq
3251 q = self.mq
3252 if not q.applied:
3252 if not q.applied:
3253 return result
3253 return result
3254
3254
3255 mqtags = [(patch.node, patch.name) for patch in q.applied]
3255 mqtags = [(patch.node, patch.name) for patch in q.applied]
3256
3256
3257 try:
3257 try:
3258 self.changelog.rev(mqtags[-1][0])
3258 self.changelog.rev(mqtags[-1][0])
3259 except error.LookupError:
3259 except error.LookupError:
3260 self.ui.warn(_('mq status file refers to unknown node %s\n')
3260 self.ui.warn(_('mq status file refers to unknown node %s\n')
3261 % short(mqtags[-1][0]))
3261 % short(mqtags[-1][0]))
3262 return result
3262 return result
3263
3263
3264 mqtags.append((mqtags[-1][0], 'qtip'))
3264 mqtags.append((mqtags[-1][0], 'qtip'))
3265 mqtags.append((mqtags[0][0], 'qbase'))
3265 mqtags.append((mqtags[0][0], 'qbase'))
3266 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3266 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3267 tags = result[0]
3267 tags = result[0]
3268 for patch in mqtags:
3268 for patch in mqtags:
3269 if patch[1] in tags:
3269 if patch[1] in tags:
3270 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3270 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3271 % patch[1])
3271 % patch[1])
3272 else:
3272 else:
3273 tags[patch[1]] = patch[0]
3273 tags[patch[1]] = patch[0]
3274
3274
3275 return result
3275 return result
3276
3276
3277 def _branchtags(self, partial, lrev):
3277 def _branchtags(self, partial, lrev):
3278 q = self.mq
3278 q = self.mq
3279 cl = self.changelog
3279 cl = self.changelog
3280 qbase = None
3280 qbase = None
3281 if not q.applied:
3281 if not q.applied:
3282 if getattr(self, '_committingpatch', False):
3282 if getattr(self, '_committingpatch', False):
3283 # Committing a new patch, must be tip
3283 # Committing a new patch, must be tip
3284 qbase = len(cl) - 1
3284 qbase = len(cl) - 1
3285 else:
3285 else:
3286 qbasenode = q.applied[0].node
3286 qbasenode = q.applied[0].node
3287 try:
3287 try:
3288 qbase = cl.rev(qbasenode)
3288 qbase = cl.rev(qbasenode)
3289 except error.LookupError:
3289 except error.LookupError:
3290 self.ui.warn(_('mq status file refers to unknown node %s\n')
3290 self.ui.warn(_('mq status file refers to unknown node %s\n')
3291 % short(qbasenode))
3291 % short(qbasenode))
3292 if qbase is None:
3292 if qbase is None:
3293 return super(mqrepo, self)._branchtags(partial, lrev)
3293 return super(mqrepo, self)._branchtags(partial, lrev)
3294
3294
3295 start = lrev + 1
3295 start = lrev + 1
3296 if start < qbase:
3296 if start < qbase:
3297 # update the cache (excluding the patches) and save it
3297 # update the cache (excluding the patches) and save it
3298 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3298 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3299 self._updatebranchcache(partial, ctxgen)
3299 self._updatebranchcache(partial, ctxgen)
3300 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3300 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3301 start = qbase
3301 start = qbase
3302 # if start = qbase, the cache is as updated as it should be.
3302 # if start = qbase, the cache is as updated as it should be.
3303 # if start > qbase, the cache includes (part of) the patches.
3303 # if start > qbase, the cache includes (part of) the patches.
3304 # we might as well use it, but we won't save it.
3304 # we might as well use it, but we won't save it.
3305
3305
3306 # update the cache up to the tip
3306 # update the cache up to the tip
3307 ctxgen = (self[r] for r in xrange(start, len(cl)))
3307 ctxgen = (self[r] for r in xrange(start, len(cl)))
3308 self._updatebranchcache(partial, ctxgen)
3308 self._updatebranchcache(partial, ctxgen)
3309
3309
3310 return partial
3310 return partial
3311
3311
3312 if repo.local():
3312 if repo.local():
3313 repo.__class__ = mqrepo
3313 repo.__class__ = mqrepo
3314
3314
3315 repo._phasedefaults.append(mqphasedefaults)
3315 repo._phasedefaults.append(mqphasedefaults)
3316
3316
3317 def mqimport(orig, ui, repo, *args, **kwargs):
3317 def mqimport(orig, ui, repo, *args, **kwargs):
3318 if (util.safehasattr(repo, 'abortifwdirpatched')
3318 if (util.safehasattr(repo, 'abortifwdirpatched')
3319 and not kwargs.get('no_commit', False)):
3319 and not kwargs.get('no_commit', False)):
3320 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3320 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3321 kwargs.get('force'))
3321 kwargs.get('force'))
3322 return orig(ui, repo, *args, **kwargs)
3322 return orig(ui, repo, *args, **kwargs)
3323
3323
3324 def mqinit(orig, ui, *args, **kwargs):
3324 def mqinit(orig, ui, *args, **kwargs):
3325 mq = kwargs.pop('mq', None)
3325 mq = kwargs.pop('mq', None)
3326
3326
3327 if not mq:
3327 if not mq:
3328 return orig(ui, *args, **kwargs)
3328 return orig(ui, *args, **kwargs)
3329
3329
3330 if args:
3330 if args:
3331 repopath = args[0]
3331 repopath = args[0]
3332 if not hg.islocal(repopath):
3332 if not hg.islocal(repopath):
3333 raise util.Abort(_('only a local queue repository '
3333 raise util.Abort(_('only a local queue repository '
3334 'may be initialized'))
3334 'may be initialized'))
3335 else:
3335 else:
3336 repopath = cmdutil.findrepo(os.getcwd())
3336 repopath = cmdutil.findrepo(os.getcwd())
3337 if not repopath:
3337 if not repopath:
3338 raise util.Abort(_('there is no Mercurial repository here '
3338 raise util.Abort(_('there is no Mercurial repository here '
3339 '(.hg not found)'))
3339 '(.hg not found)'))
3340 repo = hg.repository(ui, repopath)
3340 repo = hg.repository(ui, repopath)
3341 return qinit(ui, repo, True)
3341 return qinit(ui, repo, True)
3342
3342
3343 def mqcommand(orig, ui, repo, *args, **kwargs):
3343 def mqcommand(orig, ui, repo, *args, **kwargs):
3344 """Add --mq option to operate on patch repository instead of main"""
3344 """Add --mq option to operate on patch repository instead of main"""
3345
3345
3346 # some commands do not like getting unknown options
3346 # some commands do not like getting unknown options
3347 mq = kwargs.pop('mq', None)
3347 mq = kwargs.pop('mq', None)
3348
3348
3349 if not mq:
3349 if not mq:
3350 return orig(ui, repo, *args, **kwargs)
3350 return orig(ui, repo, *args, **kwargs)
3351
3351
3352 q = repo.mq
3352 q = repo.mq
3353 r = q.qrepo()
3353 r = q.qrepo()
3354 if not r:
3354 if not r:
3355 raise util.Abort(_('no queue repository'))
3355 raise util.Abort(_('no queue repository'))
3356 return orig(r.ui, r, *args, **kwargs)
3356 return orig(r.ui, r, *args, **kwargs)
3357
3357
3358 def summary(orig, ui, repo, *args, **kwargs):
3358 def summary(orig, ui, repo, *args, **kwargs):
3359 r = orig(ui, repo, *args, **kwargs)
3359 r = orig(ui, repo, *args, **kwargs)
3360 q = repo.mq
3360 q = repo.mq
3361 m = []
3361 m = []
3362 a, u = len(q.applied), len(q.unapplied(repo))
3362 a, u = len(q.applied), len(q.unapplied(repo))
3363 if a:
3363 if a:
3364 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3364 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3365 if u:
3365 if u:
3366 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3366 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3367 if m:
3367 if m:
3368 ui.write("mq: %s\n" % ', '.join(m))
3368 ui.write("mq: %s\n" % ', '.join(m))
3369 else:
3369 else:
3370 ui.note(_("mq: (empty queue)\n"))
3370 ui.note(_("mq: (empty queue)\n"))
3371 return r
3371 return r
3372
3372
3373 def revsetmq(repo, subset, x):
3373 def revsetmq(repo, subset, x):
3374 """``mq()``
3374 """``mq()``
3375 Changesets managed by MQ.
3375 Changesets managed by MQ.
3376 """
3376 """
3377 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3377 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3378 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3378 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3379 return [r for r in subset if r in applied]
3379 return [r for r in subset if r in applied]
3380
3380
3381 def extsetup(ui):
3381 def extsetup(ui):
3382 revset.symbols['mq'] = revsetmq
3382 revset.symbols['mq'] = revsetmq
3383
3383
3384 # tell hggettext to extract docstrings from these functions:
3384 # tell hggettext to extract docstrings from these functions:
3385 i18nfunctions = [revsetmq]
3385 i18nfunctions = [revsetmq]
3386
3386
3387 def uisetup(ui):
3387 def uisetup(ui):
3388 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3388 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3389
3389
3390 extensions.wrapcommand(commands.table, 'import', mqimport)
3390 extensions.wrapcommand(commands.table, 'import', mqimport)
3391 extensions.wrapcommand(commands.table, 'summary', summary)
3391 extensions.wrapcommand(commands.table, 'summary', summary)
3392
3392
3393 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3393 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3394 entry[1].extend(mqopt)
3394 entry[1].extend(mqopt)
3395
3395
3396 nowrap = set(commands.norepo.split(" "))
3396 nowrap = set(commands.norepo.split(" "))
3397
3397
3398 def dotable(cmdtable):
3398 def dotable(cmdtable):
3399 for cmd in cmdtable.keys():
3399 for cmd in cmdtable.keys():
3400 cmd = cmdutil.parsealiases(cmd)[0]
3400 cmd = cmdutil.parsealiases(cmd)[0]
3401 if cmd in nowrap:
3401 if cmd in nowrap:
3402 continue
3402 continue
3403 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3403 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3404 entry[1].extend(mqopt)
3404 entry[1].extend(mqopt)
3405
3405
3406 dotable(commands.table)
3406 dotable(commands.table)
3407
3407
3408 for extname, extmodule in extensions.extensions():
3408 for extname, extmodule in extensions.extensions():
3409 if extmodule.__file__ != __file__:
3409 if extmodule.__file__ != __file__:
3410 dotable(getattr(extmodule, 'cmdtable', {}))
3410 dotable(getattr(extmodule, 'cmdtable', {}))
3411
3411
3412
3412
3413 colortable = {'qguard.negative': 'red',
3413 colortable = {'qguard.negative': 'red',
3414 'qguard.positive': 'yellow',
3414 'qguard.positive': 'yellow',
3415 'qguard.unguarded': 'green',
3415 'qguard.unguarded': 'green',
3416 'qseries.applied': 'blue bold underline',
3416 'qseries.applied': 'blue bold underline',
3417 'qseries.guarded': 'black bold',
3417 'qseries.guarded': 'black bold',
3418 'qseries.missing': 'red bold',
3418 'qseries.missing': 'red bold',
3419 'qseries.unapplied': 'black bold'}
3419 'qseries.unapplied': 'black bold'}
@@ -1,674 +1,674 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 from mercurial import extensions, patch, scmutil, phases
18 from mercurial import extensions, patch, scmutil, phases
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26
26
27 cmdtable = {}
27 cmdtable = {}
28 command = cmdutil.command(cmdtable)
28 command = cmdutil.command(cmdtable)
29
29
30 @command('rebase',
30 @command('rebase',
31 [('s', 'source', '',
31 [('s', 'source', '',
32 _('rebase from the specified changeset'), _('REV')),
32 _('rebase from the specified changeset'), _('REV')),
33 ('b', 'base', '',
33 ('b', 'base', '',
34 _('rebase from the base of the specified changeset '
34 _('rebase from the base of the specified changeset '
35 '(up to greatest common ancestor of base and dest)'),
35 '(up to greatest common ancestor of base and dest)'),
36 _('REV')),
36 _('REV')),
37 ('r', 'rev', [],
37 ('r', 'rev', [],
38 _('rebase these revisions'),
38 _('rebase these revisions'),
39 _('REV')),
39 _('REV')),
40 ('d', 'dest', '',
40 ('d', 'dest', '',
41 _('rebase onto the specified changeset'), _('REV')),
41 _('rebase onto the specified changeset'), _('REV')),
42 ('', 'collapse', False, _('collapse the rebased changesets')),
42 ('', 'collapse', False, _('collapse the rebased changesets')),
43 ('m', 'message', '',
43 ('m', 'message', '',
44 _('use text as collapse commit message'), _('TEXT')),
44 _('use text as collapse commit message'), _('TEXT')),
45 ('e', 'edit', False, _('invoke editor on commit messages')),
45 ('e', 'edit', False, _('invoke editor on commit messages')),
46 ('l', 'logfile', '',
46 ('l', 'logfile', '',
47 _('read collapse commit message from file'), _('FILE')),
47 _('read collapse commit message from file'), _('FILE')),
48 ('', 'keep', False, _('keep original changesets')),
48 ('', 'keep', False, _('keep original changesets')),
49 ('', 'keepbranches', False, _('keep original branch names')),
49 ('', 'keepbranches', False, _('keep original branch names')),
50 ('D', 'detach', False, _('force detaching of source from its original '
50 ('D', 'detach', False, _('force detaching of source from its original '
51 'branch')),
51 'branch')),
52 ('t', 'tool', '', _('specify merge tool')),
52 ('t', 'tool', '', _('specify merge tool')),
53 ('c', 'continue', False, _('continue an interrupted rebase')),
53 ('c', 'continue', False, _('continue an interrupted rebase')),
54 ('a', 'abort', False, _('abort an interrupted rebase'))] +
54 ('a', 'abort', False, _('abort an interrupted rebase'))] +
55 templateopts,
55 templateopts,
56 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
56 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
57 'hg rebase {-a|-c}'))
57 'hg rebase {-a|-c}'))
58 def rebase(ui, repo, **opts):
58 def rebase(ui, repo, **opts):
59 """move changeset (and descendants) to a different branch
59 """move changeset (and descendants) to a different branch
60
60
61 Rebase uses repeated merging to graft changesets from one part of
61 Rebase uses repeated merging to graft changesets from one part of
62 history (the source) onto another (the destination). This can be
62 history (the source) onto another (the destination). This can be
63 useful for linearizing *local* changes relative to a master
63 useful for linearizing *local* changes relative to a master
64 development tree.
64 development tree.
65
65
66 You should not rebase changesets that have already been shared
66 You should not rebase changesets that have already been shared
67 with others. Doing so will force everybody else to perform the
67 with others. Doing so will force everybody else to perform the
68 same rebase or they will end up with duplicated changesets after
68 same rebase or they will end up with duplicated changesets after
69 pulling in your rebased changesets.
69 pulling in your rebased changesets.
70
70
71 If you don't specify a destination changeset (``-d/--dest``),
71 If you don't specify a destination changeset (``-d/--dest``),
72 rebase uses the tipmost head of the current named branch as the
72 rebase uses the tipmost head of the current named branch as the
73 destination. (The destination changeset is not modified by
73 destination. (The destination changeset is not modified by
74 rebasing, but new changesets are added as its descendants.)
74 rebasing, but new changesets are added as its descendants.)
75
75
76 You can specify which changesets to rebase in two ways: as a
76 You can specify which changesets to rebase in two ways: as a
77 "source" changeset or as a "base" changeset. Both are shorthand
77 "source" changeset or as a "base" changeset. Both are shorthand
78 for a topologically related set of changesets (the "source
78 for a topologically related set of changesets (the "source
79 branch"). If you specify source (``-s/--source``), rebase will
79 branch"). If you specify source (``-s/--source``), rebase will
80 rebase that changeset and all of its descendants onto dest. If you
80 rebase that changeset and all of its descendants onto dest. If you
81 specify base (``-b/--base``), rebase will select ancestors of base
81 specify base (``-b/--base``), rebase will select ancestors of base
82 back to but not including the common ancestor with dest. Thus,
82 back to but not including the common ancestor with dest. Thus,
83 ``-b`` is less precise but more convenient than ``-s``: you can
83 ``-b`` is less precise but more convenient than ``-s``: you can
84 specify any changeset in the source branch, and rebase will select
84 specify any changeset in the source branch, and rebase will select
85 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
85 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
86 uses the parent of the working directory as the base.
86 uses the parent of the working directory as the base.
87
87
88 By default, rebase recreates the changesets in the source branch
88 By default, rebase recreates the changesets in the source branch
89 as descendants of dest and then destroys the originals. Use
89 as descendants of dest and then destroys the originals. Use
90 ``--keep`` to preserve the original source changesets. Some
90 ``--keep`` to preserve the original source changesets. Some
91 changesets in the source branch (e.g. merges from the destination
91 changesets in the source branch (e.g. merges from the destination
92 branch) may be dropped if they no longer contribute any change.
92 branch) may be dropped if they no longer contribute any change.
93
93
94 One result of the rules for selecting the destination changeset
94 One result of the rules for selecting the destination changeset
95 and source branch is that, unlike ``merge``, rebase will do
95 and source branch is that, unlike ``merge``, rebase will do
96 nothing if you are at the latest (tipmost) head of a named branch
96 nothing if you are at the latest (tipmost) head of a named branch
97 with two heads. You need to explicitly specify source and/or
97 with two heads. You need to explicitly specify source and/or
98 destination (or ``update`` to the other head, if it's the head of
98 destination (or ``update`` to the other head, if it's the head of
99 the intended source branch).
99 the intended source branch).
100
100
101 If a rebase is interrupted to manually resolve a merge, it can be
101 If a rebase is interrupted to manually resolve a merge, it can be
102 continued with --continue/-c or aborted with --abort/-a.
102 continued with --continue/-c or aborted with --abort/-a.
103
103
104 Returns 0 on success, 1 if nothing to rebase.
104 Returns 0 on success, 1 if nothing to rebase.
105 """
105 """
106 originalwd = target = None
106 originalwd = target = None
107 external = nullrev
107 external = nullrev
108 state = {}
108 state = {}
109 skipped = set()
109 skipped = set()
110 targetancestors = set()
110 targetancestors = set()
111
111
112 editor = None
112 editor = None
113 if opts.get('edit'):
113 if opts.get('edit'):
114 editor = cmdutil.commitforceeditor
114 editor = cmdutil.commitforceeditor
115
115
116 lock = wlock = None
116 lock = wlock = None
117 try:
117 try:
118 wlock = repo.wlock()
118 wlock = repo.wlock()
119 lock = repo.lock()
119 lock = repo.lock()
120
120
121 # Validate input and define rebasing points
121 # Validate input and define rebasing points
122 destf = opts.get('dest', None)
122 destf = opts.get('dest', None)
123 srcf = opts.get('source', None)
123 srcf = opts.get('source', None)
124 basef = opts.get('base', None)
124 basef = opts.get('base', None)
125 revf = opts.get('rev', [])
125 revf = opts.get('rev', [])
126 contf = opts.get('continue')
126 contf = opts.get('continue')
127 abortf = opts.get('abort')
127 abortf = opts.get('abort')
128 collapsef = opts.get('collapse', False)
128 collapsef = opts.get('collapse', False)
129 collapsemsg = cmdutil.logmessage(ui, opts)
129 collapsemsg = cmdutil.logmessage(ui, opts)
130 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
130 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
131 keepf = opts.get('keep', False)
131 keepf = opts.get('keep', False)
132 keepbranchesf = opts.get('keepbranches', False)
132 keepbranchesf = opts.get('keepbranches', False)
133 detachf = opts.get('detach', False)
133 detachf = opts.get('detach', False)
134 # keepopen is not meant for use on the command line, but by
134 # keepopen is not meant for use on the command line, but by
135 # other extensions
135 # other extensions
136 keepopen = opts.get('keepopen', False)
136 keepopen = opts.get('keepopen', False)
137
137
138 if collapsemsg and not collapsef:
138 if collapsemsg and not collapsef:
139 raise util.Abort(
139 raise util.Abort(
140 _('message can only be specified with collapse'))
140 _('message can only be specified with collapse'))
141
141
142 if contf or abortf:
142 if contf or abortf:
143 if contf and abortf:
143 if contf and abortf:
144 raise util.Abort(_('cannot use both abort and continue'))
144 raise util.Abort(_('cannot use both abort and continue'))
145 if collapsef:
145 if collapsef:
146 raise util.Abort(
146 raise util.Abort(
147 _('cannot use collapse with continue or abort'))
147 _('cannot use collapse with continue or abort'))
148 if detachf:
148 if detachf:
149 raise util.Abort(_('cannot use detach with continue or abort'))
149 raise util.Abort(_('cannot use detach with continue or abort'))
150 if srcf or basef or destf:
150 if srcf or basef or destf:
151 raise util.Abort(
151 raise util.Abort(
152 _('abort and continue do not allow specifying revisions'))
152 _('abort and continue do not allow specifying revisions'))
153 if opts.get('tool', False):
153 if opts.get('tool', False):
154 ui.warn(_('tool option will be ignored\n'))
154 ui.warn(_('tool option will be ignored\n'))
155
155
156 (originalwd, target, state, skipped, collapsef, keepf,
156 (originalwd, target, state, skipped, collapsef, keepf,
157 keepbranchesf, external) = restorestatus(repo)
157 keepbranchesf, external) = restorestatus(repo)
158 if abortf:
158 if abortf:
159 return abort(repo, originalwd, target, state)
159 return abort(repo, originalwd, target, state)
160 else:
160 else:
161 if srcf and basef:
161 if srcf and basef:
162 raise util.Abort(_('cannot specify both a '
162 raise util.Abort(_('cannot specify both a '
163 'source and a base'))
163 'source and a base'))
164 if revf and basef:
164 if revf and basef:
165 raise util.Abort(_('cannot specify both a '
165 raise util.Abort(_('cannot specify both a '
166 'revision and a base'))
166 'revision and a base'))
167 if revf and srcf:
167 if revf and srcf:
168 raise util.Abort(_('cannot specify both a '
168 raise util.Abort(_('cannot specify both a '
169 'revision and a source'))
169 'revision and a source'))
170 if detachf:
170 if detachf:
171 if not (srcf or revf):
171 if not (srcf or revf):
172 raise util.Abort(
172 raise util.Abort(
173 _('detach requires a revision to be specified'))
173 _('detach requires a revision to be specified'))
174 if basef:
174 if basef:
175 raise util.Abort(_('cannot specify a base with detach'))
175 raise util.Abort(_('cannot specify a base with detach'))
176
176
177 cmdutil.bailifchanged(repo)
177 cmdutil.bailifchanged(repo)
178
178
179 if not destf:
179 if not destf:
180 # Destination defaults to the latest revision in the
180 # Destination defaults to the latest revision in the
181 # current branch
181 # current branch
182 branch = repo[None].branch()
182 branch = repo[None].branch()
183 dest = repo[branch]
183 dest = repo[branch]
184 else:
184 else:
185 dest = repo[destf]
185 dest = repo[destf]
186
186
187 if revf:
187 if revf:
188 rebaseset = repo.revs('%lr', revf)
188 rebaseset = repo.revs('%lr', revf)
189 elif srcf:
189 elif srcf:
190 src = scmutil.revrange(repo, [srcf])
190 src = scmutil.revrange(repo, [srcf])
191 rebaseset = repo.revs('(%ld)::', src)
191 rebaseset = repo.revs('(%ld)::', src)
192 else:
192 else:
193 base = scmutil.revrange(repo, [basef or '.'])
193 base = scmutil.revrange(repo, [basef or '.'])
194 rebaseset = repo.revs(
194 rebaseset = repo.revs(
195 '(children(ancestor(%ld, %d)) and ::(%ld))::',
195 '(children(ancestor(%ld, %d)) and ::(%ld))::',
196 base, dest, base)
196 base, dest, base)
197
197
198 if rebaseset:
198 if rebaseset:
199 root = min(rebaseset)
199 root = min(rebaseset)
200 else:
200 else:
201 root = None
201 root = None
202
202
203 if not rebaseset:
203 if not rebaseset:
204 repo.ui.debug('base is ancestor of destination')
204 repo.ui.debug('base is ancestor of destination')
205 result = None
205 result = None
206 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
206 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
207 rebaseset, rebaseset)):
207 rebaseset, rebaseset)):
208 raise util.Abort(
208 raise util.Abort(
209 _("can't remove original changesets with"
209 _("can't remove original changesets with"
210 " unrebased descendants"),
210 " unrebased descendants"),
211 hint=_('use --keep to keep original changesets'))
211 hint=_('use --keep to keep original changesets'))
212 elif not keepf and not repo[root].mutable():
212 elif not keepf and not repo[root].mutable():
213 raise util.Abort(_("can't rebase immutable changeset %s")
213 raise util.Abort(_("can't rebase immutable changeset %s")
214 % repo[root],
214 % repo[root],
215 hint=_('see hg help phases for details'))
215 hint=_('see hg help phases for details'))
216 else:
216 else:
217 result = buildstate(repo, dest, rebaseset, detachf)
217 result = buildstate(repo, dest, rebaseset, detachf)
218
218
219 if not result:
219 if not result:
220 # Empty state built, nothing to rebase
220 # Empty state built, nothing to rebase
221 ui.status(_('nothing to rebase\n'))
221 ui.status(_('nothing to rebase\n'))
222 return 1
222 return 1
223 else:
223 else:
224 originalwd, target, state = result
224 originalwd, target, state = result
225 if collapsef:
225 if collapsef:
226 targetancestors = set(repo.changelog.ancestors(target))
226 targetancestors = set(repo.changelog.ancestors(target))
227 targetancestors.add(target)
227 targetancestors.add(target)
228 external = checkexternal(repo, state, targetancestors)
228 external = checkexternal(repo, state, targetancestors)
229
229
230 if keepbranchesf:
230 if keepbranchesf:
231 assert not extrafn, 'cannot use both keepbranches and extrafn'
231 assert not extrafn, 'cannot use both keepbranches and extrafn'
232 def extrafn(ctx, extra):
232 def extrafn(ctx, extra):
233 extra['branch'] = ctx.branch()
233 extra['branch'] = ctx.branch()
234 if collapsef:
234 if collapsef:
235 branches = set()
235 branches = set()
236 for rev in state:
236 for rev in state:
237 branches.add(repo[rev].branch())
237 branches.add(repo[rev].branch())
238 if len(branches) > 1:
238 if len(branches) > 1:
239 raise util.Abort(_('cannot collapse multiple named '
239 raise util.Abort(_('cannot collapse multiple named '
240 'branches'))
240 'branches'))
241
241
242
242
243 # Rebase
243 # Rebase
244 if not targetancestors:
244 if not targetancestors:
245 targetancestors = set(repo.changelog.ancestors(target))
245 targetancestors = set(repo.changelog.ancestors(target))
246 targetancestors.add(target)
246 targetancestors.add(target)
247
247
248 # Keep track of the current bookmarks in order to reset them later
248 # Keep track of the current bookmarks in order to reset them later
249 currentbookmarks = repo._bookmarks.copy()
249 currentbookmarks = repo._bookmarks.copy()
250
250
251 sortedstate = sorted(state)
251 sortedstate = sorted(state)
252 total = len(sortedstate)
252 total = len(sortedstate)
253 pos = 0
253 pos = 0
254 for rev in sortedstate:
254 for rev in sortedstate:
255 pos += 1
255 pos += 1
256 if state[rev] == -1:
256 if state[rev] == -1:
257 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
257 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
258 _('changesets'), total)
258 _('changesets'), total)
259 storestatus(repo, originalwd, target, state, collapsef, keepf,
259 storestatus(repo, originalwd, target, state, collapsef, keepf,
260 keepbranchesf, external)
260 keepbranchesf, external)
261 p1, p2 = defineparents(repo, rev, target, state,
261 p1, p2 = defineparents(repo, rev, target, state,
262 targetancestors)
262 targetancestors)
263 if len(repo.parents()) == 2:
263 if len(repo.parents()) == 2:
264 repo.ui.debug('resuming interrupted rebase\n')
264 repo.ui.debug('resuming interrupted rebase\n')
265 else:
265 else:
266 try:
266 try:
267 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
267 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
268 stats = rebasenode(repo, rev, p1, state)
268 stats = rebasenode(repo, rev, p1, state)
269 if stats and stats[3] > 0:
269 if stats and stats[3] > 0:
270 raise util.Abort(_('unresolved conflicts (see hg '
270 raise util.Abort(_('unresolved conflicts (see hg '
271 'resolve, then hg rebase --continue)'))
271 'resolve, then hg rebase --continue)'))
272 finally:
272 finally:
273 ui.setconfig('ui', 'forcemerge', '')
273 ui.setconfig('ui', 'forcemerge', '')
274 cmdutil.duplicatecopies(repo, rev, target)
274 cmdutil.duplicatecopies(repo, rev, target)
275 if not collapsef:
275 if not collapsef:
276 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
276 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
277 editor=editor)
277 editor=editor)
278 else:
278 else:
279 # Skip commit if we are collapsing
279 # Skip commit if we are collapsing
280 repo.dirstate.setparents(repo[p1].node())
280 repo.setparents(repo[p1].node())
281 newrev = None
281 newrev = None
282 # Update the state
282 # Update the state
283 if newrev is not None:
283 if newrev is not None:
284 state[rev] = repo[newrev].rev()
284 state[rev] = repo[newrev].rev()
285 else:
285 else:
286 if not collapsef:
286 if not collapsef:
287 ui.note(_('no changes, revision %d skipped\n') % rev)
287 ui.note(_('no changes, revision %d skipped\n') % rev)
288 ui.debug('next revision set to %s\n' % p1)
288 ui.debug('next revision set to %s\n' % p1)
289 skipped.add(rev)
289 skipped.add(rev)
290 state[rev] = p1
290 state[rev] = p1
291
291
292 ui.progress(_('rebasing'), None)
292 ui.progress(_('rebasing'), None)
293 ui.note(_('rebase merging completed\n'))
293 ui.note(_('rebase merging completed\n'))
294
294
295 if collapsef and not keepopen:
295 if collapsef and not keepopen:
296 p1, p2 = defineparents(repo, min(state), target,
296 p1, p2 = defineparents(repo, min(state), target,
297 state, targetancestors)
297 state, targetancestors)
298 if collapsemsg:
298 if collapsemsg:
299 commitmsg = collapsemsg
299 commitmsg = collapsemsg
300 else:
300 else:
301 commitmsg = 'Collapsed revision'
301 commitmsg = 'Collapsed revision'
302 for rebased in state:
302 for rebased in state:
303 if rebased not in skipped and state[rebased] != nullmerge:
303 if rebased not in skipped and state[rebased] != nullmerge:
304 commitmsg += '\n* %s' % repo[rebased].description()
304 commitmsg += '\n* %s' % repo[rebased].description()
305 commitmsg = ui.edit(commitmsg, repo.ui.username())
305 commitmsg = ui.edit(commitmsg, repo.ui.username())
306 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
306 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
307 extrafn=extrafn, editor=editor)
307 extrafn=extrafn, editor=editor)
308
308
309 if 'qtip' in repo.tags():
309 if 'qtip' in repo.tags():
310 updatemq(repo, state, skipped, **opts)
310 updatemq(repo, state, skipped, **opts)
311
311
312 if currentbookmarks:
312 if currentbookmarks:
313 # Nodeids are needed to reset bookmarks
313 # Nodeids are needed to reset bookmarks
314 nstate = {}
314 nstate = {}
315 for k, v in state.iteritems():
315 for k, v in state.iteritems():
316 if v != nullmerge:
316 if v != nullmerge:
317 nstate[repo[k].node()] = repo[v].node()
317 nstate[repo[k].node()] = repo[v].node()
318
318
319 if not keepf:
319 if not keepf:
320 # Remove no more useful revisions
320 # Remove no more useful revisions
321 rebased = [rev for rev in state if state[rev] != nullmerge]
321 rebased = [rev for rev in state if state[rev] != nullmerge]
322 if rebased:
322 if rebased:
323 if set(repo.changelog.descendants(min(rebased))) - set(state):
323 if set(repo.changelog.descendants(min(rebased))) - set(state):
324 ui.warn(_("warning: new changesets detected "
324 ui.warn(_("warning: new changesets detected "
325 "on source branch, not stripping\n"))
325 "on source branch, not stripping\n"))
326 else:
326 else:
327 # backup the old csets by default
327 # backup the old csets by default
328 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
328 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
329
329
330 if currentbookmarks:
330 if currentbookmarks:
331 updatebookmarks(repo, nstate, currentbookmarks, **opts)
331 updatebookmarks(repo, nstate, currentbookmarks, **opts)
332
332
333 clearstatus(repo)
333 clearstatus(repo)
334 ui.note(_("rebase completed\n"))
334 ui.note(_("rebase completed\n"))
335 if os.path.exists(repo.sjoin('undo')):
335 if os.path.exists(repo.sjoin('undo')):
336 util.unlinkpath(repo.sjoin('undo'))
336 util.unlinkpath(repo.sjoin('undo'))
337 if skipped:
337 if skipped:
338 ui.note(_("%d revisions have been skipped\n") % len(skipped))
338 ui.note(_("%d revisions have been skipped\n") % len(skipped))
339 finally:
339 finally:
340 release(lock, wlock)
340 release(lock, wlock)
341
341
342 def checkexternal(repo, state, targetancestors):
342 def checkexternal(repo, state, targetancestors):
343 """Check whether one or more external revisions need to be taken in
343 """Check whether one or more external revisions need to be taken in
344 consideration. In the latter case, abort.
344 consideration. In the latter case, abort.
345 """
345 """
346 external = nullrev
346 external = nullrev
347 source = min(state)
347 source = min(state)
348 for rev in state:
348 for rev in state:
349 if rev == source:
349 if rev == source:
350 continue
350 continue
351 # Check externals and fail if there are more than one
351 # Check externals and fail if there are more than one
352 for p in repo[rev].parents():
352 for p in repo[rev].parents():
353 if (p.rev() not in state
353 if (p.rev() not in state
354 and p.rev() not in targetancestors):
354 and p.rev() not in targetancestors):
355 if external != nullrev:
355 if external != nullrev:
356 raise util.Abort(_('unable to collapse, there is more '
356 raise util.Abort(_('unable to collapse, there is more '
357 'than one external parent'))
357 'than one external parent'))
358 external = p.rev()
358 external = p.rev()
359 return external
359 return external
360
360
361 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
361 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
362 'Commit the changes and store useful information in extra'
362 'Commit the changes and store useful information in extra'
363 try:
363 try:
364 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
364 repo.setparents(repo[p1].node(), repo[p2].node())
365 ctx = repo[rev]
365 ctx = repo[rev]
366 if commitmsg is None:
366 if commitmsg is None:
367 commitmsg = ctx.description()
367 commitmsg = ctx.description()
368 extra = {'rebase_source': ctx.hex()}
368 extra = {'rebase_source': ctx.hex()}
369 if extrafn:
369 if extrafn:
370 extrafn(ctx, extra)
370 extrafn(ctx, extra)
371 # Commit might fail if unresolved files exist
371 # Commit might fail if unresolved files exist
372 newrev = repo.commit(text=commitmsg, user=ctx.user(),
372 newrev = repo.commit(text=commitmsg, user=ctx.user(),
373 date=ctx.date(), extra=extra, editor=editor)
373 date=ctx.date(), extra=extra, editor=editor)
374 repo.dirstate.setbranch(repo[newrev].branch())
374 repo.dirstate.setbranch(repo[newrev].branch())
375 targetphase = max(ctx.phase(), phases.draft)
375 targetphase = max(ctx.phase(), phases.draft)
376 # retractboundary doesn't overwrite upper phase inherited from parent
376 # retractboundary doesn't overwrite upper phase inherited from parent
377 newnode = repo[newrev].node()
377 newnode = repo[newrev].node()
378 if newnode:
378 if newnode:
379 phases.retractboundary(repo, targetphase, [newnode])
379 phases.retractboundary(repo, targetphase, [newnode])
380 return newrev
380 return newrev
381 except util.Abort:
381 except util.Abort:
382 # Invalidate the previous setparents
382 # Invalidate the previous setparents
383 repo.dirstate.invalidate()
383 repo.dirstate.invalidate()
384 raise
384 raise
385
385
386 def rebasenode(repo, rev, p1, state):
386 def rebasenode(repo, rev, p1, state):
387 'Rebase a single revision'
387 'Rebase a single revision'
388 # Merge phase
388 # Merge phase
389 # Update to target and merge it with local
389 # Update to target and merge it with local
390 if repo['.'].rev() != repo[p1].rev():
390 if repo['.'].rev() != repo[p1].rev():
391 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
391 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
392 merge.update(repo, p1, False, True, False)
392 merge.update(repo, p1, False, True, False)
393 else:
393 else:
394 repo.ui.debug(" already in target\n")
394 repo.ui.debug(" already in target\n")
395 repo.dirstate.write()
395 repo.dirstate.write()
396 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
396 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
397 base = None
397 base = None
398 if repo[rev].rev() != repo[min(state)].rev():
398 if repo[rev].rev() != repo[min(state)].rev():
399 base = repo[rev].p1().node()
399 base = repo[rev].p1().node()
400 return merge.update(repo, rev, True, True, False, base)
400 return merge.update(repo, rev, True, True, False, base)
401
401
402 def defineparents(repo, rev, target, state, targetancestors):
402 def defineparents(repo, rev, target, state, targetancestors):
403 'Return the new parent relationship of the revision that will be rebased'
403 'Return the new parent relationship of the revision that will be rebased'
404 parents = repo[rev].parents()
404 parents = repo[rev].parents()
405 p1 = p2 = nullrev
405 p1 = p2 = nullrev
406
406
407 P1n = parents[0].rev()
407 P1n = parents[0].rev()
408 if P1n in targetancestors:
408 if P1n in targetancestors:
409 p1 = target
409 p1 = target
410 elif P1n in state:
410 elif P1n in state:
411 if state[P1n] == nullmerge:
411 if state[P1n] == nullmerge:
412 p1 = target
412 p1 = target
413 else:
413 else:
414 p1 = state[P1n]
414 p1 = state[P1n]
415 else: # P1n external
415 else: # P1n external
416 p1 = target
416 p1 = target
417 p2 = P1n
417 p2 = P1n
418
418
419 if len(parents) == 2 and parents[1].rev() not in targetancestors:
419 if len(parents) == 2 and parents[1].rev() not in targetancestors:
420 P2n = parents[1].rev()
420 P2n = parents[1].rev()
421 # interesting second parent
421 # interesting second parent
422 if P2n in state:
422 if P2n in state:
423 if p1 == target: # P1n in targetancestors or external
423 if p1 == target: # P1n in targetancestors or external
424 p1 = state[P2n]
424 p1 = state[P2n]
425 else:
425 else:
426 p2 = state[P2n]
426 p2 = state[P2n]
427 else: # P2n external
427 else: # P2n external
428 if p2 != nullrev: # P1n external too => rev is a merged revision
428 if p2 != nullrev: # P1n external too => rev is a merged revision
429 raise util.Abort(_('cannot use revision %d as base, result '
429 raise util.Abort(_('cannot use revision %d as base, result '
430 'would have 3 parents') % rev)
430 'would have 3 parents') % rev)
431 p2 = P2n
431 p2 = P2n
432 repo.ui.debug(" future parents are %d and %d\n" %
432 repo.ui.debug(" future parents are %d and %d\n" %
433 (repo[p1].rev(), repo[p2].rev()))
433 (repo[p1].rev(), repo[p2].rev()))
434 return p1, p2
434 return p1, p2
435
435
436 def isagitpatch(repo, patchname):
436 def isagitpatch(repo, patchname):
437 'Return true if the given patch is in git format'
437 'Return true if the given patch is in git format'
438 mqpatch = os.path.join(repo.mq.path, patchname)
438 mqpatch = os.path.join(repo.mq.path, patchname)
439 for line in patch.linereader(file(mqpatch, 'rb')):
439 for line in patch.linereader(file(mqpatch, 'rb')):
440 if line.startswith('diff --git'):
440 if line.startswith('diff --git'):
441 return True
441 return True
442 return False
442 return False
443
443
444 def updatemq(repo, state, skipped, **opts):
444 def updatemq(repo, state, skipped, **opts):
445 'Update rebased mq patches - finalize and then import them'
445 'Update rebased mq patches - finalize and then import them'
446 mqrebase = {}
446 mqrebase = {}
447 mq = repo.mq
447 mq = repo.mq
448 original_series = mq.fullseries[:]
448 original_series = mq.fullseries[:]
449 skippedpatches = set()
449 skippedpatches = set()
450
450
451 for p in mq.applied:
451 for p in mq.applied:
452 rev = repo[p.node].rev()
452 rev = repo[p.node].rev()
453 if rev in state:
453 if rev in state:
454 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
454 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
455 (rev, p.name))
455 (rev, p.name))
456 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
456 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
457 else:
457 else:
458 # Applied but not rebased, not sure this should happen
458 # Applied but not rebased, not sure this should happen
459 skippedpatches.add(p.name)
459 skippedpatches.add(p.name)
460
460
461 if mqrebase:
461 if mqrebase:
462 mq.finish(repo, mqrebase.keys())
462 mq.finish(repo, mqrebase.keys())
463
463
464 # We must start import from the newest revision
464 # We must start import from the newest revision
465 for rev in sorted(mqrebase, reverse=True):
465 for rev in sorted(mqrebase, reverse=True):
466 if rev not in skipped:
466 if rev not in skipped:
467 name, isgit = mqrebase[rev]
467 name, isgit = mqrebase[rev]
468 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
468 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
469 mq.qimport(repo, (), patchname=name, git=isgit,
469 mq.qimport(repo, (), patchname=name, git=isgit,
470 rev=[str(state[rev])])
470 rev=[str(state[rev])])
471 else:
471 else:
472 # Rebased and skipped
472 # Rebased and skipped
473 skippedpatches.add(mqrebase[rev][0])
473 skippedpatches.add(mqrebase[rev][0])
474
474
475 # Patches were either applied and rebased and imported in
475 # Patches were either applied and rebased and imported in
476 # order, applied and removed or unapplied. Discard the removed
476 # order, applied and removed or unapplied. Discard the removed
477 # ones while preserving the original series order and guards.
477 # ones while preserving the original series order and guards.
478 newseries = [s for s in original_series
478 newseries = [s for s in original_series
479 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
479 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
480 mq.fullseries[:] = newseries
480 mq.fullseries[:] = newseries
481 mq.seriesdirty = True
481 mq.seriesdirty = True
482 mq.savedirty()
482 mq.savedirty()
483
483
484 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
484 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
485 'Move bookmarks to their correct changesets'
485 'Move bookmarks to their correct changesets'
486 current = repo._bookmarkcurrent
486 current = repo._bookmarkcurrent
487 for k, v in originalbookmarks.iteritems():
487 for k, v in originalbookmarks.iteritems():
488 if v in nstate:
488 if v in nstate:
489 if nstate[v] != nullmerge:
489 if nstate[v] != nullmerge:
490 # reset the pointer if the bookmark was moved incorrectly
490 # reset the pointer if the bookmark was moved incorrectly
491 if k != current:
491 if k != current:
492 repo._bookmarks[k] = nstate[v]
492 repo._bookmarks[k] = nstate[v]
493
493
494 bookmarks.write(repo)
494 bookmarks.write(repo)
495
495
496 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
496 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
497 external):
497 external):
498 'Store the current status to allow recovery'
498 'Store the current status to allow recovery'
499 f = repo.opener("rebasestate", "w")
499 f = repo.opener("rebasestate", "w")
500 f.write(repo[originalwd].hex() + '\n')
500 f.write(repo[originalwd].hex() + '\n')
501 f.write(repo[target].hex() + '\n')
501 f.write(repo[target].hex() + '\n')
502 f.write(repo[external].hex() + '\n')
502 f.write(repo[external].hex() + '\n')
503 f.write('%d\n' % int(collapse))
503 f.write('%d\n' % int(collapse))
504 f.write('%d\n' % int(keep))
504 f.write('%d\n' % int(keep))
505 f.write('%d\n' % int(keepbranches))
505 f.write('%d\n' % int(keepbranches))
506 for d, v in state.iteritems():
506 for d, v in state.iteritems():
507 oldrev = repo[d].hex()
507 oldrev = repo[d].hex()
508 if v != nullmerge:
508 if v != nullmerge:
509 newrev = repo[v].hex()
509 newrev = repo[v].hex()
510 else:
510 else:
511 newrev = v
511 newrev = v
512 f.write("%s:%s\n" % (oldrev, newrev))
512 f.write("%s:%s\n" % (oldrev, newrev))
513 f.close()
513 f.close()
514 repo.ui.debug('rebase status stored\n')
514 repo.ui.debug('rebase status stored\n')
515
515
516 def clearstatus(repo):
516 def clearstatus(repo):
517 'Remove the status files'
517 'Remove the status files'
518 if os.path.exists(repo.join("rebasestate")):
518 if os.path.exists(repo.join("rebasestate")):
519 util.unlinkpath(repo.join("rebasestate"))
519 util.unlinkpath(repo.join("rebasestate"))
520
520
521 def restorestatus(repo):
521 def restorestatus(repo):
522 'Restore a previously stored status'
522 'Restore a previously stored status'
523 try:
523 try:
524 target = None
524 target = None
525 collapse = False
525 collapse = False
526 external = nullrev
526 external = nullrev
527 state = {}
527 state = {}
528 f = repo.opener("rebasestate")
528 f = repo.opener("rebasestate")
529 for i, l in enumerate(f.read().splitlines()):
529 for i, l in enumerate(f.read().splitlines()):
530 if i == 0:
530 if i == 0:
531 originalwd = repo[l].rev()
531 originalwd = repo[l].rev()
532 elif i == 1:
532 elif i == 1:
533 target = repo[l].rev()
533 target = repo[l].rev()
534 elif i == 2:
534 elif i == 2:
535 external = repo[l].rev()
535 external = repo[l].rev()
536 elif i == 3:
536 elif i == 3:
537 collapse = bool(int(l))
537 collapse = bool(int(l))
538 elif i == 4:
538 elif i == 4:
539 keep = bool(int(l))
539 keep = bool(int(l))
540 elif i == 5:
540 elif i == 5:
541 keepbranches = bool(int(l))
541 keepbranches = bool(int(l))
542 else:
542 else:
543 oldrev, newrev = l.split(':')
543 oldrev, newrev = l.split(':')
544 if newrev != str(nullmerge):
544 if newrev != str(nullmerge):
545 state[repo[oldrev].rev()] = repo[newrev].rev()
545 state[repo[oldrev].rev()] = repo[newrev].rev()
546 else:
546 else:
547 state[repo[oldrev].rev()] = int(newrev)
547 state[repo[oldrev].rev()] = int(newrev)
548 skipped = set()
548 skipped = set()
549 # recompute the set of skipped revs
549 # recompute the set of skipped revs
550 if not collapse:
550 if not collapse:
551 seen = set([target])
551 seen = set([target])
552 for old, new in sorted(state.items()):
552 for old, new in sorted(state.items()):
553 if new != nullrev and new in seen:
553 if new != nullrev and new in seen:
554 skipped.add(old)
554 skipped.add(old)
555 seen.add(new)
555 seen.add(new)
556 repo.ui.debug('computed skipped revs: %s\n' % skipped)
556 repo.ui.debug('computed skipped revs: %s\n' % skipped)
557 repo.ui.debug('rebase status resumed\n')
557 repo.ui.debug('rebase status resumed\n')
558 return (originalwd, target, state, skipped,
558 return (originalwd, target, state, skipped,
559 collapse, keep, keepbranches, external)
559 collapse, keep, keepbranches, external)
560 except IOError, err:
560 except IOError, err:
561 if err.errno != errno.ENOENT:
561 if err.errno != errno.ENOENT:
562 raise
562 raise
563 raise util.Abort(_('no rebase in progress'))
563 raise util.Abort(_('no rebase in progress'))
564
564
565 def abort(repo, originalwd, target, state):
565 def abort(repo, originalwd, target, state):
566 'Restore the repository to its original state'
566 'Restore the repository to its original state'
567 dstates = [s for s in state.values() if s != nullrev]
567 dstates = [s for s in state.values() if s != nullrev]
568 if [d for d in dstates if not repo[d].mutable()]:
568 if [d for d in dstates if not repo[d].mutable()]:
569 repo.ui.warn(_("warning: immutable rebased changeset detected, "
569 repo.ui.warn(_("warning: immutable rebased changeset detected, "
570 "can't abort\n"))
570 "can't abort\n"))
571 return -1
571 return -1
572
572
573 descendants = set()
573 descendants = set()
574 if dstates:
574 if dstates:
575 descendants = set(repo.changelog.descendants(*dstates))
575 descendants = set(repo.changelog.descendants(*dstates))
576 if descendants - set(dstates):
576 if descendants - set(dstates):
577 repo.ui.warn(_("warning: new changesets detected on target branch, "
577 repo.ui.warn(_("warning: new changesets detected on target branch, "
578 "can't abort\n"))
578 "can't abort\n"))
579 return -1
579 return -1
580 else:
580 else:
581 # Strip from the first rebased revision
581 # Strip from the first rebased revision
582 merge.update(repo, repo[originalwd].rev(), False, True, False)
582 merge.update(repo, repo[originalwd].rev(), False, True, False)
583 rebased = filter(lambda x: x > -1 and x != target, state.values())
583 rebased = filter(lambda x: x > -1 and x != target, state.values())
584 if rebased:
584 if rebased:
585 strippoint = min(rebased)
585 strippoint = min(rebased)
586 # no backup of rebased cset versions needed
586 # no backup of rebased cset versions needed
587 repair.strip(repo.ui, repo, repo[strippoint].node())
587 repair.strip(repo.ui, repo, repo[strippoint].node())
588 clearstatus(repo)
588 clearstatus(repo)
589 repo.ui.warn(_('rebase aborted\n'))
589 repo.ui.warn(_('rebase aborted\n'))
590 return 0
590 return 0
591
591
592 def buildstate(repo, dest, rebaseset, detach):
592 def buildstate(repo, dest, rebaseset, detach):
593 '''Define which revisions are going to be rebased and where
593 '''Define which revisions are going to be rebased and where
594
594
595 repo: repo
595 repo: repo
596 dest: context
596 dest: context
597 rebaseset: set of rev
597 rebaseset: set of rev
598 detach: boolean'''
598 detach: boolean'''
599
599
600 # This check isn't strictly necessary, since mq detects commits over an
600 # This check isn't strictly necessary, since mq detects commits over an
601 # applied patch. But it prevents messing up the working directory when
601 # applied patch. But it prevents messing up the working directory when
602 # a partially completed rebase is blocked by mq.
602 # a partially completed rebase is blocked by mq.
603 if 'qtip' in repo.tags() and (dest.node() in
603 if 'qtip' in repo.tags() and (dest.node() in
604 [s.node for s in repo.mq.applied]):
604 [s.node for s in repo.mq.applied]):
605 raise util.Abort(_('cannot rebase onto an applied mq patch'))
605 raise util.Abort(_('cannot rebase onto an applied mq patch'))
606
606
607 detachset = set()
607 detachset = set()
608 roots = list(repo.set('roots(%ld)', rebaseset))
608 roots = list(repo.set('roots(%ld)', rebaseset))
609 if not roots:
609 if not roots:
610 raise util.Abort(_('no matching revisions'))
610 raise util.Abort(_('no matching revisions'))
611 if len(roots) > 1:
611 if len(roots) > 1:
612 raise util.Abort(_("can't rebase multiple roots"))
612 raise util.Abort(_("can't rebase multiple roots"))
613 root = roots[0]
613 root = roots[0]
614
614
615 commonbase = root.ancestor(dest)
615 commonbase = root.ancestor(dest)
616 if commonbase == root:
616 if commonbase == root:
617 raise util.Abort(_('source is ancestor of destination'))
617 raise util.Abort(_('source is ancestor of destination'))
618 if commonbase == dest:
618 if commonbase == dest:
619 samebranch = root.branch() == dest.branch()
619 samebranch = root.branch() == dest.branch()
620 if samebranch and root in dest.children():
620 if samebranch and root in dest.children():
621 repo.ui.debug('source is a child of destination')
621 repo.ui.debug('source is a child of destination')
622 return None
622 return None
623 # rebase on ancestor, force detach
623 # rebase on ancestor, force detach
624 detach = True
624 detach = True
625 if detach:
625 if detach:
626 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
626 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
627
627
628 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
628 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
629 state = dict.fromkeys(rebaseset, nullrev)
629 state = dict.fromkeys(rebaseset, nullrev)
630 state.update(dict.fromkeys(detachset, nullmerge))
630 state.update(dict.fromkeys(detachset, nullmerge))
631 return repo['.'].rev(), dest.rev(), state
631 return repo['.'].rev(), dest.rev(), state
632
632
633 def pullrebase(orig, ui, repo, *args, **opts):
633 def pullrebase(orig, ui, repo, *args, **opts):
634 'Call rebase after pull if the latter has been invoked with --rebase'
634 'Call rebase after pull if the latter has been invoked with --rebase'
635 if opts.get('rebase'):
635 if opts.get('rebase'):
636 if opts.get('update'):
636 if opts.get('update'):
637 del opts['update']
637 del opts['update']
638 ui.debug('--update and --rebase are not compatible, ignoring '
638 ui.debug('--update and --rebase are not compatible, ignoring '
639 'the update flag\n')
639 'the update flag\n')
640
640
641 movemarkfrom = repo['.'].node()
641 movemarkfrom = repo['.'].node()
642 cmdutil.bailifchanged(repo)
642 cmdutil.bailifchanged(repo)
643 revsprepull = len(repo)
643 revsprepull = len(repo)
644 origpostincoming = commands.postincoming
644 origpostincoming = commands.postincoming
645 def _dummy(*args, **kwargs):
645 def _dummy(*args, **kwargs):
646 pass
646 pass
647 commands.postincoming = _dummy
647 commands.postincoming = _dummy
648 try:
648 try:
649 orig(ui, repo, *args, **opts)
649 orig(ui, repo, *args, **opts)
650 finally:
650 finally:
651 commands.postincoming = origpostincoming
651 commands.postincoming = origpostincoming
652 revspostpull = len(repo)
652 revspostpull = len(repo)
653 if revspostpull > revsprepull:
653 if revspostpull > revsprepull:
654 rebase(ui, repo, **opts)
654 rebase(ui, repo, **opts)
655 branch = repo[None].branch()
655 branch = repo[None].branch()
656 dest = repo[branch].rev()
656 dest = repo[branch].rev()
657 if dest != repo['.'].rev():
657 if dest != repo['.'].rev():
658 # there was nothing to rebase we force an update
658 # there was nothing to rebase we force an update
659 hg.update(repo, dest)
659 hg.update(repo, dest)
660 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
660 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
661 ui.status(_("updating bookmark %s\n")
661 ui.status(_("updating bookmark %s\n")
662 % repo._bookmarkcurrent)
662 % repo._bookmarkcurrent)
663 else:
663 else:
664 if opts.get('tool'):
664 if opts.get('tool'):
665 raise util.Abort(_('--tool can only be used with --rebase'))
665 raise util.Abort(_('--tool can only be used with --rebase'))
666 orig(ui, repo, *args, **opts)
666 orig(ui, repo, *args, **opts)
667
667
668 def uisetup(ui):
668 def uisetup(ui):
669 'Replace pull with a decorator to provide --rebase option'
669 'Replace pull with a decorator to provide --rebase option'
670 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
670 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
671 entry[1].append(('', 'rebase', None,
671 entry[1].append(('', 'rebase', None,
672 _("rebase working directory to branch head")))
672 _("rebase working directory to branch head")))
673 entry[1].append(('t', 'tool', '',
673 entry[1].append(('t', 'tool', '',
674 _("specify merge tool for rebase")))
674 _("specify merge tool for rebase")))
@@ -1,679 +1,679 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to transplant changesets from another branch
8 '''command to transplant changesets from another branch
9
9
10 This extension allows you to transplant patches from another branch.
10 This extension allows you to transplant patches from another branch.
11
11
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 map from a changeset hash to its hash in the source repository.
13 map from a changeset hash to its hash in the source repository.
14 '''
14 '''
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 import os, tempfile
17 import os, tempfile
18 from mercurial.node import short
18 from mercurial.node import short
19 from mercurial import bundlerepo, hg, merge, match
19 from mercurial import bundlerepo, hg, merge, match
20 from mercurial import patch, revlog, scmutil, util, error, cmdutil
20 from mercurial import patch, revlog, scmutil, util, error, cmdutil
21 from mercurial import revset, templatekw
21 from mercurial import revset, templatekw
22
22
23 class TransplantError(error.Abort):
23 class TransplantError(error.Abort):
24 pass
24 pass
25
25
26 cmdtable = {}
26 cmdtable = {}
27 command = cmdutil.command(cmdtable)
27 command = cmdutil.command(cmdtable)
28
28
29 class transplantentry(object):
29 class transplantentry(object):
30 def __init__(self, lnode, rnode):
30 def __init__(self, lnode, rnode):
31 self.lnode = lnode
31 self.lnode = lnode
32 self.rnode = rnode
32 self.rnode = rnode
33
33
34 class transplants(object):
34 class transplants(object):
35 def __init__(self, path=None, transplantfile=None, opener=None):
35 def __init__(self, path=None, transplantfile=None, opener=None):
36 self.path = path
36 self.path = path
37 self.transplantfile = transplantfile
37 self.transplantfile = transplantfile
38 self.opener = opener
38 self.opener = opener
39
39
40 if not opener:
40 if not opener:
41 self.opener = scmutil.opener(self.path)
41 self.opener = scmutil.opener(self.path)
42 self.transplants = {}
42 self.transplants = {}
43 self.dirty = False
43 self.dirty = False
44 self.read()
44 self.read()
45
45
46 def read(self):
46 def read(self):
47 abspath = os.path.join(self.path, self.transplantfile)
47 abspath = os.path.join(self.path, self.transplantfile)
48 if self.transplantfile and os.path.exists(abspath):
48 if self.transplantfile and os.path.exists(abspath):
49 for line in self.opener.read(self.transplantfile).splitlines():
49 for line in self.opener.read(self.transplantfile).splitlines():
50 lnode, rnode = map(revlog.bin, line.split(':'))
50 lnode, rnode = map(revlog.bin, line.split(':'))
51 list = self.transplants.setdefault(rnode, [])
51 list = self.transplants.setdefault(rnode, [])
52 list.append(transplantentry(lnode, rnode))
52 list.append(transplantentry(lnode, rnode))
53
53
54 def write(self):
54 def write(self):
55 if self.dirty and self.transplantfile:
55 if self.dirty and self.transplantfile:
56 if not os.path.isdir(self.path):
56 if not os.path.isdir(self.path):
57 os.mkdir(self.path)
57 os.mkdir(self.path)
58 fp = self.opener(self.transplantfile, 'w')
58 fp = self.opener(self.transplantfile, 'w')
59 for list in self.transplants.itervalues():
59 for list in self.transplants.itervalues():
60 for t in list:
60 for t in list:
61 l, r = map(revlog.hex, (t.lnode, t.rnode))
61 l, r = map(revlog.hex, (t.lnode, t.rnode))
62 fp.write(l + ':' + r + '\n')
62 fp.write(l + ':' + r + '\n')
63 fp.close()
63 fp.close()
64 self.dirty = False
64 self.dirty = False
65
65
66 def get(self, rnode):
66 def get(self, rnode):
67 return self.transplants.get(rnode) or []
67 return self.transplants.get(rnode) or []
68
68
69 def set(self, lnode, rnode):
69 def set(self, lnode, rnode):
70 list = self.transplants.setdefault(rnode, [])
70 list = self.transplants.setdefault(rnode, [])
71 list.append(transplantentry(lnode, rnode))
71 list.append(transplantentry(lnode, rnode))
72 self.dirty = True
72 self.dirty = True
73
73
74 def remove(self, transplant):
74 def remove(self, transplant):
75 list = self.transplants.get(transplant.rnode)
75 list = self.transplants.get(transplant.rnode)
76 if list:
76 if list:
77 del list[list.index(transplant)]
77 del list[list.index(transplant)]
78 self.dirty = True
78 self.dirty = True
79
79
80 class transplanter(object):
80 class transplanter(object):
81 def __init__(self, ui, repo):
81 def __init__(self, ui, repo):
82 self.ui = ui
82 self.ui = ui
83 self.path = repo.join('transplant')
83 self.path = repo.join('transplant')
84 self.opener = scmutil.opener(self.path)
84 self.opener = scmutil.opener(self.path)
85 self.transplants = transplants(self.path, 'transplants',
85 self.transplants = transplants(self.path, 'transplants',
86 opener=self.opener)
86 opener=self.opener)
87 self.editor = None
87 self.editor = None
88
88
89 def applied(self, repo, node, parent):
89 def applied(self, repo, node, parent):
90 '''returns True if a node is already an ancestor of parent
90 '''returns True if a node is already an ancestor of parent
91 or has already been transplanted'''
91 or has already been transplanted'''
92 if hasnode(repo, node):
92 if hasnode(repo, node):
93 if node in repo.changelog.reachable(parent, stop=node):
93 if node in repo.changelog.reachable(parent, stop=node):
94 return True
94 return True
95 for t in self.transplants.get(node):
95 for t in self.transplants.get(node):
96 # it might have been stripped
96 # it might have been stripped
97 if not hasnode(repo, t.lnode):
97 if not hasnode(repo, t.lnode):
98 self.transplants.remove(t)
98 self.transplants.remove(t)
99 return False
99 return False
100 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
100 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
101 return True
101 return True
102 return False
102 return False
103
103
104 def apply(self, repo, source, revmap, merges, opts={}):
104 def apply(self, repo, source, revmap, merges, opts={}):
105 '''apply the revisions in revmap one by one in revision order'''
105 '''apply the revisions in revmap one by one in revision order'''
106 revs = sorted(revmap)
106 revs = sorted(revmap)
107 p1, p2 = repo.dirstate.parents()
107 p1, p2 = repo.dirstate.parents()
108 pulls = []
108 pulls = []
109 diffopts = patch.diffopts(self.ui, opts)
109 diffopts = patch.diffopts(self.ui, opts)
110 diffopts.git = True
110 diffopts.git = True
111
111
112 lock = wlock = tr = None
112 lock = wlock = tr = None
113 try:
113 try:
114 wlock = repo.wlock()
114 wlock = repo.wlock()
115 lock = repo.lock()
115 lock = repo.lock()
116 tr = repo.transaction('transplant')
116 tr = repo.transaction('transplant')
117 for rev in revs:
117 for rev in revs:
118 node = revmap[rev]
118 node = revmap[rev]
119 revstr = '%s:%s' % (rev, short(node))
119 revstr = '%s:%s' % (rev, short(node))
120
120
121 if self.applied(repo, node, p1):
121 if self.applied(repo, node, p1):
122 self.ui.warn(_('skipping already applied revision %s\n') %
122 self.ui.warn(_('skipping already applied revision %s\n') %
123 revstr)
123 revstr)
124 continue
124 continue
125
125
126 parents = source.changelog.parents(node)
126 parents = source.changelog.parents(node)
127 if not opts.get('filter'):
127 if not opts.get('filter'):
128 # If the changeset parent is the same as the
128 # If the changeset parent is the same as the
129 # wdir's parent, just pull it.
129 # wdir's parent, just pull it.
130 if parents[0] == p1:
130 if parents[0] == p1:
131 pulls.append(node)
131 pulls.append(node)
132 p1 = node
132 p1 = node
133 continue
133 continue
134 if pulls:
134 if pulls:
135 if source != repo:
135 if source != repo:
136 repo.pull(source, heads=pulls)
136 repo.pull(source, heads=pulls)
137 merge.update(repo, pulls[-1], False, False, None)
137 merge.update(repo, pulls[-1], False, False, None)
138 p1, p2 = repo.dirstate.parents()
138 p1, p2 = repo.dirstate.parents()
139 pulls = []
139 pulls = []
140
140
141 domerge = False
141 domerge = False
142 if node in merges:
142 if node in merges:
143 # pulling all the merge revs at once would mean we
143 # pulling all the merge revs at once would mean we
144 # couldn't transplant after the latest even if
144 # couldn't transplant after the latest even if
145 # transplants before them fail.
145 # transplants before them fail.
146 domerge = True
146 domerge = True
147 if not hasnode(repo, node):
147 if not hasnode(repo, node):
148 repo.pull(source, heads=[node])
148 repo.pull(source, heads=[node])
149
149
150 skipmerge = False
150 skipmerge = False
151 if parents[1] != revlog.nullid:
151 if parents[1] != revlog.nullid:
152 if not opts.get('parent'):
152 if not opts.get('parent'):
153 self.ui.note(_('skipping merge changeset %s:%s\n')
153 self.ui.note(_('skipping merge changeset %s:%s\n')
154 % (rev, short(node)))
154 % (rev, short(node)))
155 skipmerge = True
155 skipmerge = True
156 else:
156 else:
157 parent = source.lookup(opts['parent'])
157 parent = source.lookup(opts['parent'])
158 if parent not in parents:
158 if parent not in parents:
159 raise util.Abort(_('%s is not a parent of %s') %
159 raise util.Abort(_('%s is not a parent of %s') %
160 (short(parent), short(node)))
160 (short(parent), short(node)))
161 else:
161 else:
162 parent = parents[0]
162 parent = parents[0]
163
163
164 if skipmerge:
164 if skipmerge:
165 patchfile = None
165 patchfile = None
166 else:
166 else:
167 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
167 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
168 fp = os.fdopen(fd, 'w')
168 fp = os.fdopen(fd, 'w')
169 gen = patch.diff(source, parent, node, opts=diffopts)
169 gen = patch.diff(source, parent, node, opts=diffopts)
170 for chunk in gen:
170 for chunk in gen:
171 fp.write(chunk)
171 fp.write(chunk)
172 fp.close()
172 fp.close()
173
173
174 del revmap[rev]
174 del revmap[rev]
175 if patchfile or domerge:
175 if patchfile or domerge:
176 try:
176 try:
177 try:
177 try:
178 n = self.applyone(repo, node,
178 n = self.applyone(repo, node,
179 source.changelog.read(node),
179 source.changelog.read(node),
180 patchfile, merge=domerge,
180 patchfile, merge=domerge,
181 log=opts.get('log'),
181 log=opts.get('log'),
182 filter=opts.get('filter'))
182 filter=opts.get('filter'))
183 except TransplantError:
183 except TransplantError:
184 # Do not rollback, it is up to the user to
184 # Do not rollback, it is up to the user to
185 # fix the merge or cancel everything
185 # fix the merge or cancel everything
186 tr.close()
186 tr.close()
187 raise
187 raise
188 if n and domerge:
188 if n and domerge:
189 self.ui.status(_('%s merged at %s\n') % (revstr,
189 self.ui.status(_('%s merged at %s\n') % (revstr,
190 short(n)))
190 short(n)))
191 elif n:
191 elif n:
192 self.ui.status(_('%s transplanted to %s\n')
192 self.ui.status(_('%s transplanted to %s\n')
193 % (short(node),
193 % (short(node),
194 short(n)))
194 short(n)))
195 finally:
195 finally:
196 if patchfile:
196 if patchfile:
197 os.unlink(patchfile)
197 os.unlink(patchfile)
198 tr.close()
198 tr.close()
199 if pulls:
199 if pulls:
200 repo.pull(source, heads=pulls)
200 repo.pull(source, heads=pulls)
201 merge.update(repo, pulls[-1], False, False, None)
201 merge.update(repo, pulls[-1], False, False, None)
202 finally:
202 finally:
203 self.saveseries(revmap, merges)
203 self.saveseries(revmap, merges)
204 self.transplants.write()
204 self.transplants.write()
205 if tr:
205 if tr:
206 tr.release()
206 tr.release()
207 lock.release()
207 lock.release()
208 wlock.release()
208 wlock.release()
209
209
210 def filter(self, filter, node, changelog, patchfile):
210 def filter(self, filter, node, changelog, patchfile):
211 '''arbitrarily rewrite changeset before applying it'''
211 '''arbitrarily rewrite changeset before applying it'''
212
212
213 self.ui.status(_('filtering %s\n') % patchfile)
213 self.ui.status(_('filtering %s\n') % patchfile)
214 user, date, msg = (changelog[1], changelog[2], changelog[4])
214 user, date, msg = (changelog[1], changelog[2], changelog[4])
215 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
215 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
216 fp = os.fdopen(fd, 'w')
216 fp = os.fdopen(fd, 'w')
217 fp.write("# HG changeset patch\n")
217 fp.write("# HG changeset patch\n")
218 fp.write("# User %s\n" % user)
218 fp.write("# User %s\n" % user)
219 fp.write("# Date %d %d\n" % date)
219 fp.write("# Date %d %d\n" % date)
220 fp.write(msg + '\n')
220 fp.write(msg + '\n')
221 fp.close()
221 fp.close()
222
222
223 try:
223 try:
224 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
224 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
225 util.shellquote(patchfile)),
225 util.shellquote(patchfile)),
226 environ={'HGUSER': changelog[1],
226 environ={'HGUSER': changelog[1],
227 'HGREVISION': revlog.hex(node),
227 'HGREVISION': revlog.hex(node),
228 },
228 },
229 onerr=util.Abort, errprefix=_('filter failed'),
229 onerr=util.Abort, errprefix=_('filter failed'),
230 out=self.ui.fout)
230 out=self.ui.fout)
231 user, date, msg = self.parselog(file(headerfile))[1:4]
231 user, date, msg = self.parselog(file(headerfile))[1:4]
232 finally:
232 finally:
233 os.unlink(headerfile)
233 os.unlink(headerfile)
234
234
235 return (user, date, msg)
235 return (user, date, msg)
236
236
237 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
237 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
238 filter=None):
238 filter=None):
239 '''apply the patch in patchfile to the repository as a transplant'''
239 '''apply the patch in patchfile to the repository as a transplant'''
240 (manifest, user, (time, timezone), files, message) = cl[:5]
240 (manifest, user, (time, timezone), files, message) = cl[:5]
241 date = "%d %d" % (time, timezone)
241 date = "%d %d" % (time, timezone)
242 extra = {'transplant_source': node}
242 extra = {'transplant_source': node}
243 if filter:
243 if filter:
244 (user, date, message) = self.filter(filter, node, cl, patchfile)
244 (user, date, message) = self.filter(filter, node, cl, patchfile)
245
245
246 if log:
246 if log:
247 # we don't translate messages inserted into commits
247 # we don't translate messages inserted into commits
248 message += '\n(transplanted from %s)' % revlog.hex(node)
248 message += '\n(transplanted from %s)' % revlog.hex(node)
249
249
250 self.ui.status(_('applying %s\n') % short(node))
250 self.ui.status(_('applying %s\n') % short(node))
251 self.ui.note('%s %s\n%s\n' % (user, date, message))
251 self.ui.note('%s %s\n%s\n' % (user, date, message))
252
252
253 if not patchfile and not merge:
253 if not patchfile and not merge:
254 raise util.Abort(_('can only omit patchfile if merging'))
254 raise util.Abort(_('can only omit patchfile if merging'))
255 if patchfile:
255 if patchfile:
256 try:
256 try:
257 files = set()
257 files = set()
258 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
258 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
259 files = list(files)
259 files = list(files)
260 if not files:
260 if not files:
261 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
261 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
262 return None
262 return None
263 except Exception, inst:
263 except Exception, inst:
264 seriespath = os.path.join(self.path, 'series')
264 seriespath = os.path.join(self.path, 'series')
265 if os.path.exists(seriespath):
265 if os.path.exists(seriespath):
266 os.unlink(seriespath)
266 os.unlink(seriespath)
267 p1 = repo.dirstate.p1()
267 p1 = repo.dirstate.p1()
268 p2 = node
268 p2 = node
269 self.log(user, date, message, p1, p2, merge=merge)
269 self.log(user, date, message, p1, p2, merge=merge)
270 self.ui.write(str(inst) + '\n')
270 self.ui.write(str(inst) + '\n')
271 raise TransplantError(_('fix up the merge and run '
271 raise TransplantError(_('fix up the merge and run '
272 'hg transplant --continue'))
272 'hg transplant --continue'))
273 else:
273 else:
274 files = None
274 files = None
275 if merge:
275 if merge:
276 p1, p2 = repo.dirstate.parents()
276 p1, p2 = repo.dirstate.parents()
277 repo.dirstate.setparents(p1, node)
277 repo.setparents(p1, node)
278 m = match.always(repo.root, '')
278 m = match.always(repo.root, '')
279 else:
279 else:
280 m = match.exact(repo.root, '', files)
280 m = match.exact(repo.root, '', files)
281
281
282 n = repo.commit(message, user, date, extra=extra, match=m,
282 n = repo.commit(message, user, date, extra=extra, match=m,
283 editor=self.editor)
283 editor=self.editor)
284 if not n:
284 if not n:
285 # Crash here to prevent an unclear crash later, in
285 # Crash here to prevent an unclear crash later, in
286 # transplants.write(). This can happen if patch.patch()
286 # transplants.write(). This can happen if patch.patch()
287 # does nothing but claims success or if repo.status() fails
287 # does nothing but claims success or if repo.status() fails
288 # to report changes done by patch.patch(). These both
288 # to report changes done by patch.patch(). These both
289 # appear to be bugs in other parts of Mercurial, but dying
289 # appear to be bugs in other parts of Mercurial, but dying
290 # here, as soon as we can detect the problem, is preferable
290 # here, as soon as we can detect the problem, is preferable
291 # to silently dropping changesets on the floor.
291 # to silently dropping changesets on the floor.
292 raise RuntimeError('nothing committed after transplant')
292 raise RuntimeError('nothing committed after transplant')
293 if not merge:
293 if not merge:
294 self.transplants.set(n, node)
294 self.transplants.set(n, node)
295
295
296 return n
296 return n
297
297
298 def resume(self, repo, source, opts=None):
298 def resume(self, repo, source, opts=None):
299 '''recover last transaction and apply remaining changesets'''
299 '''recover last transaction and apply remaining changesets'''
300 if os.path.exists(os.path.join(self.path, 'journal')):
300 if os.path.exists(os.path.join(self.path, 'journal')):
301 n, node = self.recover(repo)
301 n, node = self.recover(repo)
302 self.ui.status(_('%s transplanted as %s\n') % (short(node),
302 self.ui.status(_('%s transplanted as %s\n') % (short(node),
303 short(n)))
303 short(n)))
304 seriespath = os.path.join(self.path, 'series')
304 seriespath = os.path.join(self.path, 'series')
305 if not os.path.exists(seriespath):
305 if not os.path.exists(seriespath):
306 self.transplants.write()
306 self.transplants.write()
307 return
307 return
308 nodes, merges = self.readseries()
308 nodes, merges = self.readseries()
309 revmap = {}
309 revmap = {}
310 for n in nodes:
310 for n in nodes:
311 revmap[source.changelog.rev(n)] = n
311 revmap[source.changelog.rev(n)] = n
312 os.unlink(seriespath)
312 os.unlink(seriespath)
313
313
314 self.apply(repo, source, revmap, merges, opts)
314 self.apply(repo, source, revmap, merges, opts)
315
315
316 def recover(self, repo):
316 def recover(self, repo):
317 '''commit working directory using journal metadata'''
317 '''commit working directory using journal metadata'''
318 node, user, date, message, parents = self.readlog()
318 node, user, date, message, parents = self.readlog()
319 merge = False
319 merge = False
320
320
321 if not user or not date or not message or not parents[0]:
321 if not user or not date or not message or not parents[0]:
322 raise util.Abort(_('transplant log file is corrupt'))
322 raise util.Abort(_('transplant log file is corrupt'))
323
323
324 parent = parents[0]
324 parent = parents[0]
325 if len(parents) > 1:
325 if len(parents) > 1:
326 if opts.get('parent'):
326 if opts.get('parent'):
327 parent = source.lookup(opts['parent'])
327 parent = source.lookup(opts['parent'])
328 if parent not in parents:
328 if parent not in parents:
329 raise util.Abort(_('%s is not a parent of %s') %
329 raise util.Abort(_('%s is not a parent of %s') %
330 (short(parent), short(node)))
330 (short(parent), short(node)))
331 else:
331 else:
332 merge = True
332 merge = True
333
333
334 extra = {'transplant_source': node}
334 extra = {'transplant_source': node}
335 wlock = repo.wlock()
335 wlock = repo.wlock()
336 try:
336 try:
337 p1, p2 = repo.dirstate.parents()
337 p1, p2 = repo.dirstate.parents()
338 if p1 != parent:
338 if p1 != parent:
339 raise util.Abort(
339 raise util.Abort(
340 _('working dir not at transplant parent %s') %
340 _('working dir not at transplant parent %s') %
341 revlog.hex(parent))
341 revlog.hex(parent))
342 if merge:
342 if merge:
343 repo.dirstate.setparents(p1, parents[1])
343 repo.setparents(p1, parents[1])
344 n = repo.commit(message, user, date, extra=extra,
344 n = repo.commit(message, user, date, extra=extra,
345 editor=self.editor)
345 editor=self.editor)
346 if not n:
346 if not n:
347 raise util.Abort(_('commit failed'))
347 raise util.Abort(_('commit failed'))
348 if not merge:
348 if not merge:
349 self.transplants.set(n, node)
349 self.transplants.set(n, node)
350 self.unlog()
350 self.unlog()
351
351
352 return n, node
352 return n, node
353 finally:
353 finally:
354 wlock.release()
354 wlock.release()
355
355
356 def readseries(self):
356 def readseries(self):
357 nodes = []
357 nodes = []
358 merges = []
358 merges = []
359 cur = nodes
359 cur = nodes
360 for line in self.opener.read('series').splitlines():
360 for line in self.opener.read('series').splitlines():
361 if line.startswith('# Merges'):
361 if line.startswith('# Merges'):
362 cur = merges
362 cur = merges
363 continue
363 continue
364 cur.append(revlog.bin(line))
364 cur.append(revlog.bin(line))
365
365
366 return (nodes, merges)
366 return (nodes, merges)
367
367
368 def saveseries(self, revmap, merges):
368 def saveseries(self, revmap, merges):
369 if not revmap:
369 if not revmap:
370 return
370 return
371
371
372 if not os.path.isdir(self.path):
372 if not os.path.isdir(self.path):
373 os.mkdir(self.path)
373 os.mkdir(self.path)
374 series = self.opener('series', 'w')
374 series = self.opener('series', 'w')
375 for rev in sorted(revmap):
375 for rev in sorted(revmap):
376 series.write(revlog.hex(revmap[rev]) + '\n')
376 series.write(revlog.hex(revmap[rev]) + '\n')
377 if merges:
377 if merges:
378 series.write('# Merges\n')
378 series.write('# Merges\n')
379 for m in merges:
379 for m in merges:
380 series.write(revlog.hex(m) + '\n')
380 series.write(revlog.hex(m) + '\n')
381 series.close()
381 series.close()
382
382
383 def parselog(self, fp):
383 def parselog(self, fp):
384 parents = []
384 parents = []
385 message = []
385 message = []
386 node = revlog.nullid
386 node = revlog.nullid
387 inmsg = False
387 inmsg = False
388 user = None
388 user = None
389 date = None
389 date = None
390 for line in fp.read().splitlines():
390 for line in fp.read().splitlines():
391 if inmsg:
391 if inmsg:
392 message.append(line)
392 message.append(line)
393 elif line.startswith('# User '):
393 elif line.startswith('# User '):
394 user = line[7:]
394 user = line[7:]
395 elif line.startswith('# Date '):
395 elif line.startswith('# Date '):
396 date = line[7:]
396 date = line[7:]
397 elif line.startswith('# Node ID '):
397 elif line.startswith('# Node ID '):
398 node = revlog.bin(line[10:])
398 node = revlog.bin(line[10:])
399 elif line.startswith('# Parent '):
399 elif line.startswith('# Parent '):
400 parents.append(revlog.bin(line[9:]))
400 parents.append(revlog.bin(line[9:]))
401 elif not line.startswith('# '):
401 elif not line.startswith('# '):
402 inmsg = True
402 inmsg = True
403 message.append(line)
403 message.append(line)
404 if None in (user, date):
404 if None in (user, date):
405 raise util.Abort(_("filter corrupted changeset (no user or date)"))
405 raise util.Abort(_("filter corrupted changeset (no user or date)"))
406 return (node, user, date, '\n'.join(message), parents)
406 return (node, user, date, '\n'.join(message), parents)
407
407
408 def log(self, user, date, message, p1, p2, merge=False):
408 def log(self, user, date, message, p1, p2, merge=False):
409 '''journal changelog metadata for later recover'''
409 '''journal changelog metadata for later recover'''
410
410
411 if not os.path.isdir(self.path):
411 if not os.path.isdir(self.path):
412 os.mkdir(self.path)
412 os.mkdir(self.path)
413 fp = self.opener('journal', 'w')
413 fp = self.opener('journal', 'w')
414 fp.write('# User %s\n' % user)
414 fp.write('# User %s\n' % user)
415 fp.write('# Date %s\n' % date)
415 fp.write('# Date %s\n' % date)
416 fp.write('# Node ID %s\n' % revlog.hex(p2))
416 fp.write('# Node ID %s\n' % revlog.hex(p2))
417 fp.write('# Parent ' + revlog.hex(p1) + '\n')
417 fp.write('# Parent ' + revlog.hex(p1) + '\n')
418 if merge:
418 if merge:
419 fp.write('# Parent ' + revlog.hex(p2) + '\n')
419 fp.write('# Parent ' + revlog.hex(p2) + '\n')
420 fp.write(message.rstrip() + '\n')
420 fp.write(message.rstrip() + '\n')
421 fp.close()
421 fp.close()
422
422
423 def readlog(self):
423 def readlog(self):
424 return self.parselog(self.opener('journal'))
424 return self.parselog(self.opener('journal'))
425
425
426 def unlog(self):
426 def unlog(self):
427 '''remove changelog journal'''
427 '''remove changelog journal'''
428 absdst = os.path.join(self.path, 'journal')
428 absdst = os.path.join(self.path, 'journal')
429 if os.path.exists(absdst):
429 if os.path.exists(absdst):
430 os.unlink(absdst)
430 os.unlink(absdst)
431
431
432 def transplantfilter(self, repo, source, root):
432 def transplantfilter(self, repo, source, root):
433 def matchfn(node):
433 def matchfn(node):
434 if self.applied(repo, node, root):
434 if self.applied(repo, node, root):
435 return False
435 return False
436 if source.changelog.parents(node)[1] != revlog.nullid:
436 if source.changelog.parents(node)[1] != revlog.nullid:
437 return False
437 return False
438 extra = source.changelog.read(node)[5]
438 extra = source.changelog.read(node)[5]
439 cnode = extra.get('transplant_source')
439 cnode = extra.get('transplant_source')
440 if cnode and self.applied(repo, cnode, root):
440 if cnode and self.applied(repo, cnode, root):
441 return False
441 return False
442 return True
442 return True
443
443
444 return matchfn
444 return matchfn
445
445
446 def hasnode(repo, node):
446 def hasnode(repo, node):
447 try:
447 try:
448 return repo.changelog.rev(node) is not None
448 return repo.changelog.rev(node) is not None
449 except error.RevlogError:
449 except error.RevlogError:
450 return False
450 return False
451
451
452 def browserevs(ui, repo, nodes, opts):
452 def browserevs(ui, repo, nodes, opts):
453 '''interactively transplant changesets'''
453 '''interactively transplant changesets'''
454 def browsehelp(ui):
454 def browsehelp(ui):
455 ui.write(_('y: transplant this changeset\n'
455 ui.write(_('y: transplant this changeset\n'
456 'n: skip this changeset\n'
456 'n: skip this changeset\n'
457 'm: merge at this changeset\n'
457 'm: merge at this changeset\n'
458 'p: show patch\n'
458 'p: show patch\n'
459 'c: commit selected changesets\n'
459 'c: commit selected changesets\n'
460 'q: cancel transplant\n'
460 'q: cancel transplant\n'
461 '?: show this help\n'))
461 '?: show this help\n'))
462
462
463 displayer = cmdutil.show_changeset(ui, repo, opts)
463 displayer = cmdutil.show_changeset(ui, repo, opts)
464 transplants = []
464 transplants = []
465 merges = []
465 merges = []
466 for node in nodes:
466 for node in nodes:
467 displayer.show(repo[node])
467 displayer.show(repo[node])
468 action = None
468 action = None
469 while not action:
469 while not action:
470 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
470 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
471 if action == '?':
471 if action == '?':
472 browsehelp(ui)
472 browsehelp(ui)
473 action = None
473 action = None
474 elif action == 'p':
474 elif action == 'p':
475 parent = repo.changelog.parents(node)[0]
475 parent = repo.changelog.parents(node)[0]
476 for chunk in patch.diff(repo, parent, node):
476 for chunk in patch.diff(repo, parent, node):
477 ui.write(chunk)
477 ui.write(chunk)
478 action = None
478 action = None
479 elif action not in ('y', 'n', 'm', 'c', 'q'):
479 elif action not in ('y', 'n', 'm', 'c', 'q'):
480 ui.write(_('no such option\n'))
480 ui.write(_('no such option\n'))
481 action = None
481 action = None
482 if action == 'y':
482 if action == 'y':
483 transplants.append(node)
483 transplants.append(node)
484 elif action == 'm':
484 elif action == 'm':
485 merges.append(node)
485 merges.append(node)
486 elif action == 'c':
486 elif action == 'c':
487 break
487 break
488 elif action == 'q':
488 elif action == 'q':
489 transplants = ()
489 transplants = ()
490 merges = ()
490 merges = ()
491 break
491 break
492 displayer.close()
492 displayer.close()
493 return (transplants, merges)
493 return (transplants, merges)
494
494
495 @command('transplant',
495 @command('transplant',
496 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
496 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
497 ('b', 'branch', [],
497 ('b', 'branch', [],
498 _('pull patches from branch BRANCH'), _('BRANCH')),
498 _('pull patches from branch BRANCH'), _('BRANCH')),
499 ('a', 'all', None, _('pull all changesets up to BRANCH')),
499 ('a', 'all', None, _('pull all changesets up to BRANCH')),
500 ('p', 'prune', [], _('skip over REV'), _('REV')),
500 ('p', 'prune', [], _('skip over REV'), _('REV')),
501 ('m', 'merge', [], _('merge at REV'), _('REV')),
501 ('m', 'merge', [], _('merge at REV'), _('REV')),
502 ('', 'parent', '',
502 ('', 'parent', '',
503 _('parent to choose when transplanting merge'), _('REV')),
503 _('parent to choose when transplanting merge'), _('REV')),
504 ('e', 'edit', False, _('invoke editor on commit messages')),
504 ('e', 'edit', False, _('invoke editor on commit messages')),
505 ('', 'log', None, _('append transplant info to log message')),
505 ('', 'log', None, _('append transplant info to log message')),
506 ('c', 'continue', None, _('continue last transplant session '
506 ('c', 'continue', None, _('continue last transplant session '
507 'after repair')),
507 'after repair')),
508 ('', 'filter', '',
508 ('', 'filter', '',
509 _('filter changesets through command'), _('CMD'))],
509 _('filter changesets through command'), _('CMD'))],
510 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
510 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
511 '[-m REV] [REV]...'))
511 '[-m REV] [REV]...'))
512 def transplant(ui, repo, *revs, **opts):
512 def transplant(ui, repo, *revs, **opts):
513 '''transplant changesets from another branch
513 '''transplant changesets from another branch
514
514
515 Selected changesets will be applied on top of the current working
515 Selected changesets will be applied on top of the current working
516 directory with the log of the original changeset. The changesets
516 directory with the log of the original changeset. The changesets
517 are copied and will thus appear twice in the history. Use the
517 are copied and will thus appear twice in the history. Use the
518 rebase extension instead if you want to move a whole branch of
518 rebase extension instead if you want to move a whole branch of
519 unpublished changesets.
519 unpublished changesets.
520
520
521 If --log is specified, log messages will have a comment appended
521 If --log is specified, log messages will have a comment appended
522 of the form::
522 of the form::
523
523
524 (transplanted from CHANGESETHASH)
524 (transplanted from CHANGESETHASH)
525
525
526 You can rewrite the changelog message with the --filter option.
526 You can rewrite the changelog message with the --filter option.
527 Its argument will be invoked with the current changelog message as
527 Its argument will be invoked with the current changelog message as
528 $1 and the patch as $2.
528 $1 and the patch as $2.
529
529
530 If --source/-s is specified, selects changesets from the named
530 If --source/-s is specified, selects changesets from the named
531 repository. If --branch/-b is specified, selects changesets from
531 repository. If --branch/-b is specified, selects changesets from
532 the branch holding the named revision, up to that revision. If
532 the branch holding the named revision, up to that revision. If
533 --all/-a is specified, all changesets on the branch will be
533 --all/-a is specified, all changesets on the branch will be
534 transplanted, otherwise you will be prompted to select the
534 transplanted, otherwise you will be prompted to select the
535 changesets you want.
535 changesets you want.
536
536
537 :hg:`transplant --branch REVISION --all` will transplant the
537 :hg:`transplant --branch REVISION --all` will transplant the
538 selected branch (up to the named revision) onto your current
538 selected branch (up to the named revision) onto your current
539 working directory.
539 working directory.
540
540
541 You can optionally mark selected transplanted changesets as merge
541 You can optionally mark selected transplanted changesets as merge
542 changesets. You will not be prompted to transplant any ancestors
542 changesets. You will not be prompted to transplant any ancestors
543 of a merged transplant, and you can merge descendants of them
543 of a merged transplant, and you can merge descendants of them
544 normally instead of transplanting them.
544 normally instead of transplanting them.
545
545
546 Merge changesets may be transplanted directly by specifying the
546 Merge changesets may be transplanted directly by specifying the
547 proper parent changeset by calling :hg:`transplant --parent`.
547 proper parent changeset by calling :hg:`transplant --parent`.
548
548
549 If no merges or revisions are provided, :hg:`transplant` will
549 If no merges or revisions are provided, :hg:`transplant` will
550 start an interactive changeset browser.
550 start an interactive changeset browser.
551
551
552 If a changeset application fails, you can fix the merge by hand
552 If a changeset application fails, you can fix the merge by hand
553 and then resume where you left off by calling :hg:`transplant
553 and then resume where you left off by calling :hg:`transplant
554 --continue/-c`.
554 --continue/-c`.
555 '''
555 '''
556 def incwalk(repo, csets, match=util.always):
556 def incwalk(repo, csets, match=util.always):
557 for node in csets:
557 for node in csets:
558 if match(node):
558 if match(node):
559 yield node
559 yield node
560
560
561 def transplantwalk(repo, root, branches, match=util.always):
561 def transplantwalk(repo, root, branches, match=util.always):
562 if not branches:
562 if not branches:
563 branches = repo.heads()
563 branches = repo.heads()
564 ancestors = []
564 ancestors = []
565 for branch in branches:
565 for branch in branches:
566 ancestors.append(repo.changelog.ancestor(root, branch))
566 ancestors.append(repo.changelog.ancestor(root, branch))
567 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
567 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
568 if match(node):
568 if match(node):
569 yield node
569 yield node
570
570
571 def checkopts(opts, revs):
571 def checkopts(opts, revs):
572 if opts.get('continue'):
572 if opts.get('continue'):
573 if opts.get('branch') or opts.get('all') or opts.get('merge'):
573 if opts.get('branch') or opts.get('all') or opts.get('merge'):
574 raise util.Abort(_('--continue is incompatible with '
574 raise util.Abort(_('--continue is incompatible with '
575 'branch, all or merge'))
575 'branch, all or merge'))
576 return
576 return
577 if not (opts.get('source') or revs or
577 if not (opts.get('source') or revs or
578 opts.get('merge') or opts.get('branch')):
578 opts.get('merge') or opts.get('branch')):
579 raise util.Abort(_('no source URL, branch tag or revision '
579 raise util.Abort(_('no source URL, branch tag or revision '
580 'list provided'))
580 'list provided'))
581 if opts.get('all'):
581 if opts.get('all'):
582 if not opts.get('branch'):
582 if not opts.get('branch'):
583 raise util.Abort(_('--all requires a branch revision'))
583 raise util.Abort(_('--all requires a branch revision'))
584 if revs:
584 if revs:
585 raise util.Abort(_('--all is incompatible with a '
585 raise util.Abort(_('--all is incompatible with a '
586 'revision list'))
586 'revision list'))
587
587
588 checkopts(opts, revs)
588 checkopts(opts, revs)
589
589
590 if not opts.get('log'):
590 if not opts.get('log'):
591 opts['log'] = ui.config('transplant', 'log')
591 opts['log'] = ui.config('transplant', 'log')
592 if not opts.get('filter'):
592 if not opts.get('filter'):
593 opts['filter'] = ui.config('transplant', 'filter')
593 opts['filter'] = ui.config('transplant', 'filter')
594
594
595 tp = transplanter(ui, repo)
595 tp = transplanter(ui, repo)
596 if opts.get('edit'):
596 if opts.get('edit'):
597 tp.editor = cmdutil.commitforceeditor
597 tp.editor = cmdutil.commitforceeditor
598
598
599 p1, p2 = repo.dirstate.parents()
599 p1, p2 = repo.dirstate.parents()
600 if len(repo) > 0 and p1 == revlog.nullid:
600 if len(repo) > 0 and p1 == revlog.nullid:
601 raise util.Abort(_('no revision checked out'))
601 raise util.Abort(_('no revision checked out'))
602 if not opts.get('continue'):
602 if not opts.get('continue'):
603 if p2 != revlog.nullid:
603 if p2 != revlog.nullid:
604 raise util.Abort(_('outstanding uncommitted merges'))
604 raise util.Abort(_('outstanding uncommitted merges'))
605 m, a, r, d = repo.status()[:4]
605 m, a, r, d = repo.status()[:4]
606 if m or a or r or d:
606 if m or a or r or d:
607 raise util.Abort(_('outstanding local changes'))
607 raise util.Abort(_('outstanding local changes'))
608
608
609 sourcerepo = opts.get('source')
609 sourcerepo = opts.get('source')
610 if sourcerepo:
610 if sourcerepo:
611 source = hg.peer(ui, opts, ui.expandpath(sourcerepo))
611 source = hg.peer(ui, opts, ui.expandpath(sourcerepo))
612 branches = map(source.lookup, opts.get('branch', ()))
612 branches = map(source.lookup, opts.get('branch', ()))
613 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source,
613 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source,
614 onlyheads=branches, force=True)
614 onlyheads=branches, force=True)
615 else:
615 else:
616 source = repo
616 source = repo
617 branches = map(source.lookup, opts.get('branch', ()))
617 branches = map(source.lookup, opts.get('branch', ()))
618 cleanupfn = None
618 cleanupfn = None
619
619
620 try:
620 try:
621 if opts.get('continue'):
621 if opts.get('continue'):
622 tp.resume(repo, source, opts)
622 tp.resume(repo, source, opts)
623 return
623 return
624
624
625 tf = tp.transplantfilter(repo, source, p1)
625 tf = tp.transplantfilter(repo, source, p1)
626 if opts.get('prune'):
626 if opts.get('prune'):
627 prune = [source.lookup(r)
627 prune = [source.lookup(r)
628 for r in scmutil.revrange(source, opts.get('prune'))]
628 for r in scmutil.revrange(source, opts.get('prune'))]
629 matchfn = lambda x: tf(x) and x not in prune
629 matchfn = lambda x: tf(x) and x not in prune
630 else:
630 else:
631 matchfn = tf
631 matchfn = tf
632 merges = map(source.lookup, opts.get('merge', ()))
632 merges = map(source.lookup, opts.get('merge', ()))
633 revmap = {}
633 revmap = {}
634 if revs:
634 if revs:
635 for r in scmutil.revrange(source, revs):
635 for r in scmutil.revrange(source, revs):
636 revmap[int(r)] = source.lookup(r)
636 revmap[int(r)] = source.lookup(r)
637 elif opts.get('all') or not merges:
637 elif opts.get('all') or not merges:
638 if source != repo:
638 if source != repo:
639 alltransplants = incwalk(source, csets, match=matchfn)
639 alltransplants = incwalk(source, csets, match=matchfn)
640 else:
640 else:
641 alltransplants = transplantwalk(source, p1, branches,
641 alltransplants = transplantwalk(source, p1, branches,
642 match=matchfn)
642 match=matchfn)
643 if opts.get('all'):
643 if opts.get('all'):
644 revs = alltransplants
644 revs = alltransplants
645 else:
645 else:
646 revs, newmerges = browserevs(ui, source, alltransplants, opts)
646 revs, newmerges = browserevs(ui, source, alltransplants, opts)
647 merges.extend(newmerges)
647 merges.extend(newmerges)
648 for r in revs:
648 for r in revs:
649 revmap[source.changelog.rev(r)] = r
649 revmap[source.changelog.rev(r)] = r
650 for r in merges:
650 for r in merges:
651 revmap[source.changelog.rev(r)] = r
651 revmap[source.changelog.rev(r)] = r
652
652
653 tp.apply(repo, source, revmap, merges, opts)
653 tp.apply(repo, source, revmap, merges, opts)
654 finally:
654 finally:
655 if cleanupfn:
655 if cleanupfn:
656 cleanupfn()
656 cleanupfn()
657
657
658 def revsettransplanted(repo, subset, x):
658 def revsettransplanted(repo, subset, x):
659 """``transplanted([set])``
659 """``transplanted([set])``
660 Transplanted changesets in set, or all transplanted changesets.
660 Transplanted changesets in set, or all transplanted changesets.
661 """
661 """
662 if x:
662 if x:
663 s = revset.getset(repo, subset, x)
663 s = revset.getset(repo, subset, x)
664 else:
664 else:
665 s = subset
665 s = subset
666 return [r for r in s if repo[r].extra().get('transplant_source')]
666 return [r for r in s if repo[r].extra().get('transplant_source')]
667
667
668 def kwtransplanted(repo, ctx, **args):
668 def kwtransplanted(repo, ctx, **args):
669 """:transplanted: String. The node identifier of the transplanted
669 """:transplanted: String. The node identifier of the transplanted
670 changeset if any."""
670 changeset if any."""
671 n = ctx.extra().get('transplant_source')
671 n = ctx.extra().get('transplant_source')
672 return n and revlog.hex(n) or ''
672 return n and revlog.hex(n) or ''
673
673
674 def extsetup(ui):
674 def extsetup(ui):
675 revset.symbols['transplanted'] = revsettransplanted
675 revset.symbols['transplanted'] = revsettransplanted
676 templatekw.keywords['transplanted'] = kwtransplanted
676 templatekw.keywords['transplanted'] = kwtransplanted
677
677
678 # tell hggettext to extract docstrings from these functions:
678 # tell hggettext to extract docstrings from these functions:
679 i18nfunctions = [revsettransplanted, kwtransplanted]
679 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,1644 +1,1644 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import subrepo, context, repair, bookmarks
13 import subrepo, context, repair, bookmarks
14
14
15 def parsealiases(cmd):
15 def parsealiases(cmd):
16 return cmd.lstrip("^").split("|")
16 return cmd.lstrip("^").split("|")
17
17
18 def findpossible(cmd, table, strict=False):
18 def findpossible(cmd, table, strict=False):
19 """
19 """
20 Return cmd -> (aliases, command table entry)
20 Return cmd -> (aliases, command table entry)
21 for each matching command.
21 for each matching command.
22 Return debug commands (or their aliases) only if no normal command matches.
22 Return debug commands (or their aliases) only if no normal command matches.
23 """
23 """
24 choice = {}
24 choice = {}
25 debugchoice = {}
25 debugchoice = {}
26
26
27 if cmd in table:
27 if cmd in table:
28 # short-circuit exact matches, "log" alias beats "^log|history"
28 # short-circuit exact matches, "log" alias beats "^log|history"
29 keys = [cmd]
29 keys = [cmd]
30 else:
30 else:
31 keys = table.keys()
31 keys = table.keys()
32
32
33 for e in keys:
33 for e in keys:
34 aliases = parsealiases(e)
34 aliases = parsealiases(e)
35 found = None
35 found = None
36 if cmd in aliases:
36 if cmd in aliases:
37 found = cmd
37 found = cmd
38 elif not strict:
38 elif not strict:
39 for a in aliases:
39 for a in aliases:
40 if a.startswith(cmd):
40 if a.startswith(cmd):
41 found = a
41 found = a
42 break
42 break
43 if found is not None:
43 if found is not None:
44 if aliases[0].startswith("debug") or found.startswith("debug"):
44 if aliases[0].startswith("debug") or found.startswith("debug"):
45 debugchoice[found] = (aliases, table[e])
45 debugchoice[found] = (aliases, table[e])
46 else:
46 else:
47 choice[found] = (aliases, table[e])
47 choice[found] = (aliases, table[e])
48
48
49 if not choice and debugchoice:
49 if not choice and debugchoice:
50 choice = debugchoice
50 choice = debugchoice
51
51
52 return choice
52 return choice
53
53
54 def findcmd(cmd, table, strict=True):
54 def findcmd(cmd, table, strict=True):
55 """Return (aliases, command table entry) for command string."""
55 """Return (aliases, command table entry) for command string."""
56 choice = findpossible(cmd, table, strict)
56 choice = findpossible(cmd, table, strict)
57
57
58 if cmd in choice:
58 if cmd in choice:
59 return choice[cmd]
59 return choice[cmd]
60
60
61 if len(choice) > 1:
61 if len(choice) > 1:
62 clist = choice.keys()
62 clist = choice.keys()
63 clist.sort()
63 clist.sort()
64 raise error.AmbiguousCommand(cmd, clist)
64 raise error.AmbiguousCommand(cmd, clist)
65
65
66 if choice:
66 if choice:
67 return choice.values()[0]
67 return choice.values()[0]
68
68
69 raise error.UnknownCommand(cmd)
69 raise error.UnknownCommand(cmd)
70
70
71 def findrepo(p):
71 def findrepo(p):
72 while not os.path.isdir(os.path.join(p, ".hg")):
72 while not os.path.isdir(os.path.join(p, ".hg")):
73 oldp, p = p, os.path.dirname(p)
73 oldp, p = p, os.path.dirname(p)
74 if p == oldp:
74 if p == oldp:
75 return None
75 return None
76
76
77 return p
77 return p
78
78
79 def bailifchanged(repo):
79 def bailifchanged(repo):
80 if repo.dirstate.p2() != nullid:
80 if repo.dirstate.p2() != nullid:
81 raise util.Abort(_('outstanding uncommitted merge'))
81 raise util.Abort(_('outstanding uncommitted merge'))
82 modified, added, removed, deleted = repo.status()[:4]
82 modified, added, removed, deleted = repo.status()[:4]
83 if modified or added or removed or deleted:
83 if modified or added or removed or deleted:
84 raise util.Abort(_("outstanding uncommitted changes"))
84 raise util.Abort(_("outstanding uncommitted changes"))
85 ctx = repo[None]
85 ctx = repo[None]
86 for s in ctx.substate:
86 for s in ctx.substate:
87 if ctx.sub(s).dirty():
87 if ctx.sub(s).dirty():
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
89
89
90 def logmessage(ui, opts):
90 def logmessage(ui, opts):
91 """ get the log message according to -m and -l option """
91 """ get the log message according to -m and -l option """
92 message = opts.get('message')
92 message = opts.get('message')
93 logfile = opts.get('logfile')
93 logfile = opts.get('logfile')
94
94
95 if message and logfile:
95 if message and logfile:
96 raise util.Abort(_('options --message and --logfile are mutually '
96 raise util.Abort(_('options --message and --logfile are mutually '
97 'exclusive'))
97 'exclusive'))
98 if not message and logfile:
98 if not message and logfile:
99 try:
99 try:
100 if logfile == '-':
100 if logfile == '-':
101 message = ui.fin.read()
101 message = ui.fin.read()
102 else:
102 else:
103 message = '\n'.join(util.readfile(logfile).splitlines())
103 message = '\n'.join(util.readfile(logfile).splitlines())
104 except IOError, inst:
104 except IOError, inst:
105 raise util.Abort(_("can't read commit message '%s': %s") %
105 raise util.Abort(_("can't read commit message '%s': %s") %
106 (logfile, inst.strerror))
106 (logfile, inst.strerror))
107 return message
107 return message
108
108
109 def loglimit(opts):
109 def loglimit(opts):
110 """get the log limit according to option -l/--limit"""
110 """get the log limit according to option -l/--limit"""
111 limit = opts.get('limit')
111 limit = opts.get('limit')
112 if limit:
112 if limit:
113 try:
113 try:
114 limit = int(limit)
114 limit = int(limit)
115 except ValueError:
115 except ValueError:
116 raise util.Abort(_('limit must be a positive integer'))
116 raise util.Abort(_('limit must be a positive integer'))
117 if limit <= 0:
117 if limit <= 0:
118 raise util.Abort(_('limit must be positive'))
118 raise util.Abort(_('limit must be positive'))
119 else:
119 else:
120 limit = None
120 limit = None
121 return limit
121 return limit
122
122
123 def makefilename(repo, pat, node, desc=None,
123 def makefilename(repo, pat, node, desc=None,
124 total=None, seqno=None, revwidth=None, pathname=None):
124 total=None, seqno=None, revwidth=None, pathname=None):
125 node_expander = {
125 node_expander = {
126 'H': lambda: hex(node),
126 'H': lambda: hex(node),
127 'R': lambda: str(repo.changelog.rev(node)),
127 'R': lambda: str(repo.changelog.rev(node)),
128 'h': lambda: short(node),
128 'h': lambda: short(node),
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
130 }
130 }
131 expander = {
131 expander = {
132 '%': lambda: '%',
132 '%': lambda: '%',
133 'b': lambda: os.path.basename(repo.root),
133 'b': lambda: os.path.basename(repo.root),
134 }
134 }
135
135
136 try:
136 try:
137 if node:
137 if node:
138 expander.update(node_expander)
138 expander.update(node_expander)
139 if node:
139 if node:
140 expander['r'] = (lambda:
140 expander['r'] = (lambda:
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
142 if total is not None:
142 if total is not None:
143 expander['N'] = lambda: str(total)
143 expander['N'] = lambda: str(total)
144 if seqno is not None:
144 if seqno is not None:
145 expander['n'] = lambda: str(seqno)
145 expander['n'] = lambda: str(seqno)
146 if total is not None and seqno is not None:
146 if total is not None and seqno is not None:
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
148 if pathname is not None:
148 if pathname is not None:
149 expander['s'] = lambda: os.path.basename(pathname)
149 expander['s'] = lambda: os.path.basename(pathname)
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
151 expander['p'] = lambda: pathname
151 expander['p'] = lambda: pathname
152
152
153 newname = []
153 newname = []
154 patlen = len(pat)
154 patlen = len(pat)
155 i = 0
155 i = 0
156 while i < patlen:
156 while i < patlen:
157 c = pat[i]
157 c = pat[i]
158 if c == '%':
158 if c == '%':
159 i += 1
159 i += 1
160 c = pat[i]
160 c = pat[i]
161 c = expander[c]()
161 c = expander[c]()
162 newname.append(c)
162 newname.append(c)
163 i += 1
163 i += 1
164 return ''.join(newname)
164 return ''.join(newname)
165 except KeyError, inst:
165 except KeyError, inst:
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
167 inst.args[0])
167 inst.args[0])
168
168
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
170 seqno=None, revwidth=None, mode='wb', pathname=None):
170 seqno=None, revwidth=None, mode='wb', pathname=None):
171
171
172 writable = mode not in ('r', 'rb')
172 writable = mode not in ('r', 'rb')
173
173
174 if not pat or pat == '-':
174 if not pat or pat == '-':
175 fp = writable and repo.ui.fout or repo.ui.fin
175 fp = writable and repo.ui.fout or repo.ui.fin
176 if util.safehasattr(fp, 'fileno'):
176 if util.safehasattr(fp, 'fileno'):
177 return os.fdopen(os.dup(fp.fileno()), mode)
177 return os.fdopen(os.dup(fp.fileno()), mode)
178 else:
178 else:
179 # if this fp can't be duped properly, return
179 # if this fp can't be duped properly, return
180 # a dummy object that can be closed
180 # a dummy object that can be closed
181 class wrappedfileobj(object):
181 class wrappedfileobj(object):
182 noop = lambda x: None
182 noop = lambda x: None
183 def __init__(self, f):
183 def __init__(self, f):
184 self.f = f
184 self.f = f
185 def __getattr__(self, attr):
185 def __getattr__(self, attr):
186 if attr == 'close':
186 if attr == 'close':
187 return self.noop
187 return self.noop
188 else:
188 else:
189 return getattr(self.f, attr)
189 return getattr(self.f, attr)
190
190
191 return wrappedfileobj(fp)
191 return wrappedfileobj(fp)
192 if util.safehasattr(pat, 'write') and writable:
192 if util.safehasattr(pat, 'write') and writable:
193 return pat
193 return pat
194 if util.safehasattr(pat, 'read') and 'r' in mode:
194 if util.safehasattr(pat, 'read') and 'r' in mode:
195 return pat
195 return pat
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
197 pathname),
197 pathname),
198 mode)
198 mode)
199
199
200 def openrevlog(repo, cmd, file_, opts):
200 def openrevlog(repo, cmd, file_, opts):
201 """opens the changelog, manifest, a filelog or a given revlog"""
201 """opens the changelog, manifest, a filelog or a given revlog"""
202 cl = opts['changelog']
202 cl = opts['changelog']
203 mf = opts['manifest']
203 mf = opts['manifest']
204 msg = None
204 msg = None
205 if cl and mf:
205 if cl and mf:
206 msg = _('cannot specify --changelog and --manifest at the same time')
206 msg = _('cannot specify --changelog and --manifest at the same time')
207 elif cl or mf:
207 elif cl or mf:
208 if file_:
208 if file_:
209 msg = _('cannot specify filename with --changelog or --manifest')
209 msg = _('cannot specify filename with --changelog or --manifest')
210 elif not repo:
210 elif not repo:
211 msg = _('cannot specify --changelog or --manifest '
211 msg = _('cannot specify --changelog or --manifest '
212 'without a repository')
212 'without a repository')
213 if msg:
213 if msg:
214 raise util.Abort(msg)
214 raise util.Abort(msg)
215
215
216 r = None
216 r = None
217 if repo:
217 if repo:
218 if cl:
218 if cl:
219 r = repo.changelog
219 r = repo.changelog
220 elif mf:
220 elif mf:
221 r = repo.manifest
221 r = repo.manifest
222 elif file_:
222 elif file_:
223 filelog = repo.file(file_)
223 filelog = repo.file(file_)
224 if len(filelog):
224 if len(filelog):
225 r = filelog
225 r = filelog
226 if not r:
226 if not r:
227 if not file_:
227 if not file_:
228 raise error.CommandError(cmd, _('invalid arguments'))
228 raise error.CommandError(cmd, _('invalid arguments'))
229 if not os.path.isfile(file_):
229 if not os.path.isfile(file_):
230 raise util.Abort(_("revlog '%s' not found") % file_)
230 raise util.Abort(_("revlog '%s' not found") % file_)
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
232 file_[:-2] + ".i")
232 file_[:-2] + ".i")
233 return r
233 return r
234
234
235 def copy(ui, repo, pats, opts, rename=False):
235 def copy(ui, repo, pats, opts, rename=False):
236 # called with the repo lock held
236 # called with the repo lock held
237 #
237 #
238 # hgsep => pathname that uses "/" to separate directories
238 # hgsep => pathname that uses "/" to separate directories
239 # ossep => pathname that uses os.sep to separate directories
239 # ossep => pathname that uses os.sep to separate directories
240 cwd = repo.getcwd()
240 cwd = repo.getcwd()
241 targets = {}
241 targets = {}
242 after = opts.get("after")
242 after = opts.get("after")
243 dryrun = opts.get("dry_run")
243 dryrun = opts.get("dry_run")
244 wctx = repo[None]
244 wctx = repo[None]
245
245
246 def walkpat(pat):
246 def walkpat(pat):
247 srcs = []
247 srcs = []
248 badstates = after and '?' or '?r'
248 badstates = after and '?' or '?r'
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
250 for abs in repo.walk(m):
250 for abs in repo.walk(m):
251 state = repo.dirstate[abs]
251 state = repo.dirstate[abs]
252 rel = m.rel(abs)
252 rel = m.rel(abs)
253 exact = m.exact(abs)
253 exact = m.exact(abs)
254 if state in badstates:
254 if state in badstates:
255 if exact and state == '?':
255 if exact and state == '?':
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
257 if exact and state == 'r':
257 if exact and state == 'r':
258 ui.warn(_('%s: not copying - file has been marked for'
258 ui.warn(_('%s: not copying - file has been marked for'
259 ' remove\n') % rel)
259 ' remove\n') % rel)
260 continue
260 continue
261 # abs: hgsep
261 # abs: hgsep
262 # rel: ossep
262 # rel: ossep
263 srcs.append((abs, rel, exact))
263 srcs.append((abs, rel, exact))
264 return srcs
264 return srcs
265
265
266 # abssrc: hgsep
266 # abssrc: hgsep
267 # relsrc: ossep
267 # relsrc: ossep
268 # otarget: ossep
268 # otarget: ossep
269 def copyfile(abssrc, relsrc, otarget, exact):
269 def copyfile(abssrc, relsrc, otarget, exact):
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
271 if '/' in abstarget:
271 if '/' in abstarget:
272 # We cannot normalize abstarget itself, this would prevent
272 # We cannot normalize abstarget itself, this would prevent
273 # case only renames, like a => A.
273 # case only renames, like a => A.
274 abspath, absname = abstarget.rsplit('/', 1)
274 abspath, absname = abstarget.rsplit('/', 1)
275 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
275 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
276 reltarget = repo.pathto(abstarget, cwd)
276 reltarget = repo.pathto(abstarget, cwd)
277 target = repo.wjoin(abstarget)
277 target = repo.wjoin(abstarget)
278 src = repo.wjoin(abssrc)
278 src = repo.wjoin(abssrc)
279 state = repo.dirstate[abstarget]
279 state = repo.dirstate[abstarget]
280
280
281 scmutil.checkportable(ui, abstarget)
281 scmutil.checkportable(ui, abstarget)
282
282
283 # check for collisions
283 # check for collisions
284 prevsrc = targets.get(abstarget)
284 prevsrc = targets.get(abstarget)
285 if prevsrc is not None:
285 if prevsrc is not None:
286 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
286 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
287 (reltarget, repo.pathto(abssrc, cwd),
287 (reltarget, repo.pathto(abssrc, cwd),
288 repo.pathto(prevsrc, cwd)))
288 repo.pathto(prevsrc, cwd)))
289 return
289 return
290
290
291 # check for overwrites
291 # check for overwrites
292 exists = os.path.lexists(target)
292 exists = os.path.lexists(target)
293 samefile = False
293 samefile = False
294 if exists and abssrc != abstarget:
294 if exists and abssrc != abstarget:
295 if (repo.dirstate.normalize(abssrc) ==
295 if (repo.dirstate.normalize(abssrc) ==
296 repo.dirstate.normalize(abstarget)):
296 repo.dirstate.normalize(abstarget)):
297 if not rename:
297 if not rename:
298 ui.warn(_("%s: can't copy - same file\n") % reltarget)
298 ui.warn(_("%s: can't copy - same file\n") % reltarget)
299 return
299 return
300 exists = False
300 exists = False
301 samefile = True
301 samefile = True
302
302
303 if not after and exists or after and state in 'mn':
303 if not after and exists or after and state in 'mn':
304 if not opts['force']:
304 if not opts['force']:
305 ui.warn(_('%s: not overwriting - file exists\n') %
305 ui.warn(_('%s: not overwriting - file exists\n') %
306 reltarget)
306 reltarget)
307 return
307 return
308
308
309 if after:
309 if after:
310 if not exists:
310 if not exists:
311 if rename:
311 if rename:
312 ui.warn(_('%s: not recording move - %s does not exist\n') %
312 ui.warn(_('%s: not recording move - %s does not exist\n') %
313 (relsrc, reltarget))
313 (relsrc, reltarget))
314 else:
314 else:
315 ui.warn(_('%s: not recording copy - %s does not exist\n') %
315 ui.warn(_('%s: not recording copy - %s does not exist\n') %
316 (relsrc, reltarget))
316 (relsrc, reltarget))
317 return
317 return
318 elif not dryrun:
318 elif not dryrun:
319 try:
319 try:
320 if exists:
320 if exists:
321 os.unlink(target)
321 os.unlink(target)
322 targetdir = os.path.dirname(target) or '.'
322 targetdir = os.path.dirname(target) or '.'
323 if not os.path.isdir(targetdir):
323 if not os.path.isdir(targetdir):
324 os.makedirs(targetdir)
324 os.makedirs(targetdir)
325 if samefile:
325 if samefile:
326 tmp = target + "~hgrename"
326 tmp = target + "~hgrename"
327 os.rename(src, tmp)
327 os.rename(src, tmp)
328 os.rename(tmp, target)
328 os.rename(tmp, target)
329 else:
329 else:
330 util.copyfile(src, target)
330 util.copyfile(src, target)
331 srcexists = True
331 srcexists = True
332 except IOError, inst:
332 except IOError, inst:
333 if inst.errno == errno.ENOENT:
333 if inst.errno == errno.ENOENT:
334 ui.warn(_('%s: deleted in working copy\n') % relsrc)
334 ui.warn(_('%s: deleted in working copy\n') % relsrc)
335 srcexists = False
335 srcexists = False
336 else:
336 else:
337 ui.warn(_('%s: cannot copy - %s\n') %
337 ui.warn(_('%s: cannot copy - %s\n') %
338 (relsrc, inst.strerror))
338 (relsrc, inst.strerror))
339 return True # report a failure
339 return True # report a failure
340
340
341 if ui.verbose or not exact:
341 if ui.verbose or not exact:
342 if rename:
342 if rename:
343 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
343 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
344 else:
344 else:
345 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
345 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
346
346
347 targets[abstarget] = abssrc
347 targets[abstarget] = abssrc
348
348
349 # fix up dirstate
349 # fix up dirstate
350 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
350 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
351 dryrun=dryrun, cwd=cwd)
351 dryrun=dryrun, cwd=cwd)
352 if rename and not dryrun:
352 if rename and not dryrun:
353 if not after and srcexists and not samefile:
353 if not after and srcexists and not samefile:
354 util.unlinkpath(repo.wjoin(abssrc))
354 util.unlinkpath(repo.wjoin(abssrc))
355 wctx.forget([abssrc])
355 wctx.forget([abssrc])
356
356
357 # pat: ossep
357 # pat: ossep
358 # dest ossep
358 # dest ossep
359 # srcs: list of (hgsep, hgsep, ossep, bool)
359 # srcs: list of (hgsep, hgsep, ossep, bool)
360 # return: function that takes hgsep and returns ossep
360 # return: function that takes hgsep and returns ossep
361 def targetpathfn(pat, dest, srcs):
361 def targetpathfn(pat, dest, srcs):
362 if os.path.isdir(pat):
362 if os.path.isdir(pat):
363 abspfx = scmutil.canonpath(repo.root, cwd, pat)
363 abspfx = scmutil.canonpath(repo.root, cwd, pat)
364 abspfx = util.localpath(abspfx)
364 abspfx = util.localpath(abspfx)
365 if destdirexists:
365 if destdirexists:
366 striplen = len(os.path.split(abspfx)[0])
366 striplen = len(os.path.split(abspfx)[0])
367 else:
367 else:
368 striplen = len(abspfx)
368 striplen = len(abspfx)
369 if striplen:
369 if striplen:
370 striplen += len(os.sep)
370 striplen += len(os.sep)
371 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
371 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
372 elif destdirexists:
372 elif destdirexists:
373 res = lambda p: os.path.join(dest,
373 res = lambda p: os.path.join(dest,
374 os.path.basename(util.localpath(p)))
374 os.path.basename(util.localpath(p)))
375 else:
375 else:
376 res = lambda p: dest
376 res = lambda p: dest
377 return res
377 return res
378
378
379 # pat: ossep
379 # pat: ossep
380 # dest ossep
380 # dest ossep
381 # srcs: list of (hgsep, hgsep, ossep, bool)
381 # srcs: list of (hgsep, hgsep, ossep, bool)
382 # return: function that takes hgsep and returns ossep
382 # return: function that takes hgsep and returns ossep
383 def targetpathafterfn(pat, dest, srcs):
383 def targetpathafterfn(pat, dest, srcs):
384 if matchmod.patkind(pat):
384 if matchmod.patkind(pat):
385 # a mercurial pattern
385 # a mercurial pattern
386 res = lambda p: os.path.join(dest,
386 res = lambda p: os.path.join(dest,
387 os.path.basename(util.localpath(p)))
387 os.path.basename(util.localpath(p)))
388 else:
388 else:
389 abspfx = scmutil.canonpath(repo.root, cwd, pat)
389 abspfx = scmutil.canonpath(repo.root, cwd, pat)
390 if len(abspfx) < len(srcs[0][0]):
390 if len(abspfx) < len(srcs[0][0]):
391 # A directory. Either the target path contains the last
391 # A directory. Either the target path contains the last
392 # component of the source path or it does not.
392 # component of the source path or it does not.
393 def evalpath(striplen):
393 def evalpath(striplen):
394 score = 0
394 score = 0
395 for s in srcs:
395 for s in srcs:
396 t = os.path.join(dest, util.localpath(s[0])[striplen:])
396 t = os.path.join(dest, util.localpath(s[0])[striplen:])
397 if os.path.lexists(t):
397 if os.path.lexists(t):
398 score += 1
398 score += 1
399 return score
399 return score
400
400
401 abspfx = util.localpath(abspfx)
401 abspfx = util.localpath(abspfx)
402 striplen = len(abspfx)
402 striplen = len(abspfx)
403 if striplen:
403 if striplen:
404 striplen += len(os.sep)
404 striplen += len(os.sep)
405 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
405 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
406 score = evalpath(striplen)
406 score = evalpath(striplen)
407 striplen1 = len(os.path.split(abspfx)[0])
407 striplen1 = len(os.path.split(abspfx)[0])
408 if striplen1:
408 if striplen1:
409 striplen1 += len(os.sep)
409 striplen1 += len(os.sep)
410 if evalpath(striplen1) > score:
410 if evalpath(striplen1) > score:
411 striplen = striplen1
411 striplen = striplen1
412 res = lambda p: os.path.join(dest,
412 res = lambda p: os.path.join(dest,
413 util.localpath(p)[striplen:])
413 util.localpath(p)[striplen:])
414 else:
414 else:
415 # a file
415 # a file
416 if destdirexists:
416 if destdirexists:
417 res = lambda p: os.path.join(dest,
417 res = lambda p: os.path.join(dest,
418 os.path.basename(util.localpath(p)))
418 os.path.basename(util.localpath(p)))
419 else:
419 else:
420 res = lambda p: dest
420 res = lambda p: dest
421 return res
421 return res
422
422
423
423
424 pats = scmutil.expandpats(pats)
424 pats = scmutil.expandpats(pats)
425 if not pats:
425 if not pats:
426 raise util.Abort(_('no source or destination specified'))
426 raise util.Abort(_('no source or destination specified'))
427 if len(pats) == 1:
427 if len(pats) == 1:
428 raise util.Abort(_('no destination specified'))
428 raise util.Abort(_('no destination specified'))
429 dest = pats.pop()
429 dest = pats.pop()
430 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
430 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
431 if not destdirexists:
431 if not destdirexists:
432 if len(pats) > 1 or matchmod.patkind(pats[0]):
432 if len(pats) > 1 or matchmod.patkind(pats[0]):
433 raise util.Abort(_('with multiple sources, destination must be an '
433 raise util.Abort(_('with multiple sources, destination must be an '
434 'existing directory'))
434 'existing directory'))
435 if util.endswithsep(dest):
435 if util.endswithsep(dest):
436 raise util.Abort(_('destination %s is not a directory') % dest)
436 raise util.Abort(_('destination %s is not a directory') % dest)
437
437
438 tfn = targetpathfn
438 tfn = targetpathfn
439 if after:
439 if after:
440 tfn = targetpathafterfn
440 tfn = targetpathafterfn
441 copylist = []
441 copylist = []
442 for pat in pats:
442 for pat in pats:
443 srcs = walkpat(pat)
443 srcs = walkpat(pat)
444 if not srcs:
444 if not srcs:
445 continue
445 continue
446 copylist.append((tfn(pat, dest, srcs), srcs))
446 copylist.append((tfn(pat, dest, srcs), srcs))
447 if not copylist:
447 if not copylist:
448 raise util.Abort(_('no files to copy'))
448 raise util.Abort(_('no files to copy'))
449
449
450 errors = 0
450 errors = 0
451 for targetpath, srcs in copylist:
451 for targetpath, srcs in copylist:
452 for abssrc, relsrc, exact in srcs:
452 for abssrc, relsrc, exact in srcs:
453 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
453 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
454 errors += 1
454 errors += 1
455
455
456 if errors:
456 if errors:
457 ui.warn(_('(consider using --after)\n'))
457 ui.warn(_('(consider using --after)\n'))
458
458
459 return errors != 0
459 return errors != 0
460
460
461 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
461 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
462 runargs=None, appendpid=False):
462 runargs=None, appendpid=False):
463 '''Run a command as a service.'''
463 '''Run a command as a service.'''
464
464
465 if opts['daemon'] and not opts['daemon_pipefds']:
465 if opts['daemon'] and not opts['daemon_pipefds']:
466 # Signal child process startup with file removal
466 # Signal child process startup with file removal
467 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
467 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
468 os.close(lockfd)
468 os.close(lockfd)
469 try:
469 try:
470 if not runargs:
470 if not runargs:
471 runargs = util.hgcmd() + sys.argv[1:]
471 runargs = util.hgcmd() + sys.argv[1:]
472 runargs.append('--daemon-pipefds=%s' % lockpath)
472 runargs.append('--daemon-pipefds=%s' % lockpath)
473 # Don't pass --cwd to the child process, because we've already
473 # Don't pass --cwd to the child process, because we've already
474 # changed directory.
474 # changed directory.
475 for i in xrange(1, len(runargs)):
475 for i in xrange(1, len(runargs)):
476 if runargs[i].startswith('--cwd='):
476 if runargs[i].startswith('--cwd='):
477 del runargs[i]
477 del runargs[i]
478 break
478 break
479 elif runargs[i].startswith('--cwd'):
479 elif runargs[i].startswith('--cwd'):
480 del runargs[i:i + 2]
480 del runargs[i:i + 2]
481 break
481 break
482 def condfn():
482 def condfn():
483 return not os.path.exists(lockpath)
483 return not os.path.exists(lockpath)
484 pid = util.rundetached(runargs, condfn)
484 pid = util.rundetached(runargs, condfn)
485 if pid < 0:
485 if pid < 0:
486 raise util.Abort(_('child process failed to start'))
486 raise util.Abort(_('child process failed to start'))
487 finally:
487 finally:
488 try:
488 try:
489 os.unlink(lockpath)
489 os.unlink(lockpath)
490 except OSError, e:
490 except OSError, e:
491 if e.errno != errno.ENOENT:
491 if e.errno != errno.ENOENT:
492 raise
492 raise
493 if parentfn:
493 if parentfn:
494 return parentfn(pid)
494 return parentfn(pid)
495 else:
495 else:
496 return
496 return
497
497
498 if initfn:
498 if initfn:
499 initfn()
499 initfn()
500
500
501 if opts['pid_file']:
501 if opts['pid_file']:
502 mode = appendpid and 'a' or 'w'
502 mode = appendpid and 'a' or 'w'
503 fp = open(opts['pid_file'], mode)
503 fp = open(opts['pid_file'], mode)
504 fp.write(str(os.getpid()) + '\n')
504 fp.write(str(os.getpid()) + '\n')
505 fp.close()
505 fp.close()
506
506
507 if opts['daemon_pipefds']:
507 if opts['daemon_pipefds']:
508 lockpath = opts['daemon_pipefds']
508 lockpath = opts['daemon_pipefds']
509 try:
509 try:
510 os.setsid()
510 os.setsid()
511 except AttributeError:
511 except AttributeError:
512 pass
512 pass
513 os.unlink(lockpath)
513 os.unlink(lockpath)
514 util.hidewindow()
514 util.hidewindow()
515 sys.stdout.flush()
515 sys.stdout.flush()
516 sys.stderr.flush()
516 sys.stderr.flush()
517
517
518 nullfd = os.open(util.nulldev, os.O_RDWR)
518 nullfd = os.open(util.nulldev, os.O_RDWR)
519 logfilefd = nullfd
519 logfilefd = nullfd
520 if logfile:
520 if logfile:
521 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
521 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
522 os.dup2(nullfd, 0)
522 os.dup2(nullfd, 0)
523 os.dup2(logfilefd, 1)
523 os.dup2(logfilefd, 1)
524 os.dup2(logfilefd, 2)
524 os.dup2(logfilefd, 2)
525 if nullfd not in (0, 1, 2):
525 if nullfd not in (0, 1, 2):
526 os.close(nullfd)
526 os.close(nullfd)
527 if logfile and logfilefd not in (0, 1, 2):
527 if logfile and logfilefd not in (0, 1, 2):
528 os.close(logfilefd)
528 os.close(logfilefd)
529
529
530 if runfn:
530 if runfn:
531 return runfn()
531 return runfn()
532
532
533 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
533 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
534 opts=None):
534 opts=None):
535 '''export changesets as hg patches.'''
535 '''export changesets as hg patches.'''
536
536
537 total = len(revs)
537 total = len(revs)
538 revwidth = max([len(str(rev)) for rev in revs])
538 revwidth = max([len(str(rev)) for rev in revs])
539
539
540 def single(rev, seqno, fp):
540 def single(rev, seqno, fp):
541 ctx = repo[rev]
541 ctx = repo[rev]
542 node = ctx.node()
542 node = ctx.node()
543 parents = [p.node() for p in ctx.parents() if p]
543 parents = [p.node() for p in ctx.parents() if p]
544 branch = ctx.branch()
544 branch = ctx.branch()
545 if switch_parent:
545 if switch_parent:
546 parents.reverse()
546 parents.reverse()
547 prev = (parents and parents[0]) or nullid
547 prev = (parents and parents[0]) or nullid
548
548
549 shouldclose = False
549 shouldclose = False
550 if not fp:
550 if not fp:
551 desc_lines = ctx.description().rstrip().split('\n')
551 desc_lines = ctx.description().rstrip().split('\n')
552 desc = desc_lines[0] #Commit always has a first line.
552 desc = desc_lines[0] #Commit always has a first line.
553 fp = makefileobj(repo, template, node, desc=desc, total=total,
553 fp = makefileobj(repo, template, node, desc=desc, total=total,
554 seqno=seqno, revwidth=revwidth, mode='ab')
554 seqno=seqno, revwidth=revwidth, mode='ab')
555 if fp != template:
555 if fp != template:
556 shouldclose = True
556 shouldclose = True
557 if fp != sys.stdout and util.safehasattr(fp, 'name'):
557 if fp != sys.stdout and util.safehasattr(fp, 'name'):
558 repo.ui.note("%s\n" % fp.name)
558 repo.ui.note("%s\n" % fp.name)
559
559
560 fp.write("# HG changeset patch\n")
560 fp.write("# HG changeset patch\n")
561 fp.write("# User %s\n" % ctx.user())
561 fp.write("# User %s\n" % ctx.user())
562 fp.write("# Date %d %d\n" % ctx.date())
562 fp.write("# Date %d %d\n" % ctx.date())
563 if branch and branch != 'default':
563 if branch and branch != 'default':
564 fp.write("# Branch %s\n" % branch)
564 fp.write("# Branch %s\n" % branch)
565 fp.write("# Node ID %s\n" % hex(node))
565 fp.write("# Node ID %s\n" % hex(node))
566 fp.write("# Parent %s\n" % hex(prev))
566 fp.write("# Parent %s\n" % hex(prev))
567 if len(parents) > 1:
567 if len(parents) > 1:
568 fp.write("# Parent %s\n" % hex(parents[1]))
568 fp.write("# Parent %s\n" % hex(parents[1]))
569 fp.write(ctx.description().rstrip())
569 fp.write(ctx.description().rstrip())
570 fp.write("\n\n")
570 fp.write("\n\n")
571
571
572 for chunk in patch.diff(repo, prev, node, opts=opts):
572 for chunk in patch.diff(repo, prev, node, opts=opts):
573 fp.write(chunk)
573 fp.write(chunk)
574
574
575 if shouldclose:
575 if shouldclose:
576 fp.close()
576 fp.close()
577
577
578 for seqno, rev in enumerate(revs):
578 for seqno, rev in enumerate(revs):
579 single(rev, seqno + 1, fp)
579 single(rev, seqno + 1, fp)
580
580
581 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
581 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
582 changes=None, stat=False, fp=None, prefix='',
582 changes=None, stat=False, fp=None, prefix='',
583 listsubrepos=False):
583 listsubrepos=False):
584 '''show diff or diffstat.'''
584 '''show diff or diffstat.'''
585 if fp is None:
585 if fp is None:
586 write = ui.write
586 write = ui.write
587 else:
587 else:
588 def write(s, **kw):
588 def write(s, **kw):
589 fp.write(s)
589 fp.write(s)
590
590
591 if stat:
591 if stat:
592 diffopts = diffopts.copy(context=0)
592 diffopts = diffopts.copy(context=0)
593 width = 80
593 width = 80
594 if not ui.plain():
594 if not ui.plain():
595 width = ui.termwidth()
595 width = ui.termwidth()
596 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
596 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
597 prefix=prefix)
597 prefix=prefix)
598 for chunk, label in patch.diffstatui(util.iterlines(chunks),
598 for chunk, label in patch.diffstatui(util.iterlines(chunks),
599 width=width,
599 width=width,
600 git=diffopts.git):
600 git=diffopts.git):
601 write(chunk, label=label)
601 write(chunk, label=label)
602 else:
602 else:
603 for chunk, label in patch.diffui(repo, node1, node2, match,
603 for chunk, label in patch.diffui(repo, node1, node2, match,
604 changes, diffopts, prefix=prefix):
604 changes, diffopts, prefix=prefix):
605 write(chunk, label=label)
605 write(chunk, label=label)
606
606
607 if listsubrepos:
607 if listsubrepos:
608 ctx1 = repo[node1]
608 ctx1 = repo[node1]
609 ctx2 = repo[node2]
609 ctx2 = repo[node2]
610 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
610 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
611 tempnode2 = node2
611 tempnode2 = node2
612 try:
612 try:
613 if node2 is not None:
613 if node2 is not None:
614 tempnode2 = ctx2.substate[subpath][1]
614 tempnode2 = ctx2.substate[subpath][1]
615 except KeyError:
615 except KeyError:
616 # A subrepo that existed in node1 was deleted between node1 and
616 # A subrepo that existed in node1 was deleted between node1 and
617 # node2 (inclusive). Thus, ctx2's substate won't contain that
617 # node2 (inclusive). Thus, ctx2's substate won't contain that
618 # subpath. The best we can do is to ignore it.
618 # subpath. The best we can do is to ignore it.
619 tempnode2 = None
619 tempnode2 = None
620 submatch = matchmod.narrowmatcher(subpath, match)
620 submatch = matchmod.narrowmatcher(subpath, match)
621 sub.diff(diffopts, tempnode2, submatch, changes=changes,
621 sub.diff(diffopts, tempnode2, submatch, changes=changes,
622 stat=stat, fp=fp, prefix=prefix)
622 stat=stat, fp=fp, prefix=prefix)
623
623
624 class changeset_printer(object):
624 class changeset_printer(object):
625 '''show changeset information when templating not requested.'''
625 '''show changeset information when templating not requested.'''
626
626
627 def __init__(self, ui, repo, patch, diffopts, buffered):
627 def __init__(self, ui, repo, patch, diffopts, buffered):
628 self.ui = ui
628 self.ui = ui
629 self.repo = repo
629 self.repo = repo
630 self.buffered = buffered
630 self.buffered = buffered
631 self.patch = patch
631 self.patch = patch
632 self.diffopts = diffopts
632 self.diffopts = diffopts
633 self.header = {}
633 self.header = {}
634 self.hunk = {}
634 self.hunk = {}
635 self.lastheader = None
635 self.lastheader = None
636 self.footer = None
636 self.footer = None
637
637
638 def flush(self, rev):
638 def flush(self, rev):
639 if rev in self.header:
639 if rev in self.header:
640 h = self.header[rev]
640 h = self.header[rev]
641 if h != self.lastheader:
641 if h != self.lastheader:
642 self.lastheader = h
642 self.lastheader = h
643 self.ui.write(h)
643 self.ui.write(h)
644 del self.header[rev]
644 del self.header[rev]
645 if rev in self.hunk:
645 if rev in self.hunk:
646 self.ui.write(self.hunk[rev])
646 self.ui.write(self.hunk[rev])
647 del self.hunk[rev]
647 del self.hunk[rev]
648 return 1
648 return 1
649 return 0
649 return 0
650
650
651 def close(self):
651 def close(self):
652 if self.footer:
652 if self.footer:
653 self.ui.write(self.footer)
653 self.ui.write(self.footer)
654
654
655 def show(self, ctx, copies=None, matchfn=None, **props):
655 def show(self, ctx, copies=None, matchfn=None, **props):
656 if self.buffered:
656 if self.buffered:
657 self.ui.pushbuffer()
657 self.ui.pushbuffer()
658 self._show(ctx, copies, matchfn, props)
658 self._show(ctx, copies, matchfn, props)
659 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
659 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
660 else:
660 else:
661 self._show(ctx, copies, matchfn, props)
661 self._show(ctx, copies, matchfn, props)
662
662
663 def _show(self, ctx, copies, matchfn, props):
663 def _show(self, ctx, copies, matchfn, props):
664 '''show a single changeset or file revision'''
664 '''show a single changeset or file revision'''
665 changenode = ctx.node()
665 changenode = ctx.node()
666 rev = ctx.rev()
666 rev = ctx.rev()
667
667
668 if self.ui.quiet:
668 if self.ui.quiet:
669 self.ui.write("%d:%s\n" % (rev, short(changenode)),
669 self.ui.write("%d:%s\n" % (rev, short(changenode)),
670 label='log.node')
670 label='log.node')
671 return
671 return
672
672
673 log = self.repo.changelog
673 log = self.repo.changelog
674 date = util.datestr(ctx.date())
674 date = util.datestr(ctx.date())
675
675
676 hexfunc = self.ui.debugflag and hex or short
676 hexfunc = self.ui.debugflag and hex or short
677
677
678 parents = [(p, hexfunc(log.node(p)))
678 parents = [(p, hexfunc(log.node(p)))
679 for p in self._meaningful_parentrevs(log, rev)]
679 for p in self._meaningful_parentrevs(log, rev)]
680
680
681 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
681 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
682 label='log.changeset')
682 label='log.changeset')
683
683
684 branch = ctx.branch()
684 branch = ctx.branch()
685 # don't show the default branch name
685 # don't show the default branch name
686 if branch != 'default':
686 if branch != 'default':
687 self.ui.write(_("branch: %s\n") % branch,
687 self.ui.write(_("branch: %s\n") % branch,
688 label='log.branch')
688 label='log.branch')
689 for bookmark in self.repo.nodebookmarks(changenode):
689 for bookmark in self.repo.nodebookmarks(changenode):
690 self.ui.write(_("bookmark: %s\n") % bookmark,
690 self.ui.write(_("bookmark: %s\n") % bookmark,
691 label='log.bookmark')
691 label='log.bookmark')
692 for tag in self.repo.nodetags(changenode):
692 for tag in self.repo.nodetags(changenode):
693 self.ui.write(_("tag: %s\n") % tag,
693 self.ui.write(_("tag: %s\n") % tag,
694 label='log.tag')
694 label='log.tag')
695 if self.ui.debugflag and ctx.phase():
695 if self.ui.debugflag and ctx.phase():
696 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
696 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
697 label='log.phase')
697 label='log.phase')
698 for parent in parents:
698 for parent in parents:
699 self.ui.write(_("parent: %d:%s\n") % parent,
699 self.ui.write(_("parent: %d:%s\n") % parent,
700 label='log.parent')
700 label='log.parent')
701
701
702 if self.ui.debugflag:
702 if self.ui.debugflag:
703 mnode = ctx.manifestnode()
703 mnode = ctx.manifestnode()
704 self.ui.write(_("manifest: %d:%s\n") %
704 self.ui.write(_("manifest: %d:%s\n") %
705 (self.repo.manifest.rev(mnode), hex(mnode)),
705 (self.repo.manifest.rev(mnode), hex(mnode)),
706 label='ui.debug log.manifest')
706 label='ui.debug log.manifest')
707 self.ui.write(_("user: %s\n") % ctx.user(),
707 self.ui.write(_("user: %s\n") % ctx.user(),
708 label='log.user')
708 label='log.user')
709 self.ui.write(_("date: %s\n") % date,
709 self.ui.write(_("date: %s\n") % date,
710 label='log.date')
710 label='log.date')
711
711
712 if self.ui.debugflag:
712 if self.ui.debugflag:
713 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
713 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
714 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
714 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
715 files):
715 files):
716 if value:
716 if value:
717 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
717 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
718 label='ui.debug log.files')
718 label='ui.debug log.files')
719 elif ctx.files() and self.ui.verbose:
719 elif ctx.files() and self.ui.verbose:
720 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
720 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
721 label='ui.note log.files')
721 label='ui.note log.files')
722 if copies and self.ui.verbose:
722 if copies and self.ui.verbose:
723 copies = ['%s (%s)' % c for c in copies]
723 copies = ['%s (%s)' % c for c in copies]
724 self.ui.write(_("copies: %s\n") % ' '.join(copies),
724 self.ui.write(_("copies: %s\n") % ' '.join(copies),
725 label='ui.note log.copies')
725 label='ui.note log.copies')
726
726
727 extra = ctx.extra()
727 extra = ctx.extra()
728 if extra and self.ui.debugflag:
728 if extra and self.ui.debugflag:
729 for key, value in sorted(extra.items()):
729 for key, value in sorted(extra.items()):
730 self.ui.write(_("extra: %s=%s\n")
730 self.ui.write(_("extra: %s=%s\n")
731 % (key, value.encode('string_escape')),
731 % (key, value.encode('string_escape')),
732 label='ui.debug log.extra')
732 label='ui.debug log.extra')
733
733
734 description = ctx.description().strip()
734 description = ctx.description().strip()
735 if description:
735 if description:
736 if self.ui.verbose:
736 if self.ui.verbose:
737 self.ui.write(_("description:\n"),
737 self.ui.write(_("description:\n"),
738 label='ui.note log.description')
738 label='ui.note log.description')
739 self.ui.write(description,
739 self.ui.write(description,
740 label='ui.note log.description')
740 label='ui.note log.description')
741 self.ui.write("\n\n")
741 self.ui.write("\n\n")
742 else:
742 else:
743 self.ui.write(_("summary: %s\n") %
743 self.ui.write(_("summary: %s\n") %
744 description.splitlines()[0],
744 description.splitlines()[0],
745 label='log.summary')
745 label='log.summary')
746 self.ui.write("\n")
746 self.ui.write("\n")
747
747
748 self.showpatch(changenode, matchfn)
748 self.showpatch(changenode, matchfn)
749
749
750 def showpatch(self, node, matchfn):
750 def showpatch(self, node, matchfn):
751 if not matchfn:
751 if not matchfn:
752 matchfn = self.patch
752 matchfn = self.patch
753 if matchfn:
753 if matchfn:
754 stat = self.diffopts.get('stat')
754 stat = self.diffopts.get('stat')
755 diff = self.diffopts.get('patch')
755 diff = self.diffopts.get('patch')
756 diffopts = patch.diffopts(self.ui, self.diffopts)
756 diffopts = patch.diffopts(self.ui, self.diffopts)
757 prev = self.repo.changelog.parents(node)[0]
757 prev = self.repo.changelog.parents(node)[0]
758 if stat:
758 if stat:
759 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
759 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
760 match=matchfn, stat=True)
760 match=matchfn, stat=True)
761 if diff:
761 if diff:
762 if stat:
762 if stat:
763 self.ui.write("\n")
763 self.ui.write("\n")
764 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
764 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
765 match=matchfn, stat=False)
765 match=matchfn, stat=False)
766 self.ui.write("\n")
766 self.ui.write("\n")
767
767
768 def _meaningful_parentrevs(self, log, rev):
768 def _meaningful_parentrevs(self, log, rev):
769 """Return list of meaningful (or all if debug) parentrevs for rev.
769 """Return list of meaningful (or all if debug) parentrevs for rev.
770
770
771 For merges (two non-nullrev revisions) both parents are meaningful.
771 For merges (two non-nullrev revisions) both parents are meaningful.
772 Otherwise the first parent revision is considered meaningful if it
772 Otherwise the first parent revision is considered meaningful if it
773 is not the preceding revision.
773 is not the preceding revision.
774 """
774 """
775 parents = log.parentrevs(rev)
775 parents = log.parentrevs(rev)
776 if not self.ui.debugflag and parents[1] == nullrev:
776 if not self.ui.debugflag and parents[1] == nullrev:
777 if parents[0] >= rev - 1:
777 if parents[0] >= rev - 1:
778 parents = []
778 parents = []
779 else:
779 else:
780 parents = [parents[0]]
780 parents = [parents[0]]
781 return parents
781 return parents
782
782
783
783
784 class changeset_templater(changeset_printer):
784 class changeset_templater(changeset_printer):
785 '''format changeset information.'''
785 '''format changeset information.'''
786
786
787 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
787 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
788 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
788 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
789 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
789 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
790 defaulttempl = {
790 defaulttempl = {
791 'parent': '{rev}:{node|formatnode} ',
791 'parent': '{rev}:{node|formatnode} ',
792 'manifest': '{rev}:{node|formatnode}',
792 'manifest': '{rev}:{node|formatnode}',
793 'file_copy': '{name} ({source})',
793 'file_copy': '{name} ({source})',
794 'extra': '{key}={value|stringescape}'
794 'extra': '{key}={value|stringescape}'
795 }
795 }
796 # filecopy is preserved for compatibility reasons
796 # filecopy is preserved for compatibility reasons
797 defaulttempl['filecopy'] = defaulttempl['file_copy']
797 defaulttempl['filecopy'] = defaulttempl['file_copy']
798 self.t = templater.templater(mapfile, {'formatnode': formatnode},
798 self.t = templater.templater(mapfile, {'formatnode': formatnode},
799 cache=defaulttempl)
799 cache=defaulttempl)
800 self.cache = {}
800 self.cache = {}
801
801
802 def use_template(self, t):
802 def use_template(self, t):
803 '''set template string to use'''
803 '''set template string to use'''
804 self.t.cache['changeset'] = t
804 self.t.cache['changeset'] = t
805
805
806 def _meaningful_parentrevs(self, ctx):
806 def _meaningful_parentrevs(self, ctx):
807 """Return list of meaningful (or all if debug) parentrevs for rev.
807 """Return list of meaningful (or all if debug) parentrevs for rev.
808 """
808 """
809 parents = ctx.parents()
809 parents = ctx.parents()
810 if len(parents) > 1:
810 if len(parents) > 1:
811 return parents
811 return parents
812 if self.ui.debugflag:
812 if self.ui.debugflag:
813 return [parents[0], self.repo['null']]
813 return [parents[0], self.repo['null']]
814 if parents[0].rev() >= ctx.rev() - 1:
814 if parents[0].rev() >= ctx.rev() - 1:
815 return []
815 return []
816 return parents
816 return parents
817
817
818 def _show(self, ctx, copies, matchfn, props):
818 def _show(self, ctx, copies, matchfn, props):
819 '''show a single changeset or file revision'''
819 '''show a single changeset or file revision'''
820
820
821 showlist = templatekw.showlist
821 showlist = templatekw.showlist
822
822
823 # showparents() behaviour depends on ui trace level which
823 # showparents() behaviour depends on ui trace level which
824 # causes unexpected behaviours at templating level and makes
824 # causes unexpected behaviours at templating level and makes
825 # it harder to extract it in a standalone function. Its
825 # it harder to extract it in a standalone function. Its
826 # behaviour cannot be changed so leave it here for now.
826 # behaviour cannot be changed so leave it here for now.
827 def showparents(**args):
827 def showparents(**args):
828 ctx = args['ctx']
828 ctx = args['ctx']
829 parents = [[('rev', p.rev()), ('node', p.hex())]
829 parents = [[('rev', p.rev()), ('node', p.hex())]
830 for p in self._meaningful_parentrevs(ctx)]
830 for p in self._meaningful_parentrevs(ctx)]
831 return showlist('parent', parents, **args)
831 return showlist('parent', parents, **args)
832
832
833 props = props.copy()
833 props = props.copy()
834 props.update(templatekw.keywords)
834 props.update(templatekw.keywords)
835 props['parents'] = showparents
835 props['parents'] = showparents
836 props['templ'] = self.t
836 props['templ'] = self.t
837 props['ctx'] = ctx
837 props['ctx'] = ctx
838 props['repo'] = self.repo
838 props['repo'] = self.repo
839 props['revcache'] = {'copies': copies}
839 props['revcache'] = {'copies': copies}
840 props['cache'] = self.cache
840 props['cache'] = self.cache
841
841
842 # find correct templates for current mode
842 # find correct templates for current mode
843
843
844 tmplmodes = [
844 tmplmodes = [
845 (True, None),
845 (True, None),
846 (self.ui.verbose, 'verbose'),
846 (self.ui.verbose, 'verbose'),
847 (self.ui.quiet, 'quiet'),
847 (self.ui.quiet, 'quiet'),
848 (self.ui.debugflag, 'debug'),
848 (self.ui.debugflag, 'debug'),
849 ]
849 ]
850
850
851 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
851 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
852 for mode, postfix in tmplmodes:
852 for mode, postfix in tmplmodes:
853 for type in types:
853 for type in types:
854 cur = postfix and ('%s_%s' % (type, postfix)) or type
854 cur = postfix and ('%s_%s' % (type, postfix)) or type
855 if mode and cur in self.t:
855 if mode and cur in self.t:
856 types[type] = cur
856 types[type] = cur
857
857
858 try:
858 try:
859
859
860 # write header
860 # write header
861 if types['header']:
861 if types['header']:
862 h = templater.stringify(self.t(types['header'], **props))
862 h = templater.stringify(self.t(types['header'], **props))
863 if self.buffered:
863 if self.buffered:
864 self.header[ctx.rev()] = h
864 self.header[ctx.rev()] = h
865 else:
865 else:
866 if self.lastheader != h:
866 if self.lastheader != h:
867 self.lastheader = h
867 self.lastheader = h
868 self.ui.write(h)
868 self.ui.write(h)
869
869
870 # write changeset metadata, then patch if requested
870 # write changeset metadata, then patch if requested
871 key = types['changeset']
871 key = types['changeset']
872 self.ui.write(templater.stringify(self.t(key, **props)))
872 self.ui.write(templater.stringify(self.t(key, **props)))
873 self.showpatch(ctx.node(), matchfn)
873 self.showpatch(ctx.node(), matchfn)
874
874
875 if types['footer']:
875 if types['footer']:
876 if not self.footer:
876 if not self.footer:
877 self.footer = templater.stringify(self.t(types['footer'],
877 self.footer = templater.stringify(self.t(types['footer'],
878 **props))
878 **props))
879
879
880 except KeyError, inst:
880 except KeyError, inst:
881 msg = _("%s: no key named '%s'")
881 msg = _("%s: no key named '%s'")
882 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
882 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
883 except SyntaxError, inst:
883 except SyntaxError, inst:
884 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
884 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
885
885
886 def show_changeset(ui, repo, opts, buffered=False):
886 def show_changeset(ui, repo, opts, buffered=False):
887 """show one changeset using template or regular display.
887 """show one changeset using template or regular display.
888
888
889 Display format will be the first non-empty hit of:
889 Display format will be the first non-empty hit of:
890 1. option 'template'
890 1. option 'template'
891 2. option 'style'
891 2. option 'style'
892 3. [ui] setting 'logtemplate'
892 3. [ui] setting 'logtemplate'
893 4. [ui] setting 'style'
893 4. [ui] setting 'style'
894 If all of these values are either the unset or the empty string,
894 If all of these values are either the unset or the empty string,
895 regular display via changeset_printer() is done.
895 regular display via changeset_printer() is done.
896 """
896 """
897 # options
897 # options
898 patch = False
898 patch = False
899 if opts.get('patch') or opts.get('stat'):
899 if opts.get('patch') or opts.get('stat'):
900 patch = scmutil.matchall(repo)
900 patch = scmutil.matchall(repo)
901
901
902 tmpl = opts.get('template')
902 tmpl = opts.get('template')
903 style = None
903 style = None
904 if tmpl:
904 if tmpl:
905 tmpl = templater.parsestring(tmpl, quoted=False)
905 tmpl = templater.parsestring(tmpl, quoted=False)
906 else:
906 else:
907 style = opts.get('style')
907 style = opts.get('style')
908
908
909 # ui settings
909 # ui settings
910 if not (tmpl or style):
910 if not (tmpl or style):
911 tmpl = ui.config('ui', 'logtemplate')
911 tmpl = ui.config('ui', 'logtemplate')
912 if tmpl:
912 if tmpl:
913 tmpl = templater.parsestring(tmpl)
913 tmpl = templater.parsestring(tmpl)
914 else:
914 else:
915 style = util.expandpath(ui.config('ui', 'style', ''))
915 style = util.expandpath(ui.config('ui', 'style', ''))
916
916
917 if not (tmpl or style):
917 if not (tmpl or style):
918 return changeset_printer(ui, repo, patch, opts, buffered)
918 return changeset_printer(ui, repo, patch, opts, buffered)
919
919
920 mapfile = None
920 mapfile = None
921 if style and not tmpl:
921 if style and not tmpl:
922 mapfile = style
922 mapfile = style
923 if not os.path.split(mapfile)[0]:
923 if not os.path.split(mapfile)[0]:
924 mapname = (templater.templatepath('map-cmdline.' + mapfile)
924 mapname = (templater.templatepath('map-cmdline.' + mapfile)
925 or templater.templatepath(mapfile))
925 or templater.templatepath(mapfile))
926 if mapname:
926 if mapname:
927 mapfile = mapname
927 mapfile = mapname
928
928
929 try:
929 try:
930 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
930 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
931 except SyntaxError, inst:
931 except SyntaxError, inst:
932 raise util.Abort(inst.args[0])
932 raise util.Abort(inst.args[0])
933 if tmpl:
933 if tmpl:
934 t.use_template(tmpl)
934 t.use_template(tmpl)
935 return t
935 return t
936
936
937 def finddate(ui, repo, date):
937 def finddate(ui, repo, date):
938 """Find the tipmost changeset that matches the given date spec"""
938 """Find the tipmost changeset that matches the given date spec"""
939
939
940 df = util.matchdate(date)
940 df = util.matchdate(date)
941 m = scmutil.matchall(repo)
941 m = scmutil.matchall(repo)
942 results = {}
942 results = {}
943
943
944 def prep(ctx, fns):
944 def prep(ctx, fns):
945 d = ctx.date()
945 d = ctx.date()
946 if df(d[0]):
946 if df(d[0]):
947 results[ctx.rev()] = d
947 results[ctx.rev()] = d
948
948
949 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
949 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
950 rev = ctx.rev()
950 rev = ctx.rev()
951 if rev in results:
951 if rev in results:
952 ui.status(_("Found revision %s from %s\n") %
952 ui.status(_("Found revision %s from %s\n") %
953 (rev, util.datestr(results[rev])))
953 (rev, util.datestr(results[rev])))
954 return str(rev)
954 return str(rev)
955
955
956 raise util.Abort(_("revision matching date not found"))
956 raise util.Abort(_("revision matching date not found"))
957
957
958 def walkchangerevs(repo, match, opts, prepare):
958 def walkchangerevs(repo, match, opts, prepare):
959 '''Iterate over files and the revs in which they changed.
959 '''Iterate over files and the revs in which they changed.
960
960
961 Callers most commonly need to iterate backwards over the history
961 Callers most commonly need to iterate backwards over the history
962 in which they are interested. Doing so has awful (quadratic-looking)
962 in which they are interested. Doing so has awful (quadratic-looking)
963 performance, so we use iterators in a "windowed" way.
963 performance, so we use iterators in a "windowed" way.
964
964
965 We walk a window of revisions in the desired order. Within the
965 We walk a window of revisions in the desired order. Within the
966 window, we first walk forwards to gather data, then in the desired
966 window, we first walk forwards to gather data, then in the desired
967 order (usually backwards) to display it.
967 order (usually backwards) to display it.
968
968
969 This function returns an iterator yielding contexts. Before
969 This function returns an iterator yielding contexts. Before
970 yielding each context, the iterator will first call the prepare
970 yielding each context, the iterator will first call the prepare
971 function on each context in the window in forward order.'''
971 function on each context in the window in forward order.'''
972
972
973 def increasing_windows(start, end, windowsize=8, sizelimit=512):
973 def increasing_windows(start, end, windowsize=8, sizelimit=512):
974 if start < end:
974 if start < end:
975 while start < end:
975 while start < end:
976 yield start, min(windowsize, end - start)
976 yield start, min(windowsize, end - start)
977 start += windowsize
977 start += windowsize
978 if windowsize < sizelimit:
978 if windowsize < sizelimit:
979 windowsize *= 2
979 windowsize *= 2
980 else:
980 else:
981 while start > end:
981 while start > end:
982 yield start, min(windowsize, start - end - 1)
982 yield start, min(windowsize, start - end - 1)
983 start -= windowsize
983 start -= windowsize
984 if windowsize < sizelimit:
984 if windowsize < sizelimit:
985 windowsize *= 2
985 windowsize *= 2
986
986
987 follow = opts.get('follow') or opts.get('follow_first')
987 follow = opts.get('follow') or opts.get('follow_first')
988
988
989 if not len(repo):
989 if not len(repo):
990 return []
990 return []
991
991
992 if follow:
992 if follow:
993 defrange = '%s:0' % repo['.'].rev()
993 defrange = '%s:0' % repo['.'].rev()
994 else:
994 else:
995 defrange = '-1:0'
995 defrange = '-1:0'
996 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
996 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
997 if not revs:
997 if not revs:
998 return []
998 return []
999 wanted = set()
999 wanted = set()
1000 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1000 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1001 fncache = {}
1001 fncache = {}
1002 change = repo.changectx
1002 change = repo.changectx
1003
1003
1004 # First step is to fill wanted, the set of revisions that we want to yield.
1004 # First step is to fill wanted, the set of revisions that we want to yield.
1005 # When it does not induce extra cost, we also fill fncache for revisions in
1005 # When it does not induce extra cost, we also fill fncache for revisions in
1006 # wanted: a cache of filenames that were changed (ctx.files()) and that
1006 # wanted: a cache of filenames that were changed (ctx.files()) and that
1007 # match the file filtering conditions.
1007 # match the file filtering conditions.
1008
1008
1009 if not slowpath and not match.files():
1009 if not slowpath and not match.files():
1010 # No files, no patterns. Display all revs.
1010 # No files, no patterns. Display all revs.
1011 wanted = set(revs)
1011 wanted = set(revs)
1012 copies = []
1012 copies = []
1013
1013
1014 if not slowpath and match.files():
1014 if not slowpath and match.files():
1015 # We only have to read through the filelog to find wanted revisions
1015 # We only have to read through the filelog to find wanted revisions
1016
1016
1017 minrev, maxrev = min(revs), max(revs)
1017 minrev, maxrev = min(revs), max(revs)
1018 def filerevgen(filelog, last):
1018 def filerevgen(filelog, last):
1019 """
1019 """
1020 Only files, no patterns. Check the history of each file.
1020 Only files, no patterns. Check the history of each file.
1021
1021
1022 Examines filelog entries within minrev, maxrev linkrev range
1022 Examines filelog entries within minrev, maxrev linkrev range
1023 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1023 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1024 tuples in backwards order
1024 tuples in backwards order
1025 """
1025 """
1026 cl_count = len(repo)
1026 cl_count = len(repo)
1027 revs = []
1027 revs = []
1028 for j in xrange(0, last + 1):
1028 for j in xrange(0, last + 1):
1029 linkrev = filelog.linkrev(j)
1029 linkrev = filelog.linkrev(j)
1030 if linkrev < minrev:
1030 if linkrev < minrev:
1031 continue
1031 continue
1032 # only yield rev for which we have the changelog, it can
1032 # only yield rev for which we have the changelog, it can
1033 # happen while doing "hg log" during a pull or commit
1033 # happen while doing "hg log" during a pull or commit
1034 if linkrev >= cl_count:
1034 if linkrev >= cl_count:
1035 break
1035 break
1036
1036
1037 parentlinkrevs = []
1037 parentlinkrevs = []
1038 for p in filelog.parentrevs(j):
1038 for p in filelog.parentrevs(j):
1039 if p != nullrev:
1039 if p != nullrev:
1040 parentlinkrevs.append(filelog.linkrev(p))
1040 parentlinkrevs.append(filelog.linkrev(p))
1041 n = filelog.node(j)
1041 n = filelog.node(j)
1042 revs.append((linkrev, parentlinkrevs,
1042 revs.append((linkrev, parentlinkrevs,
1043 follow and filelog.renamed(n)))
1043 follow and filelog.renamed(n)))
1044
1044
1045 return reversed(revs)
1045 return reversed(revs)
1046 def iterfiles():
1046 def iterfiles():
1047 pctx = repo['.']
1047 pctx = repo['.']
1048 for filename in match.files():
1048 for filename in match.files():
1049 if follow:
1049 if follow:
1050 if filename not in pctx:
1050 if filename not in pctx:
1051 raise util.Abort(_('cannot follow file not in parent '
1051 raise util.Abort(_('cannot follow file not in parent '
1052 'revision: "%s"') % filename)
1052 'revision: "%s"') % filename)
1053 yield filename, pctx[filename].filenode()
1053 yield filename, pctx[filename].filenode()
1054 else:
1054 else:
1055 yield filename, None
1055 yield filename, None
1056 for filename_node in copies:
1056 for filename_node in copies:
1057 yield filename_node
1057 yield filename_node
1058 for file_, node in iterfiles():
1058 for file_, node in iterfiles():
1059 filelog = repo.file(file_)
1059 filelog = repo.file(file_)
1060 if not len(filelog):
1060 if not len(filelog):
1061 if node is None:
1061 if node is None:
1062 # A zero count may be a directory or deleted file, so
1062 # A zero count may be a directory or deleted file, so
1063 # try to find matching entries on the slow path.
1063 # try to find matching entries on the slow path.
1064 if follow:
1064 if follow:
1065 raise util.Abort(
1065 raise util.Abort(
1066 _('cannot follow nonexistent file: "%s"') % file_)
1066 _('cannot follow nonexistent file: "%s"') % file_)
1067 slowpath = True
1067 slowpath = True
1068 break
1068 break
1069 else:
1069 else:
1070 continue
1070 continue
1071
1071
1072 if node is None:
1072 if node is None:
1073 last = len(filelog) - 1
1073 last = len(filelog) - 1
1074 else:
1074 else:
1075 last = filelog.rev(node)
1075 last = filelog.rev(node)
1076
1076
1077
1077
1078 # keep track of all ancestors of the file
1078 # keep track of all ancestors of the file
1079 ancestors = set([filelog.linkrev(last)])
1079 ancestors = set([filelog.linkrev(last)])
1080
1080
1081 # iterate from latest to oldest revision
1081 # iterate from latest to oldest revision
1082 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1082 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1083 if not follow:
1083 if not follow:
1084 if rev > maxrev:
1084 if rev > maxrev:
1085 continue
1085 continue
1086 else:
1086 else:
1087 # Note that last might not be the first interesting
1087 # Note that last might not be the first interesting
1088 # rev to us:
1088 # rev to us:
1089 # if the file has been changed after maxrev, we'll
1089 # if the file has been changed after maxrev, we'll
1090 # have linkrev(last) > maxrev, and we still need
1090 # have linkrev(last) > maxrev, and we still need
1091 # to explore the file graph
1091 # to explore the file graph
1092 if rev not in ancestors:
1092 if rev not in ancestors:
1093 continue
1093 continue
1094 # XXX insert 1327 fix here
1094 # XXX insert 1327 fix here
1095 if flparentlinkrevs:
1095 if flparentlinkrevs:
1096 ancestors.update(flparentlinkrevs)
1096 ancestors.update(flparentlinkrevs)
1097
1097
1098 fncache.setdefault(rev, []).append(file_)
1098 fncache.setdefault(rev, []).append(file_)
1099 wanted.add(rev)
1099 wanted.add(rev)
1100 if copied:
1100 if copied:
1101 copies.append(copied)
1101 copies.append(copied)
1102 if slowpath:
1102 if slowpath:
1103 # We have to read the changelog to match filenames against
1103 # We have to read the changelog to match filenames against
1104 # changed files
1104 # changed files
1105
1105
1106 if follow:
1106 if follow:
1107 raise util.Abort(_('can only follow copies/renames for explicit '
1107 raise util.Abort(_('can only follow copies/renames for explicit '
1108 'filenames'))
1108 'filenames'))
1109
1109
1110 # The slow path checks files modified in every changeset.
1110 # The slow path checks files modified in every changeset.
1111 for i in sorted(revs):
1111 for i in sorted(revs):
1112 ctx = change(i)
1112 ctx = change(i)
1113 matches = filter(match, ctx.files())
1113 matches = filter(match, ctx.files())
1114 if matches:
1114 if matches:
1115 fncache[i] = matches
1115 fncache[i] = matches
1116 wanted.add(i)
1116 wanted.add(i)
1117
1117
1118 class followfilter(object):
1118 class followfilter(object):
1119 def __init__(self, onlyfirst=False):
1119 def __init__(self, onlyfirst=False):
1120 self.startrev = nullrev
1120 self.startrev = nullrev
1121 self.roots = set()
1121 self.roots = set()
1122 self.onlyfirst = onlyfirst
1122 self.onlyfirst = onlyfirst
1123
1123
1124 def match(self, rev):
1124 def match(self, rev):
1125 def realparents(rev):
1125 def realparents(rev):
1126 if self.onlyfirst:
1126 if self.onlyfirst:
1127 return repo.changelog.parentrevs(rev)[0:1]
1127 return repo.changelog.parentrevs(rev)[0:1]
1128 else:
1128 else:
1129 return filter(lambda x: x != nullrev,
1129 return filter(lambda x: x != nullrev,
1130 repo.changelog.parentrevs(rev))
1130 repo.changelog.parentrevs(rev))
1131
1131
1132 if self.startrev == nullrev:
1132 if self.startrev == nullrev:
1133 self.startrev = rev
1133 self.startrev = rev
1134 return True
1134 return True
1135
1135
1136 if rev > self.startrev:
1136 if rev > self.startrev:
1137 # forward: all descendants
1137 # forward: all descendants
1138 if not self.roots:
1138 if not self.roots:
1139 self.roots.add(self.startrev)
1139 self.roots.add(self.startrev)
1140 for parent in realparents(rev):
1140 for parent in realparents(rev):
1141 if parent in self.roots:
1141 if parent in self.roots:
1142 self.roots.add(rev)
1142 self.roots.add(rev)
1143 return True
1143 return True
1144 else:
1144 else:
1145 # backwards: all parents
1145 # backwards: all parents
1146 if not self.roots:
1146 if not self.roots:
1147 self.roots.update(realparents(self.startrev))
1147 self.roots.update(realparents(self.startrev))
1148 if rev in self.roots:
1148 if rev in self.roots:
1149 self.roots.remove(rev)
1149 self.roots.remove(rev)
1150 self.roots.update(realparents(rev))
1150 self.roots.update(realparents(rev))
1151 return True
1151 return True
1152
1152
1153 return False
1153 return False
1154
1154
1155 # it might be worthwhile to do this in the iterator if the rev range
1155 # it might be worthwhile to do this in the iterator if the rev range
1156 # is descending and the prune args are all within that range
1156 # is descending and the prune args are all within that range
1157 for rev in opts.get('prune', ()):
1157 for rev in opts.get('prune', ()):
1158 rev = repo[rev].rev()
1158 rev = repo[rev].rev()
1159 ff = followfilter()
1159 ff = followfilter()
1160 stop = min(revs[0], revs[-1])
1160 stop = min(revs[0], revs[-1])
1161 for x in xrange(rev, stop - 1, -1):
1161 for x in xrange(rev, stop - 1, -1):
1162 if ff.match(x):
1162 if ff.match(x):
1163 wanted.discard(x)
1163 wanted.discard(x)
1164
1164
1165 # Now that wanted is correctly initialized, we can iterate over the
1165 # Now that wanted is correctly initialized, we can iterate over the
1166 # revision range, yielding only revisions in wanted.
1166 # revision range, yielding only revisions in wanted.
1167 def iterate():
1167 def iterate():
1168 if follow and not match.files():
1168 if follow and not match.files():
1169 ff = followfilter(onlyfirst=opts.get('follow_first'))
1169 ff = followfilter(onlyfirst=opts.get('follow_first'))
1170 def want(rev):
1170 def want(rev):
1171 return ff.match(rev) and rev in wanted
1171 return ff.match(rev) and rev in wanted
1172 else:
1172 else:
1173 def want(rev):
1173 def want(rev):
1174 return rev in wanted
1174 return rev in wanted
1175
1175
1176 for i, window in increasing_windows(0, len(revs)):
1176 for i, window in increasing_windows(0, len(revs)):
1177 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1177 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1178 for rev in sorted(nrevs):
1178 for rev in sorted(nrevs):
1179 fns = fncache.get(rev)
1179 fns = fncache.get(rev)
1180 ctx = change(rev)
1180 ctx = change(rev)
1181 if not fns:
1181 if not fns:
1182 def fns_generator():
1182 def fns_generator():
1183 for f in ctx.files():
1183 for f in ctx.files():
1184 if match(f):
1184 if match(f):
1185 yield f
1185 yield f
1186 fns = fns_generator()
1186 fns = fns_generator()
1187 prepare(ctx, fns)
1187 prepare(ctx, fns)
1188 for rev in nrevs:
1188 for rev in nrevs:
1189 yield change(rev)
1189 yield change(rev)
1190 return iterate()
1190 return iterate()
1191
1191
1192 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1192 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1193 join = lambda f: os.path.join(prefix, f)
1193 join = lambda f: os.path.join(prefix, f)
1194 bad = []
1194 bad = []
1195 oldbad = match.bad
1195 oldbad = match.bad
1196 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1196 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1197 names = []
1197 names = []
1198 wctx = repo[None]
1198 wctx = repo[None]
1199 cca = None
1199 cca = None
1200 abort, warn = scmutil.checkportabilityalert(ui)
1200 abort, warn = scmutil.checkportabilityalert(ui)
1201 if abort or warn:
1201 if abort or warn:
1202 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1202 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1203 for f in repo.walk(match):
1203 for f in repo.walk(match):
1204 exact = match.exact(f)
1204 exact = match.exact(f)
1205 if exact or not explicitonly and f not in repo.dirstate:
1205 if exact or not explicitonly and f not in repo.dirstate:
1206 if cca:
1206 if cca:
1207 cca(f)
1207 cca(f)
1208 names.append(f)
1208 names.append(f)
1209 if ui.verbose or not exact:
1209 if ui.verbose or not exact:
1210 ui.status(_('adding %s\n') % match.rel(join(f)))
1210 ui.status(_('adding %s\n') % match.rel(join(f)))
1211
1211
1212 for subpath in wctx.substate:
1212 for subpath in wctx.substate:
1213 sub = wctx.sub(subpath)
1213 sub = wctx.sub(subpath)
1214 try:
1214 try:
1215 submatch = matchmod.narrowmatcher(subpath, match)
1215 submatch = matchmod.narrowmatcher(subpath, match)
1216 if listsubrepos:
1216 if listsubrepos:
1217 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1217 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1218 False))
1218 False))
1219 else:
1219 else:
1220 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1220 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1221 True))
1221 True))
1222 except error.LookupError:
1222 except error.LookupError:
1223 ui.status(_("skipping missing subrepository: %s\n")
1223 ui.status(_("skipping missing subrepository: %s\n")
1224 % join(subpath))
1224 % join(subpath))
1225
1225
1226 if not dryrun:
1226 if not dryrun:
1227 rejected = wctx.add(names, prefix)
1227 rejected = wctx.add(names, prefix)
1228 bad.extend(f for f in rejected if f in match.files())
1228 bad.extend(f for f in rejected if f in match.files())
1229 return bad
1229 return bad
1230
1230
1231 def forget(ui, repo, match, prefix, explicitonly):
1231 def forget(ui, repo, match, prefix, explicitonly):
1232 join = lambda f: os.path.join(prefix, f)
1232 join = lambda f: os.path.join(prefix, f)
1233 bad = []
1233 bad = []
1234 oldbad = match.bad
1234 oldbad = match.bad
1235 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1235 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1236 wctx = repo[None]
1236 wctx = repo[None]
1237 forgot = []
1237 forgot = []
1238 s = repo.status(match=match, clean=True)
1238 s = repo.status(match=match, clean=True)
1239 forget = sorted(s[0] + s[1] + s[3] + s[6])
1239 forget = sorted(s[0] + s[1] + s[3] + s[6])
1240 if explicitonly:
1240 if explicitonly:
1241 forget = [f for f in forget if match.exact(f)]
1241 forget = [f for f in forget if match.exact(f)]
1242
1242
1243 for subpath in wctx.substate:
1243 for subpath in wctx.substate:
1244 sub = wctx.sub(subpath)
1244 sub = wctx.sub(subpath)
1245 try:
1245 try:
1246 submatch = matchmod.narrowmatcher(subpath, match)
1246 submatch = matchmod.narrowmatcher(subpath, match)
1247 subbad, subforgot = sub.forget(ui, submatch, prefix)
1247 subbad, subforgot = sub.forget(ui, submatch, prefix)
1248 bad.extend([subpath + '/' + f for f in subbad])
1248 bad.extend([subpath + '/' + f for f in subbad])
1249 forgot.extend([subpath + '/' + f for f in subforgot])
1249 forgot.extend([subpath + '/' + f for f in subforgot])
1250 except error.LookupError:
1250 except error.LookupError:
1251 ui.status(_("skipping missing subrepository: %s\n")
1251 ui.status(_("skipping missing subrepository: %s\n")
1252 % join(subpath))
1252 % join(subpath))
1253
1253
1254 if not explicitonly:
1254 if not explicitonly:
1255 for f in match.files():
1255 for f in match.files():
1256 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1256 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1257 if f not in forgot:
1257 if f not in forgot:
1258 if os.path.exists(match.rel(join(f))):
1258 if os.path.exists(match.rel(join(f))):
1259 ui.warn(_('not removing %s: '
1259 ui.warn(_('not removing %s: '
1260 'file is already untracked\n')
1260 'file is already untracked\n')
1261 % match.rel(join(f)))
1261 % match.rel(join(f)))
1262 bad.append(f)
1262 bad.append(f)
1263
1263
1264 for f in forget:
1264 for f in forget:
1265 if ui.verbose or not match.exact(f):
1265 if ui.verbose or not match.exact(f):
1266 ui.status(_('removing %s\n') % match.rel(join(f)))
1266 ui.status(_('removing %s\n') % match.rel(join(f)))
1267
1267
1268 rejected = wctx.forget(forget, prefix)
1268 rejected = wctx.forget(forget, prefix)
1269 bad.extend(f for f in rejected if f in match.files())
1269 bad.extend(f for f in rejected if f in match.files())
1270 forgot.extend(forget)
1270 forgot.extend(forget)
1271 return bad, forgot
1271 return bad, forgot
1272
1272
1273 def duplicatecopies(repo, rev, p1):
1273 def duplicatecopies(repo, rev, p1):
1274 "Reproduce copies found in the source revision in the dirstate for grafts"
1274 "Reproduce copies found in the source revision in the dirstate for grafts"
1275 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1275 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1276 repo.dirstate.copy(src, dst)
1276 repo.dirstate.copy(src, dst)
1277
1277
1278 def commit(ui, repo, commitfunc, pats, opts):
1278 def commit(ui, repo, commitfunc, pats, opts):
1279 '''commit the specified files or all outstanding changes'''
1279 '''commit the specified files or all outstanding changes'''
1280 date = opts.get('date')
1280 date = opts.get('date')
1281 if date:
1281 if date:
1282 opts['date'] = util.parsedate(date)
1282 opts['date'] = util.parsedate(date)
1283 message = logmessage(ui, opts)
1283 message = logmessage(ui, opts)
1284
1284
1285 # extract addremove carefully -- this function can be called from a command
1285 # extract addremove carefully -- this function can be called from a command
1286 # that doesn't support addremove
1286 # that doesn't support addremove
1287 if opts.get('addremove'):
1287 if opts.get('addremove'):
1288 scmutil.addremove(repo, pats, opts)
1288 scmutil.addremove(repo, pats, opts)
1289
1289
1290 return commitfunc(ui, repo, message,
1290 return commitfunc(ui, repo, message,
1291 scmutil.match(repo[None], pats, opts), opts)
1291 scmutil.match(repo[None], pats, opts), opts)
1292
1292
1293 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1293 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1294 ui.note(_('amending changeset %s\n') % old)
1294 ui.note(_('amending changeset %s\n') % old)
1295 base = old.p1()
1295 base = old.p1()
1296
1296
1297 wlock = repo.wlock()
1297 wlock = repo.wlock()
1298 try:
1298 try:
1299 # Fix up dirstate for copies and renames
1299 # Fix up dirstate for copies and renames
1300 duplicatecopies(repo, None, base.node())
1300 duplicatecopies(repo, None, base.node())
1301
1301
1302 # First, do a regular commit to record all changes in the working
1302 # First, do a regular commit to record all changes in the working
1303 # directory (if there are any)
1303 # directory (if there are any)
1304 node = commit(ui, repo, commitfunc, pats, opts)
1304 node = commit(ui, repo, commitfunc, pats, opts)
1305 ctx = repo[node]
1305 ctx = repo[node]
1306
1306
1307 # Participating changesets:
1307 # Participating changesets:
1308 #
1308 #
1309 # node/ctx o - new (intermediate) commit that contains changes from
1309 # node/ctx o - new (intermediate) commit that contains changes from
1310 # | working dir to go into amending commit (or a workingctx
1310 # | working dir to go into amending commit (or a workingctx
1311 # | if there were no changes)
1311 # | if there were no changes)
1312 # |
1312 # |
1313 # old o - changeset to amend
1313 # old o - changeset to amend
1314 # |
1314 # |
1315 # base o - parent of amending changeset
1315 # base o - parent of amending changeset
1316
1316
1317 files = set(old.files())
1317 files = set(old.files())
1318
1318
1319 # Second, we use either the commit we just did, or if there were no
1319 # Second, we use either the commit we just did, or if there were no
1320 # changes the parent of the working directory as the version of the
1320 # changes the parent of the working directory as the version of the
1321 # files in the final amend commit
1321 # files in the final amend commit
1322 if node:
1322 if node:
1323 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1323 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1324
1324
1325 user = ctx.user()
1325 user = ctx.user()
1326 date = ctx.date()
1326 date = ctx.date()
1327 message = ctx.description()
1327 message = ctx.description()
1328 extra = ctx.extra()
1328 extra = ctx.extra()
1329
1329
1330 # Prune files which were reverted by the updates: if old introduced
1330 # Prune files which were reverted by the updates: if old introduced
1331 # file X and our intermediate commit, node, renamed that file, then
1331 # file X and our intermediate commit, node, renamed that file, then
1332 # those two files are the same and we can discard X from our list
1332 # those two files are the same and we can discard X from our list
1333 # of files. Likewise if X was deleted, it's no longer relevant
1333 # of files. Likewise if X was deleted, it's no longer relevant
1334 files.update(ctx.files())
1334 files.update(ctx.files())
1335
1335
1336 def samefile(f):
1336 def samefile(f):
1337 if f in ctx.manifest():
1337 if f in ctx.manifest():
1338 a = ctx.filectx(f)
1338 a = ctx.filectx(f)
1339 if f in base.manifest():
1339 if f in base.manifest():
1340 b = base.filectx(f)
1340 b = base.filectx(f)
1341 return (a.data() == b.data()
1341 return (a.data() == b.data()
1342 and a.flags() == b.flags()
1342 and a.flags() == b.flags()
1343 and a.renamed() == b.renamed())
1343 and a.renamed() == b.renamed())
1344 else:
1344 else:
1345 return False
1345 return False
1346 else:
1346 else:
1347 return f not in base.manifest()
1347 return f not in base.manifest()
1348 files = [f for f in files if not samefile(f)]
1348 files = [f for f in files if not samefile(f)]
1349
1349
1350 def filectxfn(repo, ctx_, path):
1350 def filectxfn(repo, ctx_, path):
1351 try:
1351 try:
1352 return ctx.filectx(path)
1352 return ctx.filectx(path)
1353 except KeyError:
1353 except KeyError:
1354 raise IOError()
1354 raise IOError()
1355 else:
1355 else:
1356 ui.note(_('copying changeset %s to %s\n') % (old, base))
1356 ui.note(_('copying changeset %s to %s\n') % (old, base))
1357
1357
1358 # Use version of files as in the old cset
1358 # Use version of files as in the old cset
1359 def filectxfn(repo, ctx_, path):
1359 def filectxfn(repo, ctx_, path):
1360 try:
1360 try:
1361 return old.filectx(path)
1361 return old.filectx(path)
1362 except KeyError:
1362 except KeyError:
1363 raise IOError()
1363 raise IOError()
1364
1364
1365 # See if we got a message from -m or -l, if not, open the editor
1365 # See if we got a message from -m or -l, if not, open the editor
1366 # with the message of the changeset to amend
1366 # with the message of the changeset to amend
1367 user = opts.get('user') or old.user()
1367 user = opts.get('user') or old.user()
1368 date = opts.get('date') or old.date()
1368 date = opts.get('date') or old.date()
1369 message = logmessage(ui, opts)
1369 message = logmessage(ui, opts)
1370 if not message:
1370 if not message:
1371 cctx = context.workingctx(repo, old.description(), user, date,
1371 cctx = context.workingctx(repo, old.description(), user, date,
1372 extra,
1372 extra,
1373 repo.status(base.node(), old.node()))
1373 repo.status(base.node(), old.node()))
1374 message = commitforceeditor(repo, cctx, [])
1374 message = commitforceeditor(repo, cctx, [])
1375
1375
1376 new = context.memctx(repo,
1376 new = context.memctx(repo,
1377 parents=[base.node(), nullid],
1377 parents=[base.node(), nullid],
1378 text=message,
1378 text=message,
1379 files=files,
1379 files=files,
1380 filectxfn=filectxfn,
1380 filectxfn=filectxfn,
1381 user=user,
1381 user=user,
1382 date=date,
1382 date=date,
1383 extra=extra)
1383 extra=extra)
1384 newid = repo.commitctx(new)
1384 newid = repo.commitctx(new)
1385 if newid != old.node():
1385 if newid != old.node():
1386 # Reroute the working copy parent to the new changeset
1386 # Reroute the working copy parent to the new changeset
1387 repo.dirstate.setparents(newid, nullid)
1387 repo.setparents(newid, nullid)
1388
1388
1389 # Move bookmarks from old parent to amend commit
1389 # Move bookmarks from old parent to amend commit
1390 bms = repo.nodebookmarks(old.node())
1390 bms = repo.nodebookmarks(old.node())
1391 if bms:
1391 if bms:
1392 for bm in bms:
1392 for bm in bms:
1393 repo._bookmarks[bm] = newid
1393 repo._bookmarks[bm] = newid
1394 bookmarks.write(repo)
1394 bookmarks.write(repo)
1395
1395
1396 # Strip the intermediate commit (if there was one) and the amended
1396 # Strip the intermediate commit (if there was one) and the amended
1397 # commit
1397 # commit
1398 lock = repo.lock()
1398 lock = repo.lock()
1399 try:
1399 try:
1400 if node:
1400 if node:
1401 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1401 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1402 ui.note(_('stripping amended changeset %s\n') % old)
1402 ui.note(_('stripping amended changeset %s\n') % old)
1403 repair.strip(ui, repo, old.node(), topic='amend-backup')
1403 repair.strip(ui, repo, old.node(), topic='amend-backup')
1404 finally:
1404 finally:
1405 lock.release()
1405 lock.release()
1406 finally:
1406 finally:
1407 wlock.release()
1407 wlock.release()
1408 return newid
1408 return newid
1409
1409
1410 def commiteditor(repo, ctx, subs):
1410 def commiteditor(repo, ctx, subs):
1411 if ctx.description():
1411 if ctx.description():
1412 return ctx.description()
1412 return ctx.description()
1413 return commitforceeditor(repo, ctx, subs)
1413 return commitforceeditor(repo, ctx, subs)
1414
1414
1415 def commitforceeditor(repo, ctx, subs):
1415 def commitforceeditor(repo, ctx, subs):
1416 edittext = []
1416 edittext = []
1417 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1417 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1418 if ctx.description():
1418 if ctx.description():
1419 edittext.append(ctx.description())
1419 edittext.append(ctx.description())
1420 edittext.append("")
1420 edittext.append("")
1421 edittext.append("") # Empty line between message and comments.
1421 edittext.append("") # Empty line between message and comments.
1422 edittext.append(_("HG: Enter commit message."
1422 edittext.append(_("HG: Enter commit message."
1423 " Lines beginning with 'HG:' are removed."))
1423 " Lines beginning with 'HG:' are removed."))
1424 edittext.append(_("HG: Leave message empty to abort commit."))
1424 edittext.append(_("HG: Leave message empty to abort commit."))
1425 edittext.append("HG: --")
1425 edittext.append("HG: --")
1426 edittext.append(_("HG: user: %s") % ctx.user())
1426 edittext.append(_("HG: user: %s") % ctx.user())
1427 if ctx.p2():
1427 if ctx.p2():
1428 edittext.append(_("HG: branch merge"))
1428 edittext.append(_("HG: branch merge"))
1429 if ctx.branch():
1429 if ctx.branch():
1430 edittext.append(_("HG: branch '%s'") % ctx.branch())
1430 edittext.append(_("HG: branch '%s'") % ctx.branch())
1431 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1431 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1432 edittext.extend([_("HG: added %s") % f for f in added])
1432 edittext.extend([_("HG: added %s") % f for f in added])
1433 edittext.extend([_("HG: changed %s") % f for f in modified])
1433 edittext.extend([_("HG: changed %s") % f for f in modified])
1434 edittext.extend([_("HG: removed %s") % f for f in removed])
1434 edittext.extend([_("HG: removed %s") % f for f in removed])
1435 if not added and not modified and not removed:
1435 if not added and not modified and not removed:
1436 edittext.append(_("HG: no files changed"))
1436 edittext.append(_("HG: no files changed"))
1437 edittext.append("")
1437 edittext.append("")
1438 # run editor in the repository root
1438 # run editor in the repository root
1439 olddir = os.getcwd()
1439 olddir = os.getcwd()
1440 os.chdir(repo.root)
1440 os.chdir(repo.root)
1441 text = repo.ui.edit("\n".join(edittext), ctx.user())
1441 text = repo.ui.edit("\n".join(edittext), ctx.user())
1442 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1442 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1443 os.chdir(olddir)
1443 os.chdir(olddir)
1444
1444
1445 if not text.strip():
1445 if not text.strip():
1446 raise util.Abort(_("empty commit message"))
1446 raise util.Abort(_("empty commit message"))
1447
1447
1448 return text
1448 return text
1449
1449
1450 def revert(ui, repo, ctx, parents, *pats, **opts):
1450 def revert(ui, repo, ctx, parents, *pats, **opts):
1451 parent, p2 = parents
1451 parent, p2 = parents
1452 node = ctx.node()
1452 node = ctx.node()
1453
1453
1454 mf = ctx.manifest()
1454 mf = ctx.manifest()
1455 if node == parent:
1455 if node == parent:
1456 pmf = mf
1456 pmf = mf
1457 else:
1457 else:
1458 pmf = None
1458 pmf = None
1459
1459
1460 # need all matching names in dirstate and manifest of target rev,
1460 # need all matching names in dirstate and manifest of target rev,
1461 # so have to walk both. do not print errors if files exist in one
1461 # so have to walk both. do not print errors if files exist in one
1462 # but not other.
1462 # but not other.
1463
1463
1464 names = {}
1464 names = {}
1465
1465
1466 wlock = repo.wlock()
1466 wlock = repo.wlock()
1467 try:
1467 try:
1468 # walk dirstate.
1468 # walk dirstate.
1469
1469
1470 m = scmutil.match(repo[None], pats, opts)
1470 m = scmutil.match(repo[None], pats, opts)
1471 m.bad = lambda x, y: False
1471 m.bad = lambda x, y: False
1472 for abs in repo.walk(m):
1472 for abs in repo.walk(m):
1473 names[abs] = m.rel(abs), m.exact(abs)
1473 names[abs] = m.rel(abs), m.exact(abs)
1474
1474
1475 # walk target manifest.
1475 # walk target manifest.
1476
1476
1477 def badfn(path, msg):
1477 def badfn(path, msg):
1478 if path in names:
1478 if path in names:
1479 return
1479 return
1480 if path in repo[node].substate:
1480 if path in repo[node].substate:
1481 return
1481 return
1482 path_ = path + '/'
1482 path_ = path + '/'
1483 for f in names:
1483 for f in names:
1484 if f.startswith(path_):
1484 if f.startswith(path_):
1485 return
1485 return
1486 ui.warn("%s: %s\n" % (m.rel(path), msg))
1486 ui.warn("%s: %s\n" % (m.rel(path), msg))
1487
1487
1488 m = scmutil.match(repo[node], pats, opts)
1488 m = scmutil.match(repo[node], pats, opts)
1489 m.bad = badfn
1489 m.bad = badfn
1490 for abs in repo[node].walk(m):
1490 for abs in repo[node].walk(m):
1491 if abs not in names:
1491 if abs not in names:
1492 names[abs] = m.rel(abs), m.exact(abs)
1492 names[abs] = m.rel(abs), m.exact(abs)
1493
1493
1494 # get the list of subrepos that must be reverted
1494 # get the list of subrepos that must be reverted
1495 targetsubs = [s for s in repo[node].substate if m(s)]
1495 targetsubs = [s for s in repo[node].substate if m(s)]
1496 m = scmutil.matchfiles(repo, names)
1496 m = scmutil.matchfiles(repo, names)
1497 changes = repo.status(match=m)[:4]
1497 changes = repo.status(match=m)[:4]
1498 modified, added, removed, deleted = map(set, changes)
1498 modified, added, removed, deleted = map(set, changes)
1499
1499
1500 # if f is a rename, also revert the source
1500 # if f is a rename, also revert the source
1501 cwd = repo.getcwd()
1501 cwd = repo.getcwd()
1502 for f in added:
1502 for f in added:
1503 src = repo.dirstate.copied(f)
1503 src = repo.dirstate.copied(f)
1504 if src and src not in names and repo.dirstate[src] == 'r':
1504 if src and src not in names and repo.dirstate[src] == 'r':
1505 removed.add(src)
1505 removed.add(src)
1506 names[src] = (repo.pathto(src, cwd), True)
1506 names[src] = (repo.pathto(src, cwd), True)
1507
1507
1508 def removeforget(abs):
1508 def removeforget(abs):
1509 if repo.dirstate[abs] == 'a':
1509 if repo.dirstate[abs] == 'a':
1510 return _('forgetting %s\n')
1510 return _('forgetting %s\n')
1511 return _('removing %s\n')
1511 return _('removing %s\n')
1512
1512
1513 revert = ([], _('reverting %s\n'))
1513 revert = ([], _('reverting %s\n'))
1514 add = ([], _('adding %s\n'))
1514 add = ([], _('adding %s\n'))
1515 remove = ([], removeforget)
1515 remove = ([], removeforget)
1516 undelete = ([], _('undeleting %s\n'))
1516 undelete = ([], _('undeleting %s\n'))
1517
1517
1518 disptable = (
1518 disptable = (
1519 # dispatch table:
1519 # dispatch table:
1520 # file state
1520 # file state
1521 # action if in target manifest
1521 # action if in target manifest
1522 # action if not in target manifest
1522 # action if not in target manifest
1523 # make backup if in target manifest
1523 # make backup if in target manifest
1524 # make backup if not in target manifest
1524 # make backup if not in target manifest
1525 (modified, revert, remove, True, True),
1525 (modified, revert, remove, True, True),
1526 (added, revert, remove, True, False),
1526 (added, revert, remove, True, False),
1527 (removed, undelete, None, False, False),
1527 (removed, undelete, None, False, False),
1528 (deleted, revert, remove, False, False),
1528 (deleted, revert, remove, False, False),
1529 )
1529 )
1530
1530
1531 for abs, (rel, exact) in sorted(names.items()):
1531 for abs, (rel, exact) in sorted(names.items()):
1532 mfentry = mf.get(abs)
1532 mfentry = mf.get(abs)
1533 target = repo.wjoin(abs)
1533 target = repo.wjoin(abs)
1534 def handle(xlist, dobackup):
1534 def handle(xlist, dobackup):
1535 xlist[0].append(abs)
1535 xlist[0].append(abs)
1536 if (dobackup and not opts.get('no_backup') and
1536 if (dobackup and not opts.get('no_backup') and
1537 os.path.lexists(target)):
1537 os.path.lexists(target)):
1538 bakname = "%s.orig" % rel
1538 bakname = "%s.orig" % rel
1539 ui.note(_('saving current version of %s as %s\n') %
1539 ui.note(_('saving current version of %s as %s\n') %
1540 (rel, bakname))
1540 (rel, bakname))
1541 if not opts.get('dry_run'):
1541 if not opts.get('dry_run'):
1542 util.rename(target, bakname)
1542 util.rename(target, bakname)
1543 if ui.verbose or not exact:
1543 if ui.verbose or not exact:
1544 msg = xlist[1]
1544 msg = xlist[1]
1545 if not isinstance(msg, basestring):
1545 if not isinstance(msg, basestring):
1546 msg = msg(abs)
1546 msg = msg(abs)
1547 ui.status(msg % rel)
1547 ui.status(msg % rel)
1548 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1548 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1549 if abs not in table:
1549 if abs not in table:
1550 continue
1550 continue
1551 # file has changed in dirstate
1551 # file has changed in dirstate
1552 if mfentry:
1552 if mfentry:
1553 handle(hitlist, backuphit)
1553 handle(hitlist, backuphit)
1554 elif misslist is not None:
1554 elif misslist is not None:
1555 handle(misslist, backupmiss)
1555 handle(misslist, backupmiss)
1556 break
1556 break
1557 else:
1557 else:
1558 if abs not in repo.dirstate:
1558 if abs not in repo.dirstate:
1559 if mfentry:
1559 if mfentry:
1560 handle(add, True)
1560 handle(add, True)
1561 elif exact:
1561 elif exact:
1562 ui.warn(_('file not managed: %s\n') % rel)
1562 ui.warn(_('file not managed: %s\n') % rel)
1563 continue
1563 continue
1564 # file has not changed in dirstate
1564 # file has not changed in dirstate
1565 if node == parent:
1565 if node == parent:
1566 if exact:
1566 if exact:
1567 ui.warn(_('no changes needed to %s\n') % rel)
1567 ui.warn(_('no changes needed to %s\n') % rel)
1568 continue
1568 continue
1569 if pmf is None:
1569 if pmf is None:
1570 # only need parent manifest in this unlikely case,
1570 # only need parent manifest in this unlikely case,
1571 # so do not read by default
1571 # so do not read by default
1572 pmf = repo[parent].manifest()
1572 pmf = repo[parent].manifest()
1573 if abs in pmf and mfentry:
1573 if abs in pmf and mfentry:
1574 # if version of file is same in parent and target
1574 # if version of file is same in parent and target
1575 # manifests, do nothing
1575 # manifests, do nothing
1576 if (pmf[abs] != mfentry or
1576 if (pmf[abs] != mfentry or
1577 pmf.flags(abs) != mf.flags(abs)):
1577 pmf.flags(abs) != mf.flags(abs)):
1578 handle(revert, False)
1578 handle(revert, False)
1579 else:
1579 else:
1580 handle(remove, False)
1580 handle(remove, False)
1581
1581
1582 if not opts.get('dry_run'):
1582 if not opts.get('dry_run'):
1583 def checkout(f):
1583 def checkout(f):
1584 fc = ctx[f]
1584 fc = ctx[f]
1585 repo.wwrite(f, fc.data(), fc.flags())
1585 repo.wwrite(f, fc.data(), fc.flags())
1586
1586
1587 audit_path = scmutil.pathauditor(repo.root)
1587 audit_path = scmutil.pathauditor(repo.root)
1588 for f in remove[0]:
1588 for f in remove[0]:
1589 if repo.dirstate[f] == 'a':
1589 if repo.dirstate[f] == 'a':
1590 repo.dirstate.drop(f)
1590 repo.dirstate.drop(f)
1591 continue
1591 continue
1592 audit_path(f)
1592 audit_path(f)
1593 try:
1593 try:
1594 util.unlinkpath(repo.wjoin(f))
1594 util.unlinkpath(repo.wjoin(f))
1595 except OSError:
1595 except OSError:
1596 pass
1596 pass
1597 repo.dirstate.remove(f)
1597 repo.dirstate.remove(f)
1598
1598
1599 normal = None
1599 normal = None
1600 if node == parent:
1600 if node == parent:
1601 # We're reverting to our parent. If possible, we'd like status
1601 # We're reverting to our parent. If possible, we'd like status
1602 # to report the file as clean. We have to use normallookup for
1602 # to report the file as clean. We have to use normallookup for
1603 # merges to avoid losing information about merged/dirty files.
1603 # merges to avoid losing information about merged/dirty files.
1604 if p2 != nullid:
1604 if p2 != nullid:
1605 normal = repo.dirstate.normallookup
1605 normal = repo.dirstate.normallookup
1606 else:
1606 else:
1607 normal = repo.dirstate.normal
1607 normal = repo.dirstate.normal
1608 for f in revert[0]:
1608 for f in revert[0]:
1609 checkout(f)
1609 checkout(f)
1610 if normal:
1610 if normal:
1611 normal(f)
1611 normal(f)
1612
1612
1613 for f in add[0]:
1613 for f in add[0]:
1614 checkout(f)
1614 checkout(f)
1615 repo.dirstate.add(f)
1615 repo.dirstate.add(f)
1616
1616
1617 normal = repo.dirstate.normallookup
1617 normal = repo.dirstate.normallookup
1618 if node == parent and p2 == nullid:
1618 if node == parent and p2 == nullid:
1619 normal = repo.dirstate.normal
1619 normal = repo.dirstate.normal
1620 for f in undelete[0]:
1620 for f in undelete[0]:
1621 checkout(f)
1621 checkout(f)
1622 normal(f)
1622 normal(f)
1623
1623
1624 if targetsubs:
1624 if targetsubs:
1625 # Revert the subrepos on the revert list
1625 # Revert the subrepos on the revert list
1626 for sub in targetsubs:
1626 for sub in targetsubs:
1627 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1627 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1628 finally:
1628 finally:
1629 wlock.release()
1629 wlock.release()
1630
1630
1631 def command(table):
1631 def command(table):
1632 '''returns a function object bound to table which can be used as
1632 '''returns a function object bound to table which can be used as
1633 a decorator for populating table as a command table'''
1633 a decorator for populating table as a command table'''
1634
1634
1635 def cmd(name, options, synopsis=None):
1635 def cmd(name, options, synopsis=None):
1636 def decorator(func):
1636 def decorator(func):
1637 if synopsis:
1637 if synopsis:
1638 table[name] = func, options[:], synopsis
1638 table[name] = func, options[:], synopsis
1639 else:
1639 else:
1640 table[name] = func, options[:]
1640 table[name] = func, options[:]
1641 return func
1641 return func
1642 return decorator
1642 return decorator
1643
1643
1644 return cmd
1644 return cmd
@@ -1,5717 +1,5717 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, bin, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, difflib, time, tempfile, errno
11 import os, re, difflib, time, tempfile, errno
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, url, encoding, templatekw, discovery
13 import patch, help, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, hbisect
14 import archival, changegroup, cmdutil, hbisect
15 import sshserver, hgweb, hgweb.server, commandserver
15 import sshserver, hgweb, hgweb.server, commandserver
16 import merge as mergemod
16 import merge as mergemod
17 import minirst, revset, fileset
17 import minirst, revset, fileset
18 import dagparser, context, simplemerge
18 import dagparser, context, simplemerge
19 import random, setdiscovery, treediscovery, dagutil, pvec
19 import random, setdiscovery, treediscovery, dagutil, pvec
20 import phases
20 import phases
21
21
22 table = {}
22 table = {}
23
23
24 command = cmdutil.command(table)
24 command = cmdutil.command(table)
25
25
26 # common command options
26 # common command options
27
27
28 globalopts = [
28 globalopts = [
29 ('R', 'repository', '',
29 ('R', 'repository', '',
30 _('repository root directory or name of overlay bundle file'),
30 _('repository root directory or name of overlay bundle file'),
31 _('REPO')),
31 _('REPO')),
32 ('', 'cwd', '',
32 ('', 'cwd', '',
33 _('change working directory'), _('DIR')),
33 _('change working directory'), _('DIR')),
34 ('y', 'noninteractive', None,
34 ('y', 'noninteractive', None,
35 _('do not prompt, automatically pick the first choice for all prompts')),
35 _('do not prompt, automatically pick the first choice for all prompts')),
36 ('q', 'quiet', None, _('suppress output')),
36 ('q', 'quiet', None, _('suppress output')),
37 ('v', 'verbose', None, _('enable additional output')),
37 ('v', 'verbose', None, _('enable additional output')),
38 ('', 'config', [],
38 ('', 'config', [],
39 _('set/override config option (use \'section.name=value\')'),
39 _('set/override config option (use \'section.name=value\')'),
40 _('CONFIG')),
40 _('CONFIG')),
41 ('', 'debug', None, _('enable debugging output')),
41 ('', 'debug', None, _('enable debugging output')),
42 ('', 'debugger', None, _('start debugger')),
42 ('', 'debugger', None, _('start debugger')),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
44 _('ENCODE')),
44 _('ENCODE')),
45 ('', 'encodingmode', encoding.encodingmode,
45 ('', 'encodingmode', encoding.encodingmode,
46 _('set the charset encoding mode'), _('MODE')),
46 _('set the charset encoding mode'), _('MODE')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
48 ('', 'time', None, _('time how long the command takes')),
48 ('', 'time', None, _('time how long the command takes')),
49 ('', 'profile', None, _('print command execution profile')),
49 ('', 'profile', None, _('print command execution profile')),
50 ('', 'version', None, _('output version information and exit')),
50 ('', 'version', None, _('output version information and exit')),
51 ('h', 'help', None, _('display help and exit')),
51 ('h', 'help', None, _('display help and exit')),
52 ]
52 ]
53
53
54 dryrunopts = [('n', 'dry-run', None,
54 dryrunopts = [('n', 'dry-run', None,
55 _('do not perform actions, just print output'))]
55 _('do not perform actions, just print output'))]
56
56
57 remoteopts = [
57 remoteopts = [
58 ('e', 'ssh', '',
58 ('e', 'ssh', '',
59 _('specify ssh command to use'), _('CMD')),
59 _('specify ssh command to use'), _('CMD')),
60 ('', 'remotecmd', '',
60 ('', 'remotecmd', '',
61 _('specify hg command to run on the remote side'), _('CMD')),
61 _('specify hg command to run on the remote side'), _('CMD')),
62 ('', 'insecure', None,
62 ('', 'insecure', None,
63 _('do not verify server certificate (ignoring web.cacerts config)')),
63 _('do not verify server certificate (ignoring web.cacerts config)')),
64 ]
64 ]
65
65
66 walkopts = [
66 walkopts = [
67 ('I', 'include', [],
67 ('I', 'include', [],
68 _('include names matching the given patterns'), _('PATTERN')),
68 _('include names matching the given patterns'), _('PATTERN')),
69 ('X', 'exclude', [],
69 ('X', 'exclude', [],
70 _('exclude names matching the given patterns'), _('PATTERN')),
70 _('exclude names matching the given patterns'), _('PATTERN')),
71 ]
71 ]
72
72
73 commitopts = [
73 commitopts = [
74 ('m', 'message', '',
74 ('m', 'message', '',
75 _('use text as commit message'), _('TEXT')),
75 _('use text as commit message'), _('TEXT')),
76 ('l', 'logfile', '',
76 ('l', 'logfile', '',
77 _('read commit message from file'), _('FILE')),
77 _('read commit message from file'), _('FILE')),
78 ]
78 ]
79
79
80 commitopts2 = [
80 commitopts2 = [
81 ('d', 'date', '',
81 ('d', 'date', '',
82 _('record the specified date as commit date'), _('DATE')),
82 _('record the specified date as commit date'), _('DATE')),
83 ('u', 'user', '',
83 ('u', 'user', '',
84 _('record the specified user as committer'), _('USER')),
84 _('record the specified user as committer'), _('USER')),
85 ]
85 ]
86
86
87 templateopts = [
87 templateopts = [
88 ('', 'style', '',
88 ('', 'style', '',
89 _('display using template map file'), _('STYLE')),
89 _('display using template map file'), _('STYLE')),
90 ('', 'template', '',
90 ('', 'template', '',
91 _('display with template'), _('TEMPLATE')),
91 _('display with template'), _('TEMPLATE')),
92 ]
92 ]
93
93
94 logopts = [
94 logopts = [
95 ('p', 'patch', None, _('show patch')),
95 ('p', 'patch', None, _('show patch')),
96 ('g', 'git', None, _('use git extended diff format')),
96 ('g', 'git', None, _('use git extended diff format')),
97 ('l', 'limit', '',
97 ('l', 'limit', '',
98 _('limit number of changes displayed'), _('NUM')),
98 _('limit number of changes displayed'), _('NUM')),
99 ('M', 'no-merges', None, _('do not show merges')),
99 ('M', 'no-merges', None, _('do not show merges')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
101 ] + templateopts
101 ] + templateopts
102
102
103 diffopts = [
103 diffopts = [
104 ('a', 'text', None, _('treat all files as text')),
104 ('a', 'text', None, _('treat all files as text')),
105 ('g', 'git', None, _('use git extended diff format')),
105 ('g', 'git', None, _('use git extended diff format')),
106 ('', 'nodates', None, _('omit dates from diff headers'))
106 ('', 'nodates', None, _('omit dates from diff headers'))
107 ]
107 ]
108
108
109 diffwsopts = [
109 diffwsopts = [
110 ('w', 'ignore-all-space', None,
110 ('w', 'ignore-all-space', None,
111 _('ignore white space when comparing lines')),
111 _('ignore white space when comparing lines')),
112 ('b', 'ignore-space-change', None,
112 ('b', 'ignore-space-change', None,
113 _('ignore changes in the amount of white space')),
113 _('ignore changes in the amount of white space')),
114 ('B', 'ignore-blank-lines', None,
114 ('B', 'ignore-blank-lines', None,
115 _('ignore changes whose lines are all blank')),
115 _('ignore changes whose lines are all blank')),
116 ]
116 ]
117
117
118 diffopts2 = [
118 diffopts2 = [
119 ('p', 'show-function', None, _('show which function each change is in')),
119 ('p', 'show-function', None, _('show which function each change is in')),
120 ('', 'reverse', None, _('produce a diff that undoes the changes')),
120 ('', 'reverse', None, _('produce a diff that undoes the changes')),
121 ] + diffwsopts + [
121 ] + diffwsopts + [
122 ('U', 'unified', '',
122 ('U', 'unified', '',
123 _('number of lines of context to show'), _('NUM')),
123 _('number of lines of context to show'), _('NUM')),
124 ('', 'stat', None, _('output diffstat-style summary of changes')),
124 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 ]
125 ]
126
126
127 mergetoolopts = [
127 mergetoolopts = [
128 ('t', 'tool', '', _('specify merge tool')),
128 ('t', 'tool', '', _('specify merge tool')),
129 ]
129 ]
130
130
131 similarityopts = [
131 similarityopts = [
132 ('s', 'similarity', '',
132 ('s', 'similarity', '',
133 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
133 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
134 ]
134 ]
135
135
136 subrepoopts = [
136 subrepoopts = [
137 ('S', 'subrepos', None,
137 ('S', 'subrepos', None,
138 _('recurse into subrepositories'))
138 _('recurse into subrepositories'))
139 ]
139 ]
140
140
141 # Commands start here, listed alphabetically
141 # Commands start here, listed alphabetically
142
142
143 @command('^add',
143 @command('^add',
144 walkopts + subrepoopts + dryrunopts,
144 walkopts + subrepoopts + dryrunopts,
145 _('[OPTION]... [FILE]...'))
145 _('[OPTION]... [FILE]...'))
146 def add(ui, repo, *pats, **opts):
146 def add(ui, repo, *pats, **opts):
147 """add the specified files on the next commit
147 """add the specified files on the next commit
148
148
149 Schedule files to be version controlled and added to the
149 Schedule files to be version controlled and added to the
150 repository.
150 repository.
151
151
152 The files will be added to the repository at the next commit. To
152 The files will be added to the repository at the next commit. To
153 undo an add before that, see :hg:`forget`.
153 undo an add before that, see :hg:`forget`.
154
154
155 If no names are given, add all files to the repository.
155 If no names are given, add all files to the repository.
156
156
157 .. container:: verbose
157 .. container:: verbose
158
158
159 An example showing how new (unknown) files are added
159 An example showing how new (unknown) files are added
160 automatically by :hg:`add`::
160 automatically by :hg:`add`::
161
161
162 $ ls
162 $ ls
163 foo.c
163 foo.c
164 $ hg status
164 $ hg status
165 ? foo.c
165 ? foo.c
166 $ hg add
166 $ hg add
167 adding foo.c
167 adding foo.c
168 $ hg status
168 $ hg status
169 A foo.c
169 A foo.c
170
170
171 Returns 0 if all files are successfully added.
171 Returns 0 if all files are successfully added.
172 """
172 """
173
173
174 m = scmutil.match(repo[None], pats, opts)
174 m = scmutil.match(repo[None], pats, opts)
175 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
175 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
176 opts.get('subrepos'), prefix="", explicitonly=False)
176 opts.get('subrepos'), prefix="", explicitonly=False)
177 return rejected and 1 or 0
177 return rejected and 1 or 0
178
178
179 @command('addremove',
179 @command('addremove',
180 similarityopts + walkopts + dryrunopts,
180 similarityopts + walkopts + dryrunopts,
181 _('[OPTION]... [FILE]...'))
181 _('[OPTION]... [FILE]...'))
182 def addremove(ui, repo, *pats, **opts):
182 def addremove(ui, repo, *pats, **opts):
183 """add all new files, delete all missing files
183 """add all new files, delete all missing files
184
184
185 Add all new files and remove all missing files from the
185 Add all new files and remove all missing files from the
186 repository.
186 repository.
187
187
188 New files are ignored if they match any of the patterns in
188 New files are ignored if they match any of the patterns in
189 ``.hgignore``. As with add, these changes take effect at the next
189 ``.hgignore``. As with add, these changes take effect at the next
190 commit.
190 commit.
191
191
192 Use the -s/--similarity option to detect renamed files. With a
192 Use the -s/--similarity option to detect renamed files. With a
193 parameter greater than 0, this compares every removed file with
193 parameter greater than 0, this compares every removed file with
194 every added file and records those similar enough as renames. This
194 every added file and records those similar enough as renames. This
195 option takes a percentage between 0 (disabled) and 100 (files must
195 option takes a percentage between 0 (disabled) and 100 (files must
196 be identical) as its parameter. Detecting renamed files this way
196 be identical) as its parameter. Detecting renamed files this way
197 can be expensive. After using this option, :hg:`status -C` can be
197 can be expensive. After using this option, :hg:`status -C` can be
198 used to check which files were identified as moved or renamed.
198 used to check which files were identified as moved or renamed.
199
199
200 Returns 0 if all files are successfully added.
200 Returns 0 if all files are successfully added.
201 """
201 """
202 try:
202 try:
203 sim = float(opts.get('similarity') or 100)
203 sim = float(opts.get('similarity') or 100)
204 except ValueError:
204 except ValueError:
205 raise util.Abort(_('similarity must be a number'))
205 raise util.Abort(_('similarity must be a number'))
206 if sim < 0 or sim > 100:
206 if sim < 0 or sim > 100:
207 raise util.Abort(_('similarity must be between 0 and 100'))
207 raise util.Abort(_('similarity must be between 0 and 100'))
208 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
208 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
209
209
210 @command('^annotate|blame',
210 @command('^annotate|blame',
211 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
211 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
212 ('', 'follow', None,
212 ('', 'follow', None,
213 _('follow copies/renames and list the filename (DEPRECATED)')),
213 _('follow copies/renames and list the filename (DEPRECATED)')),
214 ('', 'no-follow', None, _("don't follow copies and renames")),
214 ('', 'no-follow', None, _("don't follow copies and renames")),
215 ('a', 'text', None, _('treat all files as text')),
215 ('a', 'text', None, _('treat all files as text')),
216 ('u', 'user', None, _('list the author (long with -v)')),
216 ('u', 'user', None, _('list the author (long with -v)')),
217 ('f', 'file', None, _('list the filename')),
217 ('f', 'file', None, _('list the filename')),
218 ('d', 'date', None, _('list the date (short with -q)')),
218 ('d', 'date', None, _('list the date (short with -q)')),
219 ('n', 'number', None, _('list the revision number (default)')),
219 ('n', 'number', None, _('list the revision number (default)')),
220 ('c', 'changeset', None, _('list the changeset')),
220 ('c', 'changeset', None, _('list the changeset')),
221 ('l', 'line-number', None, _('show line number at the first appearance'))
221 ('l', 'line-number', None, _('show line number at the first appearance'))
222 ] + diffwsopts + walkopts,
222 ] + diffwsopts + walkopts,
223 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
223 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
224 def annotate(ui, repo, *pats, **opts):
224 def annotate(ui, repo, *pats, **opts):
225 """show changeset information by line for each file
225 """show changeset information by line for each file
226
226
227 List changes in files, showing the revision id responsible for
227 List changes in files, showing the revision id responsible for
228 each line
228 each line
229
229
230 This command is useful for discovering when a change was made and
230 This command is useful for discovering when a change was made and
231 by whom.
231 by whom.
232
232
233 Without the -a/--text option, annotate will avoid processing files
233 Without the -a/--text option, annotate will avoid processing files
234 it detects as binary. With -a, annotate will annotate the file
234 it detects as binary. With -a, annotate will annotate the file
235 anyway, although the results will probably be neither useful
235 anyway, although the results will probably be neither useful
236 nor desirable.
236 nor desirable.
237
237
238 Returns 0 on success.
238 Returns 0 on success.
239 """
239 """
240 if opts.get('follow'):
240 if opts.get('follow'):
241 # --follow is deprecated and now just an alias for -f/--file
241 # --follow is deprecated and now just an alias for -f/--file
242 # to mimic the behavior of Mercurial before version 1.5
242 # to mimic the behavior of Mercurial before version 1.5
243 opts['file'] = True
243 opts['file'] = True
244
244
245 datefunc = ui.quiet and util.shortdate or util.datestr
245 datefunc = ui.quiet and util.shortdate or util.datestr
246 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
246 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
247
247
248 if not pats:
248 if not pats:
249 raise util.Abort(_('at least one filename or pattern is required'))
249 raise util.Abort(_('at least one filename or pattern is required'))
250
250
251 hexfn = ui.debugflag and hex or short
251 hexfn = ui.debugflag and hex or short
252
252
253 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
253 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
254 ('number', ' ', lambda x: str(x[0].rev())),
254 ('number', ' ', lambda x: str(x[0].rev())),
255 ('changeset', ' ', lambda x: hexfn(x[0].node())),
255 ('changeset', ' ', lambda x: hexfn(x[0].node())),
256 ('date', ' ', getdate),
256 ('date', ' ', getdate),
257 ('file', ' ', lambda x: x[0].path()),
257 ('file', ' ', lambda x: x[0].path()),
258 ('line_number', ':', lambda x: str(x[1])),
258 ('line_number', ':', lambda x: str(x[1])),
259 ]
259 ]
260
260
261 if (not opts.get('user') and not opts.get('changeset')
261 if (not opts.get('user') and not opts.get('changeset')
262 and not opts.get('date') and not opts.get('file')):
262 and not opts.get('date') and not opts.get('file')):
263 opts['number'] = True
263 opts['number'] = True
264
264
265 linenumber = opts.get('line_number') is not None
265 linenumber = opts.get('line_number') is not None
266 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
266 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
267 raise util.Abort(_('at least one of -n/-c is required for -l'))
267 raise util.Abort(_('at least one of -n/-c is required for -l'))
268
268
269 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
269 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
270 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
270 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
271
271
272 def bad(x, y):
272 def bad(x, y):
273 raise util.Abort("%s: %s" % (x, y))
273 raise util.Abort("%s: %s" % (x, y))
274
274
275 ctx = scmutil.revsingle(repo, opts.get('rev'))
275 ctx = scmutil.revsingle(repo, opts.get('rev'))
276 m = scmutil.match(ctx, pats, opts)
276 m = scmutil.match(ctx, pats, opts)
277 m.bad = bad
277 m.bad = bad
278 follow = not opts.get('no_follow')
278 follow = not opts.get('no_follow')
279 diffopts = patch.diffopts(ui, opts, section='annotate')
279 diffopts = patch.diffopts(ui, opts, section='annotate')
280 for abs in ctx.walk(m):
280 for abs in ctx.walk(m):
281 fctx = ctx[abs]
281 fctx = ctx[abs]
282 if not opts.get('text') and util.binary(fctx.data()):
282 if not opts.get('text') and util.binary(fctx.data()):
283 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
283 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
284 continue
284 continue
285
285
286 lines = fctx.annotate(follow=follow, linenumber=linenumber,
286 lines = fctx.annotate(follow=follow, linenumber=linenumber,
287 diffopts=diffopts)
287 diffopts=diffopts)
288 pieces = []
288 pieces = []
289
289
290 for f, sep in funcmap:
290 for f, sep in funcmap:
291 l = [f(n) for n, dummy in lines]
291 l = [f(n) for n, dummy in lines]
292 if l:
292 if l:
293 sized = [(x, encoding.colwidth(x)) for x in l]
293 sized = [(x, encoding.colwidth(x)) for x in l]
294 ml = max([w for x, w in sized])
294 ml = max([w for x, w in sized])
295 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
295 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
296 for x, w in sized])
296 for x, w in sized])
297
297
298 if pieces:
298 if pieces:
299 for p, l in zip(zip(*pieces), lines):
299 for p, l in zip(zip(*pieces), lines):
300 ui.write("%s: %s" % ("".join(p), l[1]))
300 ui.write("%s: %s" % ("".join(p), l[1]))
301
301
302 if lines and not lines[-1][1].endswith('\n'):
302 if lines and not lines[-1][1].endswith('\n'):
303 ui.write('\n')
303 ui.write('\n')
304
304
305 @command('archive',
305 @command('archive',
306 [('', 'no-decode', None, _('do not pass files through decoders')),
306 [('', 'no-decode', None, _('do not pass files through decoders')),
307 ('p', 'prefix', '', _('directory prefix for files in archive'),
307 ('p', 'prefix', '', _('directory prefix for files in archive'),
308 _('PREFIX')),
308 _('PREFIX')),
309 ('r', 'rev', '', _('revision to distribute'), _('REV')),
309 ('r', 'rev', '', _('revision to distribute'), _('REV')),
310 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
310 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
311 ] + subrepoopts + walkopts,
311 ] + subrepoopts + walkopts,
312 _('[OPTION]... DEST'))
312 _('[OPTION]... DEST'))
313 def archive(ui, repo, dest, **opts):
313 def archive(ui, repo, dest, **opts):
314 '''create an unversioned archive of a repository revision
314 '''create an unversioned archive of a repository revision
315
315
316 By default, the revision used is the parent of the working
316 By default, the revision used is the parent of the working
317 directory; use -r/--rev to specify a different revision.
317 directory; use -r/--rev to specify a different revision.
318
318
319 The archive type is automatically detected based on file
319 The archive type is automatically detected based on file
320 extension (or override using -t/--type).
320 extension (or override using -t/--type).
321
321
322 .. container:: verbose
322 .. container:: verbose
323
323
324 Examples:
324 Examples:
325
325
326 - create a zip file containing the 1.0 release::
326 - create a zip file containing the 1.0 release::
327
327
328 hg archive -r 1.0 project-1.0.zip
328 hg archive -r 1.0 project-1.0.zip
329
329
330 - create a tarball excluding .hg files::
330 - create a tarball excluding .hg files::
331
331
332 hg archive project.tar.gz -X ".hg*"
332 hg archive project.tar.gz -X ".hg*"
333
333
334 Valid types are:
334 Valid types are:
335
335
336 :``files``: a directory full of files (default)
336 :``files``: a directory full of files (default)
337 :``tar``: tar archive, uncompressed
337 :``tar``: tar archive, uncompressed
338 :``tbz2``: tar archive, compressed using bzip2
338 :``tbz2``: tar archive, compressed using bzip2
339 :``tgz``: tar archive, compressed using gzip
339 :``tgz``: tar archive, compressed using gzip
340 :``uzip``: zip archive, uncompressed
340 :``uzip``: zip archive, uncompressed
341 :``zip``: zip archive, compressed using deflate
341 :``zip``: zip archive, compressed using deflate
342
342
343 The exact name of the destination archive or directory is given
343 The exact name of the destination archive or directory is given
344 using a format string; see :hg:`help export` for details.
344 using a format string; see :hg:`help export` for details.
345
345
346 Each member added to an archive file has a directory prefix
346 Each member added to an archive file has a directory prefix
347 prepended. Use -p/--prefix to specify a format string for the
347 prepended. Use -p/--prefix to specify a format string for the
348 prefix. The default is the basename of the archive, with suffixes
348 prefix. The default is the basename of the archive, with suffixes
349 removed.
349 removed.
350
350
351 Returns 0 on success.
351 Returns 0 on success.
352 '''
352 '''
353
353
354 ctx = scmutil.revsingle(repo, opts.get('rev'))
354 ctx = scmutil.revsingle(repo, opts.get('rev'))
355 if not ctx:
355 if not ctx:
356 raise util.Abort(_('no working directory: please specify a revision'))
356 raise util.Abort(_('no working directory: please specify a revision'))
357 node = ctx.node()
357 node = ctx.node()
358 dest = cmdutil.makefilename(repo, dest, node)
358 dest = cmdutil.makefilename(repo, dest, node)
359 if os.path.realpath(dest) == repo.root:
359 if os.path.realpath(dest) == repo.root:
360 raise util.Abort(_('repository root cannot be destination'))
360 raise util.Abort(_('repository root cannot be destination'))
361
361
362 kind = opts.get('type') or archival.guesskind(dest) or 'files'
362 kind = opts.get('type') or archival.guesskind(dest) or 'files'
363 prefix = opts.get('prefix')
363 prefix = opts.get('prefix')
364
364
365 if dest == '-':
365 if dest == '-':
366 if kind == 'files':
366 if kind == 'files':
367 raise util.Abort(_('cannot archive plain files to stdout'))
367 raise util.Abort(_('cannot archive plain files to stdout'))
368 dest = cmdutil.makefileobj(repo, dest)
368 dest = cmdutil.makefileobj(repo, dest)
369 if not prefix:
369 if not prefix:
370 prefix = os.path.basename(repo.root) + '-%h'
370 prefix = os.path.basename(repo.root) + '-%h'
371
371
372 prefix = cmdutil.makefilename(repo, prefix, node)
372 prefix = cmdutil.makefilename(repo, prefix, node)
373 matchfn = scmutil.match(ctx, [], opts)
373 matchfn = scmutil.match(ctx, [], opts)
374 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
374 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
375 matchfn, prefix, subrepos=opts.get('subrepos'))
375 matchfn, prefix, subrepos=opts.get('subrepos'))
376
376
377 @command('backout',
377 @command('backout',
378 [('', 'merge', None, _('merge with old dirstate parent after backout')),
378 [('', 'merge', None, _('merge with old dirstate parent after backout')),
379 ('', 'parent', '',
379 ('', 'parent', '',
380 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
380 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
381 ('r', 'rev', '', _('revision to backout'), _('REV')),
381 ('r', 'rev', '', _('revision to backout'), _('REV')),
382 ] + mergetoolopts + walkopts + commitopts + commitopts2,
382 ] + mergetoolopts + walkopts + commitopts + commitopts2,
383 _('[OPTION]... [-r] REV'))
383 _('[OPTION]... [-r] REV'))
384 def backout(ui, repo, node=None, rev=None, **opts):
384 def backout(ui, repo, node=None, rev=None, **opts):
385 '''reverse effect of earlier changeset
385 '''reverse effect of earlier changeset
386
386
387 Prepare a new changeset with the effect of REV undone in the
387 Prepare a new changeset with the effect of REV undone in the
388 current working directory.
388 current working directory.
389
389
390 If REV is the parent of the working directory, then this new changeset
390 If REV is the parent of the working directory, then this new changeset
391 is committed automatically. Otherwise, hg needs to merge the
391 is committed automatically. Otherwise, hg needs to merge the
392 changes and the merged result is left uncommitted.
392 changes and the merged result is left uncommitted.
393
393
394 .. note::
394 .. note::
395 backout cannot be used to fix either an unwanted or
395 backout cannot be used to fix either an unwanted or
396 incorrect merge.
396 incorrect merge.
397
397
398 .. container:: verbose
398 .. container:: verbose
399
399
400 By default, the pending changeset will have one parent,
400 By default, the pending changeset will have one parent,
401 maintaining a linear history. With --merge, the pending
401 maintaining a linear history. With --merge, the pending
402 changeset will instead have two parents: the old parent of the
402 changeset will instead have two parents: the old parent of the
403 working directory and a new child of REV that simply undoes REV.
403 working directory and a new child of REV that simply undoes REV.
404
404
405 Before version 1.7, the behavior without --merge was equivalent
405 Before version 1.7, the behavior without --merge was equivalent
406 to specifying --merge followed by :hg:`update --clean .` to
406 to specifying --merge followed by :hg:`update --clean .` to
407 cancel the merge and leave the child of REV as a head to be
407 cancel the merge and leave the child of REV as a head to be
408 merged separately.
408 merged separately.
409
409
410 See :hg:`help dates` for a list of formats valid for -d/--date.
410 See :hg:`help dates` for a list of formats valid for -d/--date.
411
411
412 Returns 0 on success.
412 Returns 0 on success.
413 '''
413 '''
414 if rev and node:
414 if rev and node:
415 raise util.Abort(_("please specify just one revision"))
415 raise util.Abort(_("please specify just one revision"))
416
416
417 if not rev:
417 if not rev:
418 rev = node
418 rev = node
419
419
420 if not rev:
420 if not rev:
421 raise util.Abort(_("please specify a revision to backout"))
421 raise util.Abort(_("please specify a revision to backout"))
422
422
423 date = opts.get('date')
423 date = opts.get('date')
424 if date:
424 if date:
425 opts['date'] = util.parsedate(date)
425 opts['date'] = util.parsedate(date)
426
426
427 cmdutil.bailifchanged(repo)
427 cmdutil.bailifchanged(repo)
428 node = scmutil.revsingle(repo, rev).node()
428 node = scmutil.revsingle(repo, rev).node()
429
429
430 op1, op2 = repo.dirstate.parents()
430 op1, op2 = repo.dirstate.parents()
431 a = repo.changelog.ancestor(op1, node)
431 a = repo.changelog.ancestor(op1, node)
432 if a != node:
432 if a != node:
433 raise util.Abort(_('cannot backout change on a different branch'))
433 raise util.Abort(_('cannot backout change on a different branch'))
434
434
435 p1, p2 = repo.changelog.parents(node)
435 p1, p2 = repo.changelog.parents(node)
436 if p1 == nullid:
436 if p1 == nullid:
437 raise util.Abort(_('cannot backout a change with no parents'))
437 raise util.Abort(_('cannot backout a change with no parents'))
438 if p2 != nullid:
438 if p2 != nullid:
439 if not opts.get('parent'):
439 if not opts.get('parent'):
440 raise util.Abort(_('cannot backout a merge changeset'))
440 raise util.Abort(_('cannot backout a merge changeset'))
441 p = repo.lookup(opts['parent'])
441 p = repo.lookup(opts['parent'])
442 if p not in (p1, p2):
442 if p not in (p1, p2):
443 raise util.Abort(_('%s is not a parent of %s') %
443 raise util.Abort(_('%s is not a parent of %s') %
444 (short(p), short(node)))
444 (short(p), short(node)))
445 parent = p
445 parent = p
446 else:
446 else:
447 if opts.get('parent'):
447 if opts.get('parent'):
448 raise util.Abort(_('cannot use --parent on non-merge changeset'))
448 raise util.Abort(_('cannot use --parent on non-merge changeset'))
449 parent = p1
449 parent = p1
450
450
451 # the backout should appear on the same branch
451 # the backout should appear on the same branch
452 wlock = repo.wlock()
452 wlock = repo.wlock()
453 try:
453 try:
454 branch = repo.dirstate.branch()
454 branch = repo.dirstate.branch()
455 hg.clean(repo, node, show_stats=False)
455 hg.clean(repo, node, show_stats=False)
456 repo.dirstate.setbranch(branch)
456 repo.dirstate.setbranch(branch)
457 revert_opts = opts.copy()
457 revert_opts = opts.copy()
458 revert_opts['date'] = None
458 revert_opts['date'] = None
459 revert_opts['all'] = True
459 revert_opts['all'] = True
460 revert_opts['rev'] = hex(parent)
460 revert_opts['rev'] = hex(parent)
461 revert_opts['no_backup'] = None
461 revert_opts['no_backup'] = None
462 revert(ui, repo, **revert_opts)
462 revert(ui, repo, **revert_opts)
463 if not opts.get('merge') and op1 != node:
463 if not opts.get('merge') and op1 != node:
464 try:
464 try:
465 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
465 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
466 return hg.update(repo, op1)
466 return hg.update(repo, op1)
467 finally:
467 finally:
468 ui.setconfig('ui', 'forcemerge', '')
468 ui.setconfig('ui', 'forcemerge', '')
469
469
470 commit_opts = opts.copy()
470 commit_opts = opts.copy()
471 commit_opts['addremove'] = False
471 commit_opts['addremove'] = False
472 if not commit_opts['message'] and not commit_opts['logfile']:
472 if not commit_opts['message'] and not commit_opts['logfile']:
473 # we don't translate commit messages
473 # we don't translate commit messages
474 commit_opts['message'] = "Backed out changeset %s" % short(node)
474 commit_opts['message'] = "Backed out changeset %s" % short(node)
475 commit_opts['force_editor'] = True
475 commit_opts['force_editor'] = True
476 commit(ui, repo, **commit_opts)
476 commit(ui, repo, **commit_opts)
477 def nice(node):
477 def nice(node):
478 return '%d:%s' % (repo.changelog.rev(node), short(node))
478 return '%d:%s' % (repo.changelog.rev(node), short(node))
479 ui.status(_('changeset %s backs out changeset %s\n') %
479 ui.status(_('changeset %s backs out changeset %s\n') %
480 (nice(repo.changelog.tip()), nice(node)))
480 (nice(repo.changelog.tip()), nice(node)))
481 if opts.get('merge') and op1 != node:
481 if opts.get('merge') and op1 != node:
482 hg.clean(repo, op1, show_stats=False)
482 hg.clean(repo, op1, show_stats=False)
483 ui.status(_('merging with changeset %s\n')
483 ui.status(_('merging with changeset %s\n')
484 % nice(repo.changelog.tip()))
484 % nice(repo.changelog.tip()))
485 try:
485 try:
486 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
486 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
487 return hg.merge(repo, hex(repo.changelog.tip()))
487 return hg.merge(repo, hex(repo.changelog.tip()))
488 finally:
488 finally:
489 ui.setconfig('ui', 'forcemerge', '')
489 ui.setconfig('ui', 'forcemerge', '')
490 finally:
490 finally:
491 wlock.release()
491 wlock.release()
492 return 0
492 return 0
493
493
494 @command('bisect',
494 @command('bisect',
495 [('r', 'reset', False, _('reset bisect state')),
495 [('r', 'reset', False, _('reset bisect state')),
496 ('g', 'good', False, _('mark changeset good')),
496 ('g', 'good', False, _('mark changeset good')),
497 ('b', 'bad', False, _('mark changeset bad')),
497 ('b', 'bad', False, _('mark changeset bad')),
498 ('s', 'skip', False, _('skip testing changeset')),
498 ('s', 'skip', False, _('skip testing changeset')),
499 ('e', 'extend', False, _('extend the bisect range')),
499 ('e', 'extend', False, _('extend the bisect range')),
500 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
500 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
501 ('U', 'noupdate', False, _('do not update to target'))],
501 ('U', 'noupdate', False, _('do not update to target'))],
502 _("[-gbsr] [-U] [-c CMD] [REV]"))
502 _("[-gbsr] [-U] [-c CMD] [REV]"))
503 def bisect(ui, repo, rev=None, extra=None, command=None,
503 def bisect(ui, repo, rev=None, extra=None, command=None,
504 reset=None, good=None, bad=None, skip=None, extend=None,
504 reset=None, good=None, bad=None, skip=None, extend=None,
505 noupdate=None):
505 noupdate=None):
506 """subdivision search of changesets
506 """subdivision search of changesets
507
507
508 This command helps to find changesets which introduce problems. To
508 This command helps to find changesets which introduce problems. To
509 use, mark the earliest changeset you know exhibits the problem as
509 use, mark the earliest changeset you know exhibits the problem as
510 bad, then mark the latest changeset which is free from the problem
510 bad, then mark the latest changeset which is free from the problem
511 as good. Bisect will update your working directory to a revision
511 as good. Bisect will update your working directory to a revision
512 for testing (unless the -U/--noupdate option is specified). Once
512 for testing (unless the -U/--noupdate option is specified). Once
513 you have performed tests, mark the working directory as good or
513 you have performed tests, mark the working directory as good or
514 bad, and bisect will either update to another candidate changeset
514 bad, and bisect will either update to another candidate changeset
515 or announce that it has found the bad revision.
515 or announce that it has found the bad revision.
516
516
517 As a shortcut, you can also use the revision argument to mark a
517 As a shortcut, you can also use the revision argument to mark a
518 revision as good or bad without checking it out first.
518 revision as good or bad without checking it out first.
519
519
520 If you supply a command, it will be used for automatic bisection.
520 If you supply a command, it will be used for automatic bisection.
521 Its exit status will be used to mark revisions as good or bad:
521 Its exit status will be used to mark revisions as good or bad:
522 status 0 means good, 125 means to skip the revision, 127
522 status 0 means good, 125 means to skip the revision, 127
523 (command not found) will abort the bisection, and any other
523 (command not found) will abort the bisection, and any other
524 non-zero exit status means the revision is bad.
524 non-zero exit status means the revision is bad.
525
525
526 .. container:: verbose
526 .. container:: verbose
527
527
528 Some examples:
528 Some examples:
529
529
530 - start a bisection with known bad revision 12, and good revision 34::
530 - start a bisection with known bad revision 12, and good revision 34::
531
531
532 hg bisect --bad 34
532 hg bisect --bad 34
533 hg bisect --good 12
533 hg bisect --good 12
534
534
535 - advance the current bisection by marking current revision as good or
535 - advance the current bisection by marking current revision as good or
536 bad::
536 bad::
537
537
538 hg bisect --good
538 hg bisect --good
539 hg bisect --bad
539 hg bisect --bad
540
540
541 - mark the current revision, or a known revision, to be skipped (eg. if
541 - mark the current revision, or a known revision, to be skipped (eg. if
542 that revision is not usable because of another issue)::
542 that revision is not usable because of another issue)::
543
543
544 hg bisect --skip
544 hg bisect --skip
545 hg bisect --skip 23
545 hg bisect --skip 23
546
546
547 - forget the current bisection::
547 - forget the current bisection::
548
548
549 hg bisect --reset
549 hg bisect --reset
550
550
551 - use 'make && make tests' to automatically find the first broken
551 - use 'make && make tests' to automatically find the first broken
552 revision::
552 revision::
553
553
554 hg bisect --reset
554 hg bisect --reset
555 hg bisect --bad 34
555 hg bisect --bad 34
556 hg bisect --good 12
556 hg bisect --good 12
557 hg bisect --command 'make && make tests'
557 hg bisect --command 'make && make tests'
558
558
559 - see all changesets whose states are already known in the current
559 - see all changesets whose states are already known in the current
560 bisection::
560 bisection::
561
561
562 hg log -r "bisect(pruned)"
562 hg log -r "bisect(pruned)"
563
563
564 - see all changesets that took part in the current bisection::
564 - see all changesets that took part in the current bisection::
565
565
566 hg log -r "bisect(range)"
566 hg log -r "bisect(range)"
567
567
568 - with the graphlog extension, you can even get a nice graph::
568 - with the graphlog extension, you can even get a nice graph::
569
569
570 hg log --graph -r "bisect(range)"
570 hg log --graph -r "bisect(range)"
571
571
572 See :hg:`help revsets` for more about the `bisect()` keyword.
572 See :hg:`help revsets` for more about the `bisect()` keyword.
573
573
574 Returns 0 on success.
574 Returns 0 on success.
575 """
575 """
576 def extendbisectrange(nodes, good):
576 def extendbisectrange(nodes, good):
577 # bisect is incomplete when it ends on a merge node and
577 # bisect is incomplete when it ends on a merge node and
578 # one of the parent was not checked.
578 # one of the parent was not checked.
579 parents = repo[nodes[0]].parents()
579 parents = repo[nodes[0]].parents()
580 if len(parents) > 1:
580 if len(parents) > 1:
581 side = good and state['bad'] or state['good']
581 side = good and state['bad'] or state['good']
582 num = len(set(i.node() for i in parents) & set(side))
582 num = len(set(i.node() for i in parents) & set(side))
583 if num == 1:
583 if num == 1:
584 return parents[0].ancestor(parents[1])
584 return parents[0].ancestor(parents[1])
585 return None
585 return None
586
586
587 def print_result(nodes, good):
587 def print_result(nodes, good):
588 displayer = cmdutil.show_changeset(ui, repo, {})
588 displayer = cmdutil.show_changeset(ui, repo, {})
589 if len(nodes) == 1:
589 if len(nodes) == 1:
590 # narrowed it down to a single revision
590 # narrowed it down to a single revision
591 if good:
591 if good:
592 ui.write(_("The first good revision is:\n"))
592 ui.write(_("The first good revision is:\n"))
593 else:
593 else:
594 ui.write(_("The first bad revision is:\n"))
594 ui.write(_("The first bad revision is:\n"))
595 displayer.show(repo[nodes[0]])
595 displayer.show(repo[nodes[0]])
596 extendnode = extendbisectrange(nodes, good)
596 extendnode = extendbisectrange(nodes, good)
597 if extendnode is not None:
597 if extendnode is not None:
598 ui.write(_('Not all ancestors of this changeset have been'
598 ui.write(_('Not all ancestors of this changeset have been'
599 ' checked.\nUse bisect --extend to continue the '
599 ' checked.\nUse bisect --extend to continue the '
600 'bisection from\nthe common ancestor, %s.\n')
600 'bisection from\nthe common ancestor, %s.\n')
601 % extendnode)
601 % extendnode)
602 else:
602 else:
603 # multiple possible revisions
603 # multiple possible revisions
604 if good:
604 if good:
605 ui.write(_("Due to skipped revisions, the first "
605 ui.write(_("Due to skipped revisions, the first "
606 "good revision could be any of:\n"))
606 "good revision could be any of:\n"))
607 else:
607 else:
608 ui.write(_("Due to skipped revisions, the first "
608 ui.write(_("Due to skipped revisions, the first "
609 "bad revision could be any of:\n"))
609 "bad revision could be any of:\n"))
610 for n in nodes:
610 for n in nodes:
611 displayer.show(repo[n])
611 displayer.show(repo[n])
612 displayer.close()
612 displayer.close()
613
613
614 def check_state(state, interactive=True):
614 def check_state(state, interactive=True):
615 if not state['good'] or not state['bad']:
615 if not state['good'] or not state['bad']:
616 if (good or bad or skip or reset) and interactive:
616 if (good or bad or skip or reset) and interactive:
617 return
617 return
618 if not state['good']:
618 if not state['good']:
619 raise util.Abort(_('cannot bisect (no known good revisions)'))
619 raise util.Abort(_('cannot bisect (no known good revisions)'))
620 else:
620 else:
621 raise util.Abort(_('cannot bisect (no known bad revisions)'))
621 raise util.Abort(_('cannot bisect (no known bad revisions)'))
622 return True
622 return True
623
623
624 # backward compatibility
624 # backward compatibility
625 if rev in "good bad reset init".split():
625 if rev in "good bad reset init".split():
626 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
626 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
627 cmd, rev, extra = rev, extra, None
627 cmd, rev, extra = rev, extra, None
628 if cmd == "good":
628 if cmd == "good":
629 good = True
629 good = True
630 elif cmd == "bad":
630 elif cmd == "bad":
631 bad = True
631 bad = True
632 else:
632 else:
633 reset = True
633 reset = True
634 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
634 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
635 raise util.Abort(_('incompatible arguments'))
635 raise util.Abort(_('incompatible arguments'))
636
636
637 if reset:
637 if reset:
638 p = repo.join("bisect.state")
638 p = repo.join("bisect.state")
639 if os.path.exists(p):
639 if os.path.exists(p):
640 os.unlink(p)
640 os.unlink(p)
641 return
641 return
642
642
643 state = hbisect.load_state(repo)
643 state = hbisect.load_state(repo)
644
644
645 if command:
645 if command:
646 changesets = 1
646 changesets = 1
647 try:
647 try:
648 while changesets:
648 while changesets:
649 # update state
649 # update state
650 status = util.system(command, out=ui.fout)
650 status = util.system(command, out=ui.fout)
651 if status == 125:
651 if status == 125:
652 transition = "skip"
652 transition = "skip"
653 elif status == 0:
653 elif status == 0:
654 transition = "good"
654 transition = "good"
655 # status < 0 means process was killed
655 # status < 0 means process was killed
656 elif status == 127:
656 elif status == 127:
657 raise util.Abort(_("failed to execute %s") % command)
657 raise util.Abort(_("failed to execute %s") % command)
658 elif status < 0:
658 elif status < 0:
659 raise util.Abort(_("%s killed") % command)
659 raise util.Abort(_("%s killed") % command)
660 else:
660 else:
661 transition = "bad"
661 transition = "bad"
662 ctx = scmutil.revsingle(repo, rev)
662 ctx = scmutil.revsingle(repo, rev)
663 rev = None # clear for future iterations
663 rev = None # clear for future iterations
664 state[transition].append(ctx.node())
664 state[transition].append(ctx.node())
665 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
665 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
666 check_state(state, interactive=False)
666 check_state(state, interactive=False)
667 # bisect
667 # bisect
668 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
668 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
669 # update to next check
669 # update to next check
670 cmdutil.bailifchanged(repo)
670 cmdutil.bailifchanged(repo)
671 hg.clean(repo, nodes[0], show_stats=False)
671 hg.clean(repo, nodes[0], show_stats=False)
672 finally:
672 finally:
673 hbisect.save_state(repo, state)
673 hbisect.save_state(repo, state)
674 print_result(nodes, good)
674 print_result(nodes, good)
675 return
675 return
676
676
677 # update state
677 # update state
678
678
679 if rev:
679 if rev:
680 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
680 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
681 else:
681 else:
682 nodes = [repo.lookup('.')]
682 nodes = [repo.lookup('.')]
683
683
684 if good or bad or skip:
684 if good or bad or skip:
685 if good:
685 if good:
686 state['good'] += nodes
686 state['good'] += nodes
687 elif bad:
687 elif bad:
688 state['bad'] += nodes
688 state['bad'] += nodes
689 elif skip:
689 elif skip:
690 state['skip'] += nodes
690 state['skip'] += nodes
691 hbisect.save_state(repo, state)
691 hbisect.save_state(repo, state)
692
692
693 if not check_state(state):
693 if not check_state(state):
694 return
694 return
695
695
696 # actually bisect
696 # actually bisect
697 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
697 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
698 if extend:
698 if extend:
699 if not changesets:
699 if not changesets:
700 extendnode = extendbisectrange(nodes, good)
700 extendnode = extendbisectrange(nodes, good)
701 if extendnode is not None:
701 if extendnode is not None:
702 ui.write(_("Extending search to changeset %d:%s\n"
702 ui.write(_("Extending search to changeset %d:%s\n"
703 % (extendnode.rev(), extendnode)))
703 % (extendnode.rev(), extendnode)))
704 if noupdate:
704 if noupdate:
705 return
705 return
706 cmdutil.bailifchanged(repo)
706 cmdutil.bailifchanged(repo)
707 return hg.clean(repo, extendnode.node())
707 return hg.clean(repo, extendnode.node())
708 raise util.Abort(_("nothing to extend"))
708 raise util.Abort(_("nothing to extend"))
709
709
710 if changesets == 0:
710 if changesets == 0:
711 print_result(nodes, good)
711 print_result(nodes, good)
712 else:
712 else:
713 assert len(nodes) == 1 # only a single node can be tested next
713 assert len(nodes) == 1 # only a single node can be tested next
714 node = nodes[0]
714 node = nodes[0]
715 # compute the approximate number of remaining tests
715 # compute the approximate number of remaining tests
716 tests, size = 0, 2
716 tests, size = 0, 2
717 while size <= changesets:
717 while size <= changesets:
718 tests, size = tests + 1, size * 2
718 tests, size = tests + 1, size * 2
719 rev = repo.changelog.rev(node)
719 rev = repo.changelog.rev(node)
720 ui.write(_("Testing changeset %d:%s "
720 ui.write(_("Testing changeset %d:%s "
721 "(%d changesets remaining, ~%d tests)\n")
721 "(%d changesets remaining, ~%d tests)\n")
722 % (rev, short(node), changesets, tests))
722 % (rev, short(node), changesets, tests))
723 if not noupdate:
723 if not noupdate:
724 cmdutil.bailifchanged(repo)
724 cmdutil.bailifchanged(repo)
725 return hg.clean(repo, node)
725 return hg.clean(repo, node)
726
726
727 @command('bookmarks',
727 @command('bookmarks',
728 [('f', 'force', False, _('force')),
728 [('f', 'force', False, _('force')),
729 ('r', 'rev', '', _('revision'), _('REV')),
729 ('r', 'rev', '', _('revision'), _('REV')),
730 ('d', 'delete', False, _('delete a given bookmark')),
730 ('d', 'delete', False, _('delete a given bookmark')),
731 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
731 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
732 ('i', 'inactive', False, _('mark a bookmark inactive'))],
732 ('i', 'inactive', False, _('mark a bookmark inactive'))],
733 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
733 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
734 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
734 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
735 rename=None, inactive=False):
735 rename=None, inactive=False):
736 '''track a line of development with movable markers
736 '''track a line of development with movable markers
737
737
738 Bookmarks are pointers to certain commits that move when committing.
738 Bookmarks are pointers to certain commits that move when committing.
739 Bookmarks are local. They can be renamed, copied and deleted. It is
739 Bookmarks are local. They can be renamed, copied and deleted. It is
740 possible to use :hg:`merge NAME` to merge from a given bookmark, and
740 possible to use :hg:`merge NAME` to merge from a given bookmark, and
741 :hg:`update NAME` to update to a given bookmark.
741 :hg:`update NAME` to update to a given bookmark.
742
742
743 You can use :hg:`bookmark NAME` to set a bookmark on the working
743 You can use :hg:`bookmark NAME` to set a bookmark on the working
744 directory's parent revision with the given name. If you specify
744 directory's parent revision with the given name. If you specify
745 a revision using -r REV (where REV may be an existing bookmark),
745 a revision using -r REV (where REV may be an existing bookmark),
746 the bookmark is assigned to that revision.
746 the bookmark is assigned to that revision.
747
747
748 Bookmarks can be pushed and pulled between repositories (see :hg:`help
748 Bookmarks can be pushed and pulled between repositories (see :hg:`help
749 push` and :hg:`help pull`). This requires both the local and remote
749 push` and :hg:`help pull`). This requires both the local and remote
750 repositories to support bookmarks. For versions prior to 1.8, this means
750 repositories to support bookmarks. For versions prior to 1.8, this means
751 the bookmarks extension must be enabled.
751 the bookmarks extension must be enabled.
752
752
753 With -i/--inactive, the new bookmark will not be made the active
753 With -i/--inactive, the new bookmark will not be made the active
754 bookmark. If -r/--rev is given, the new bookmark will not be made
754 bookmark. If -r/--rev is given, the new bookmark will not be made
755 active even if -i/--inactive is not given. If no NAME is given, the
755 active even if -i/--inactive is not given. If no NAME is given, the
756 current active bookmark will be marked inactive.
756 current active bookmark will be marked inactive.
757 '''
757 '''
758 hexfn = ui.debugflag and hex or short
758 hexfn = ui.debugflag and hex or short
759 marks = repo._bookmarks
759 marks = repo._bookmarks
760 cur = repo.changectx('.').node()
760 cur = repo.changectx('.').node()
761
761
762 if delete:
762 if delete:
763 if mark is None:
763 if mark is None:
764 raise util.Abort(_("bookmark name required"))
764 raise util.Abort(_("bookmark name required"))
765 if mark not in marks:
765 if mark not in marks:
766 raise util.Abort(_("bookmark '%s' does not exist") % mark)
766 raise util.Abort(_("bookmark '%s' does not exist") % mark)
767 if mark == repo._bookmarkcurrent:
767 if mark == repo._bookmarkcurrent:
768 bookmarks.setcurrent(repo, None)
768 bookmarks.setcurrent(repo, None)
769 del marks[mark]
769 del marks[mark]
770 bookmarks.write(repo)
770 bookmarks.write(repo)
771 return
771 return
772
772
773 if rename:
773 if rename:
774 if rename not in marks:
774 if rename not in marks:
775 raise util.Abort(_("bookmark '%s' does not exist") % rename)
775 raise util.Abort(_("bookmark '%s' does not exist") % rename)
776 if mark in marks and not force:
776 if mark in marks and not force:
777 raise util.Abort(_("bookmark '%s' already exists "
777 raise util.Abort(_("bookmark '%s' already exists "
778 "(use -f to force)") % mark)
778 "(use -f to force)") % mark)
779 if mark is None:
779 if mark is None:
780 raise util.Abort(_("new bookmark name required"))
780 raise util.Abort(_("new bookmark name required"))
781 marks[mark] = marks[rename]
781 marks[mark] = marks[rename]
782 if repo._bookmarkcurrent == rename and not inactive:
782 if repo._bookmarkcurrent == rename and not inactive:
783 bookmarks.setcurrent(repo, mark)
783 bookmarks.setcurrent(repo, mark)
784 del marks[rename]
784 del marks[rename]
785 bookmarks.write(repo)
785 bookmarks.write(repo)
786 return
786 return
787
787
788 if mark is not None:
788 if mark is not None:
789 if "\n" in mark:
789 if "\n" in mark:
790 raise util.Abort(_("bookmark name cannot contain newlines"))
790 raise util.Abort(_("bookmark name cannot contain newlines"))
791 mark = mark.strip()
791 mark = mark.strip()
792 if not mark:
792 if not mark:
793 raise util.Abort(_("bookmark names cannot consist entirely of "
793 raise util.Abort(_("bookmark names cannot consist entirely of "
794 "whitespace"))
794 "whitespace"))
795 if inactive and mark == repo._bookmarkcurrent:
795 if inactive and mark == repo._bookmarkcurrent:
796 bookmarks.setcurrent(repo, None)
796 bookmarks.setcurrent(repo, None)
797 return
797 return
798 if mark in marks and not force:
798 if mark in marks and not force:
799 raise util.Abort(_("bookmark '%s' already exists "
799 raise util.Abort(_("bookmark '%s' already exists "
800 "(use -f to force)") % mark)
800 "(use -f to force)") % mark)
801 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
801 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
802 and not force):
802 and not force):
803 raise util.Abort(
803 raise util.Abort(
804 _("a bookmark cannot have the name of an existing branch"))
804 _("a bookmark cannot have the name of an existing branch"))
805 if rev:
805 if rev:
806 marks[mark] = repo.lookup(rev)
806 marks[mark] = repo.lookup(rev)
807 else:
807 else:
808 marks[mark] = cur
808 marks[mark] = cur
809 if not inactive and cur == marks[mark]:
809 if not inactive and cur == marks[mark]:
810 bookmarks.setcurrent(repo, mark)
810 bookmarks.setcurrent(repo, mark)
811 bookmarks.write(repo)
811 bookmarks.write(repo)
812 return
812 return
813
813
814 if mark is None:
814 if mark is None:
815 if rev:
815 if rev:
816 raise util.Abort(_("bookmark name required"))
816 raise util.Abort(_("bookmark name required"))
817 if len(marks) == 0:
817 if len(marks) == 0:
818 ui.status(_("no bookmarks set\n"))
818 ui.status(_("no bookmarks set\n"))
819 else:
819 else:
820 for bmark, n in sorted(marks.iteritems()):
820 for bmark, n in sorted(marks.iteritems()):
821 current = repo._bookmarkcurrent
821 current = repo._bookmarkcurrent
822 if bmark == current and n == cur:
822 if bmark == current and n == cur:
823 prefix, label = '*', 'bookmarks.current'
823 prefix, label = '*', 'bookmarks.current'
824 else:
824 else:
825 prefix, label = ' ', ''
825 prefix, label = ' ', ''
826
826
827 if ui.quiet:
827 if ui.quiet:
828 ui.write("%s\n" % bmark, label=label)
828 ui.write("%s\n" % bmark, label=label)
829 else:
829 else:
830 ui.write(" %s %-25s %d:%s\n" % (
830 ui.write(" %s %-25s %d:%s\n" % (
831 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
831 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
832 label=label)
832 label=label)
833 return
833 return
834
834
835 @command('branch',
835 @command('branch',
836 [('f', 'force', None,
836 [('f', 'force', None,
837 _('set branch name even if it shadows an existing branch')),
837 _('set branch name even if it shadows an existing branch')),
838 ('C', 'clean', None, _('reset branch name to parent branch name'))],
838 ('C', 'clean', None, _('reset branch name to parent branch name'))],
839 _('[-fC] [NAME]'))
839 _('[-fC] [NAME]'))
840 def branch(ui, repo, label=None, **opts):
840 def branch(ui, repo, label=None, **opts):
841 """set or show the current branch name
841 """set or show the current branch name
842
842
843 .. note::
843 .. note::
844 Branch names are permanent and global. Use :hg:`bookmark` to create a
844 Branch names are permanent and global. Use :hg:`bookmark` to create a
845 light-weight bookmark instead. See :hg:`help glossary` for more
845 light-weight bookmark instead. See :hg:`help glossary` for more
846 information about named branches and bookmarks.
846 information about named branches and bookmarks.
847
847
848 With no argument, show the current branch name. With one argument,
848 With no argument, show the current branch name. With one argument,
849 set the working directory branch name (the branch will not exist
849 set the working directory branch name (the branch will not exist
850 in the repository until the next commit). Standard practice
850 in the repository until the next commit). Standard practice
851 recommends that primary development take place on the 'default'
851 recommends that primary development take place on the 'default'
852 branch.
852 branch.
853
853
854 Unless -f/--force is specified, branch will not let you set a
854 Unless -f/--force is specified, branch will not let you set a
855 branch name that already exists, even if it's inactive.
855 branch name that already exists, even if it's inactive.
856
856
857 Use -C/--clean to reset the working directory branch to that of
857 Use -C/--clean to reset the working directory branch to that of
858 the parent of the working directory, negating a previous branch
858 the parent of the working directory, negating a previous branch
859 change.
859 change.
860
860
861 Use the command :hg:`update` to switch to an existing branch. Use
861 Use the command :hg:`update` to switch to an existing branch. Use
862 :hg:`commit --close-branch` to mark this branch as closed.
862 :hg:`commit --close-branch` to mark this branch as closed.
863
863
864 Returns 0 on success.
864 Returns 0 on success.
865 """
865 """
866 if not opts.get('clean') and not label:
866 if not opts.get('clean') and not label:
867 ui.write("%s\n" % repo.dirstate.branch())
867 ui.write("%s\n" % repo.dirstate.branch())
868 return
868 return
869
869
870 wlock = repo.wlock()
870 wlock = repo.wlock()
871 try:
871 try:
872 if opts.get('clean'):
872 if opts.get('clean'):
873 label = repo[None].p1().branch()
873 label = repo[None].p1().branch()
874 repo.dirstate.setbranch(label)
874 repo.dirstate.setbranch(label)
875 ui.status(_('reset working directory to branch %s\n') % label)
875 ui.status(_('reset working directory to branch %s\n') % label)
876 elif label:
876 elif label:
877 if not opts.get('force') and label in repo.branchtags():
877 if not opts.get('force') and label in repo.branchtags():
878 if label not in [p.branch() for p in repo.parents()]:
878 if label not in [p.branch() for p in repo.parents()]:
879 raise util.Abort(_('a branch of the same name already'
879 raise util.Abort(_('a branch of the same name already'
880 ' exists'),
880 ' exists'),
881 # i18n: "it" refers to an existing branch
881 # i18n: "it" refers to an existing branch
882 hint=_("use 'hg update' to switch to it"))
882 hint=_("use 'hg update' to switch to it"))
883 repo.dirstate.setbranch(label)
883 repo.dirstate.setbranch(label)
884 ui.status(_('marked working directory as branch %s\n') % label)
884 ui.status(_('marked working directory as branch %s\n') % label)
885 ui.status(_('(branches are permanent and global, '
885 ui.status(_('(branches are permanent and global, '
886 'did you want a bookmark?)\n'))
886 'did you want a bookmark?)\n'))
887 finally:
887 finally:
888 wlock.release()
888 wlock.release()
889
889
890 @command('branches',
890 @command('branches',
891 [('a', 'active', False, _('show only branches that have unmerged heads')),
891 [('a', 'active', False, _('show only branches that have unmerged heads')),
892 ('c', 'closed', False, _('show normal and closed branches'))],
892 ('c', 'closed', False, _('show normal and closed branches'))],
893 _('[-ac]'))
893 _('[-ac]'))
894 def branches(ui, repo, active=False, closed=False):
894 def branches(ui, repo, active=False, closed=False):
895 """list repository named branches
895 """list repository named branches
896
896
897 List the repository's named branches, indicating which ones are
897 List the repository's named branches, indicating which ones are
898 inactive. If -c/--closed is specified, also list branches which have
898 inactive. If -c/--closed is specified, also list branches which have
899 been marked closed (see :hg:`commit --close-branch`).
899 been marked closed (see :hg:`commit --close-branch`).
900
900
901 If -a/--active is specified, only show active branches. A branch
901 If -a/--active is specified, only show active branches. A branch
902 is considered active if it contains repository heads.
902 is considered active if it contains repository heads.
903
903
904 Use the command :hg:`update` to switch to an existing branch.
904 Use the command :hg:`update` to switch to an existing branch.
905
905
906 Returns 0.
906 Returns 0.
907 """
907 """
908
908
909 hexfunc = ui.debugflag and hex or short
909 hexfunc = ui.debugflag and hex or short
910 activebranches = [repo[n].branch() for n in repo.heads()]
910 activebranches = [repo[n].branch() for n in repo.heads()]
911 def testactive(tag, node):
911 def testactive(tag, node):
912 realhead = tag in activebranches
912 realhead = tag in activebranches
913 open = node in repo.branchheads(tag, closed=False)
913 open = node in repo.branchheads(tag, closed=False)
914 return realhead and open
914 return realhead and open
915 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
915 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
916 for tag, node in repo.branchtags().items()],
916 for tag, node in repo.branchtags().items()],
917 reverse=True)
917 reverse=True)
918
918
919 for isactive, node, tag in branches:
919 for isactive, node, tag in branches:
920 if (not active) or isactive:
920 if (not active) or isactive:
921 if ui.quiet:
921 if ui.quiet:
922 ui.write("%s\n" % tag)
922 ui.write("%s\n" % tag)
923 else:
923 else:
924 hn = repo.lookup(node)
924 hn = repo.lookup(node)
925 if isactive:
925 if isactive:
926 label = 'branches.active'
926 label = 'branches.active'
927 notice = ''
927 notice = ''
928 elif hn not in repo.branchheads(tag, closed=False):
928 elif hn not in repo.branchheads(tag, closed=False):
929 if not closed:
929 if not closed:
930 continue
930 continue
931 label = 'branches.closed'
931 label = 'branches.closed'
932 notice = _(' (closed)')
932 notice = _(' (closed)')
933 else:
933 else:
934 label = 'branches.inactive'
934 label = 'branches.inactive'
935 notice = _(' (inactive)')
935 notice = _(' (inactive)')
936 if tag == repo.dirstate.branch():
936 if tag == repo.dirstate.branch():
937 label = 'branches.current'
937 label = 'branches.current'
938 rev = str(node).rjust(31 - encoding.colwidth(tag))
938 rev = str(node).rjust(31 - encoding.colwidth(tag))
939 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
939 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
940 tag = ui.label(tag, label)
940 tag = ui.label(tag, label)
941 ui.write("%s %s%s\n" % (tag, rev, notice))
941 ui.write("%s %s%s\n" % (tag, rev, notice))
942
942
943 @command('bundle',
943 @command('bundle',
944 [('f', 'force', None, _('run even when the destination is unrelated')),
944 [('f', 'force', None, _('run even when the destination is unrelated')),
945 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
945 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
946 _('REV')),
946 _('REV')),
947 ('b', 'branch', [], _('a specific branch you would like to bundle'),
947 ('b', 'branch', [], _('a specific branch you would like to bundle'),
948 _('BRANCH')),
948 _('BRANCH')),
949 ('', 'base', [],
949 ('', 'base', [],
950 _('a base changeset assumed to be available at the destination'),
950 _('a base changeset assumed to be available at the destination'),
951 _('REV')),
951 _('REV')),
952 ('a', 'all', None, _('bundle all changesets in the repository')),
952 ('a', 'all', None, _('bundle all changesets in the repository')),
953 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
953 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
954 ] + remoteopts,
954 ] + remoteopts,
955 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
955 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
956 def bundle(ui, repo, fname, dest=None, **opts):
956 def bundle(ui, repo, fname, dest=None, **opts):
957 """create a changegroup file
957 """create a changegroup file
958
958
959 Generate a compressed changegroup file collecting changesets not
959 Generate a compressed changegroup file collecting changesets not
960 known to be in another repository.
960 known to be in another repository.
961
961
962 If you omit the destination repository, then hg assumes the
962 If you omit the destination repository, then hg assumes the
963 destination will have all the nodes you specify with --base
963 destination will have all the nodes you specify with --base
964 parameters. To create a bundle containing all changesets, use
964 parameters. To create a bundle containing all changesets, use
965 -a/--all (or --base null).
965 -a/--all (or --base null).
966
966
967 You can change compression method with the -t/--type option.
967 You can change compression method with the -t/--type option.
968 The available compression methods are: none, bzip2, and
968 The available compression methods are: none, bzip2, and
969 gzip (by default, bundles are compressed using bzip2).
969 gzip (by default, bundles are compressed using bzip2).
970
970
971 The bundle file can then be transferred using conventional means
971 The bundle file can then be transferred using conventional means
972 and applied to another repository with the unbundle or pull
972 and applied to another repository with the unbundle or pull
973 command. This is useful when direct push and pull are not
973 command. This is useful when direct push and pull are not
974 available or when exporting an entire repository is undesirable.
974 available or when exporting an entire repository is undesirable.
975
975
976 Applying bundles preserves all changeset contents including
976 Applying bundles preserves all changeset contents including
977 permissions, copy/rename information, and revision history.
977 permissions, copy/rename information, and revision history.
978
978
979 Returns 0 on success, 1 if no changes found.
979 Returns 0 on success, 1 if no changes found.
980 """
980 """
981 revs = None
981 revs = None
982 if 'rev' in opts:
982 if 'rev' in opts:
983 revs = scmutil.revrange(repo, opts['rev'])
983 revs = scmutil.revrange(repo, opts['rev'])
984
984
985 bundletype = opts.get('type', 'bzip2').lower()
985 bundletype = opts.get('type', 'bzip2').lower()
986 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
986 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
987 bundletype = btypes.get(bundletype)
987 bundletype = btypes.get(bundletype)
988 if bundletype not in changegroup.bundletypes:
988 if bundletype not in changegroup.bundletypes:
989 raise util.Abort(_('unknown bundle type specified with --type'))
989 raise util.Abort(_('unknown bundle type specified with --type'))
990
990
991 if opts.get('all'):
991 if opts.get('all'):
992 base = ['null']
992 base = ['null']
993 else:
993 else:
994 base = scmutil.revrange(repo, opts.get('base'))
994 base = scmutil.revrange(repo, opts.get('base'))
995 if base:
995 if base:
996 if dest:
996 if dest:
997 raise util.Abort(_("--base is incompatible with specifying "
997 raise util.Abort(_("--base is incompatible with specifying "
998 "a destination"))
998 "a destination"))
999 common = [repo.lookup(rev) for rev in base]
999 common = [repo.lookup(rev) for rev in base]
1000 heads = revs and map(repo.lookup, revs) or revs
1000 heads = revs and map(repo.lookup, revs) or revs
1001 cg = repo.getbundle('bundle', heads=heads, common=common)
1001 cg = repo.getbundle('bundle', heads=heads, common=common)
1002 outgoing = None
1002 outgoing = None
1003 else:
1003 else:
1004 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1004 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1005 dest, branches = hg.parseurl(dest, opts.get('branch'))
1005 dest, branches = hg.parseurl(dest, opts.get('branch'))
1006 other = hg.peer(repo, opts, dest)
1006 other = hg.peer(repo, opts, dest)
1007 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1007 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1008 heads = revs and map(repo.lookup, revs) or revs
1008 heads = revs and map(repo.lookup, revs) or revs
1009 outgoing = discovery.findcommonoutgoing(repo, other,
1009 outgoing = discovery.findcommonoutgoing(repo, other,
1010 onlyheads=heads,
1010 onlyheads=heads,
1011 force=opts.get('force'))
1011 force=opts.get('force'))
1012 cg = repo.getlocalbundle('bundle', outgoing)
1012 cg = repo.getlocalbundle('bundle', outgoing)
1013 if not cg:
1013 if not cg:
1014 scmutil.nochangesfound(ui, outgoing and outgoing.excluded)
1014 scmutil.nochangesfound(ui, outgoing and outgoing.excluded)
1015 return 1
1015 return 1
1016
1016
1017 changegroup.writebundle(cg, fname, bundletype)
1017 changegroup.writebundle(cg, fname, bundletype)
1018
1018
1019 @command('cat',
1019 @command('cat',
1020 [('o', 'output', '',
1020 [('o', 'output', '',
1021 _('print output to file with formatted name'), _('FORMAT')),
1021 _('print output to file with formatted name'), _('FORMAT')),
1022 ('r', 'rev', '', _('print the given revision'), _('REV')),
1022 ('r', 'rev', '', _('print the given revision'), _('REV')),
1023 ('', 'decode', None, _('apply any matching decode filter')),
1023 ('', 'decode', None, _('apply any matching decode filter')),
1024 ] + walkopts,
1024 ] + walkopts,
1025 _('[OPTION]... FILE...'))
1025 _('[OPTION]... FILE...'))
1026 def cat(ui, repo, file1, *pats, **opts):
1026 def cat(ui, repo, file1, *pats, **opts):
1027 """output the current or given revision of files
1027 """output the current or given revision of files
1028
1028
1029 Print the specified files as they were at the given revision. If
1029 Print the specified files as they were at the given revision. If
1030 no revision is given, the parent of the working directory is used,
1030 no revision is given, the parent of the working directory is used,
1031 or tip if no revision is checked out.
1031 or tip if no revision is checked out.
1032
1032
1033 Output may be to a file, in which case the name of the file is
1033 Output may be to a file, in which case the name of the file is
1034 given using a format string. The formatting rules are the same as
1034 given using a format string. The formatting rules are the same as
1035 for the export command, with the following additions:
1035 for the export command, with the following additions:
1036
1036
1037 :``%s``: basename of file being printed
1037 :``%s``: basename of file being printed
1038 :``%d``: dirname of file being printed, or '.' if in repository root
1038 :``%d``: dirname of file being printed, or '.' if in repository root
1039 :``%p``: root-relative path name of file being printed
1039 :``%p``: root-relative path name of file being printed
1040
1040
1041 Returns 0 on success.
1041 Returns 0 on success.
1042 """
1042 """
1043 ctx = scmutil.revsingle(repo, opts.get('rev'))
1043 ctx = scmutil.revsingle(repo, opts.get('rev'))
1044 err = 1
1044 err = 1
1045 m = scmutil.match(ctx, (file1,) + pats, opts)
1045 m = scmutil.match(ctx, (file1,) + pats, opts)
1046 for abs in ctx.walk(m):
1046 for abs in ctx.walk(m):
1047 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1047 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1048 pathname=abs)
1048 pathname=abs)
1049 data = ctx[abs].data()
1049 data = ctx[abs].data()
1050 if opts.get('decode'):
1050 if opts.get('decode'):
1051 data = repo.wwritedata(abs, data)
1051 data = repo.wwritedata(abs, data)
1052 fp.write(data)
1052 fp.write(data)
1053 fp.close()
1053 fp.close()
1054 err = 0
1054 err = 0
1055 return err
1055 return err
1056
1056
1057 @command('^clone',
1057 @command('^clone',
1058 [('U', 'noupdate', None,
1058 [('U', 'noupdate', None,
1059 _('the clone will include an empty working copy (only a repository)')),
1059 _('the clone will include an empty working copy (only a repository)')),
1060 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1060 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1061 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1061 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1062 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1062 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1063 ('', 'pull', None, _('use pull protocol to copy metadata')),
1063 ('', 'pull', None, _('use pull protocol to copy metadata')),
1064 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1064 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1065 ] + remoteopts,
1065 ] + remoteopts,
1066 _('[OPTION]... SOURCE [DEST]'))
1066 _('[OPTION]... SOURCE [DEST]'))
1067 def clone(ui, source, dest=None, **opts):
1067 def clone(ui, source, dest=None, **opts):
1068 """make a copy of an existing repository
1068 """make a copy of an existing repository
1069
1069
1070 Create a copy of an existing repository in a new directory.
1070 Create a copy of an existing repository in a new directory.
1071
1071
1072 If no destination directory name is specified, it defaults to the
1072 If no destination directory name is specified, it defaults to the
1073 basename of the source.
1073 basename of the source.
1074
1074
1075 The location of the source is added to the new repository's
1075 The location of the source is added to the new repository's
1076 ``.hg/hgrc`` file, as the default to be used for future pulls.
1076 ``.hg/hgrc`` file, as the default to be used for future pulls.
1077
1077
1078 Only local paths and ``ssh://`` URLs are supported as
1078 Only local paths and ``ssh://`` URLs are supported as
1079 destinations. For ``ssh://`` destinations, no working directory or
1079 destinations. For ``ssh://`` destinations, no working directory or
1080 ``.hg/hgrc`` will be created on the remote side.
1080 ``.hg/hgrc`` will be created on the remote side.
1081
1081
1082 To pull only a subset of changesets, specify one or more revisions
1082 To pull only a subset of changesets, specify one or more revisions
1083 identifiers with -r/--rev or branches with -b/--branch. The
1083 identifiers with -r/--rev or branches with -b/--branch. The
1084 resulting clone will contain only the specified changesets and
1084 resulting clone will contain only the specified changesets and
1085 their ancestors. These options (or 'clone src#rev dest') imply
1085 their ancestors. These options (or 'clone src#rev dest') imply
1086 --pull, even for local source repositories. Note that specifying a
1086 --pull, even for local source repositories. Note that specifying a
1087 tag will include the tagged changeset but not the changeset
1087 tag will include the tagged changeset but not the changeset
1088 containing the tag.
1088 containing the tag.
1089
1089
1090 To check out a particular version, use -u/--update, or
1090 To check out a particular version, use -u/--update, or
1091 -U/--noupdate to create a clone with no working directory.
1091 -U/--noupdate to create a clone with no working directory.
1092
1092
1093 .. container:: verbose
1093 .. container:: verbose
1094
1094
1095 For efficiency, hardlinks are used for cloning whenever the
1095 For efficiency, hardlinks are used for cloning whenever the
1096 source and destination are on the same filesystem (note this
1096 source and destination are on the same filesystem (note this
1097 applies only to the repository data, not to the working
1097 applies only to the repository data, not to the working
1098 directory). Some filesystems, such as AFS, implement hardlinking
1098 directory). Some filesystems, such as AFS, implement hardlinking
1099 incorrectly, but do not report errors. In these cases, use the
1099 incorrectly, but do not report errors. In these cases, use the
1100 --pull option to avoid hardlinking.
1100 --pull option to avoid hardlinking.
1101
1101
1102 In some cases, you can clone repositories and the working
1102 In some cases, you can clone repositories and the working
1103 directory using full hardlinks with ::
1103 directory using full hardlinks with ::
1104
1104
1105 $ cp -al REPO REPOCLONE
1105 $ cp -al REPO REPOCLONE
1106
1106
1107 This is the fastest way to clone, but it is not always safe. The
1107 This is the fastest way to clone, but it is not always safe. The
1108 operation is not atomic (making sure REPO is not modified during
1108 operation is not atomic (making sure REPO is not modified during
1109 the operation is up to you) and you have to make sure your
1109 the operation is up to you) and you have to make sure your
1110 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1110 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1111 so). Also, this is not compatible with certain extensions that
1111 so). Also, this is not compatible with certain extensions that
1112 place their metadata under the .hg directory, such as mq.
1112 place their metadata under the .hg directory, such as mq.
1113
1113
1114 Mercurial will update the working directory to the first applicable
1114 Mercurial will update the working directory to the first applicable
1115 revision from this list:
1115 revision from this list:
1116
1116
1117 a) null if -U or the source repository has no changesets
1117 a) null if -U or the source repository has no changesets
1118 b) if -u . and the source repository is local, the first parent of
1118 b) if -u . and the source repository is local, the first parent of
1119 the source repository's working directory
1119 the source repository's working directory
1120 c) the changeset specified with -u (if a branch name, this means the
1120 c) the changeset specified with -u (if a branch name, this means the
1121 latest head of that branch)
1121 latest head of that branch)
1122 d) the changeset specified with -r
1122 d) the changeset specified with -r
1123 e) the tipmost head specified with -b
1123 e) the tipmost head specified with -b
1124 f) the tipmost head specified with the url#branch source syntax
1124 f) the tipmost head specified with the url#branch source syntax
1125 g) the tipmost head of the default branch
1125 g) the tipmost head of the default branch
1126 h) tip
1126 h) tip
1127
1127
1128 Examples:
1128 Examples:
1129
1129
1130 - clone a remote repository to a new directory named hg/::
1130 - clone a remote repository to a new directory named hg/::
1131
1131
1132 hg clone http://selenic.com/hg
1132 hg clone http://selenic.com/hg
1133
1133
1134 - create a lightweight local clone::
1134 - create a lightweight local clone::
1135
1135
1136 hg clone project/ project-feature/
1136 hg clone project/ project-feature/
1137
1137
1138 - clone from an absolute path on an ssh server (note double-slash)::
1138 - clone from an absolute path on an ssh server (note double-slash)::
1139
1139
1140 hg clone ssh://user@server//home/projects/alpha/
1140 hg clone ssh://user@server//home/projects/alpha/
1141
1141
1142 - do a high-speed clone over a LAN while checking out a
1142 - do a high-speed clone over a LAN while checking out a
1143 specified version::
1143 specified version::
1144
1144
1145 hg clone --uncompressed http://server/repo -u 1.5
1145 hg clone --uncompressed http://server/repo -u 1.5
1146
1146
1147 - create a repository without changesets after a particular revision::
1147 - create a repository without changesets after a particular revision::
1148
1148
1149 hg clone -r 04e544 experimental/ good/
1149 hg clone -r 04e544 experimental/ good/
1150
1150
1151 - clone (and track) a particular named branch::
1151 - clone (and track) a particular named branch::
1152
1152
1153 hg clone http://selenic.com/hg#stable
1153 hg clone http://selenic.com/hg#stable
1154
1154
1155 See :hg:`help urls` for details on specifying URLs.
1155 See :hg:`help urls` for details on specifying URLs.
1156
1156
1157 Returns 0 on success.
1157 Returns 0 on success.
1158 """
1158 """
1159 if opts.get('noupdate') and opts.get('updaterev'):
1159 if opts.get('noupdate') and opts.get('updaterev'):
1160 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1160 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1161
1161
1162 r = hg.clone(ui, opts, source, dest,
1162 r = hg.clone(ui, opts, source, dest,
1163 pull=opts.get('pull'),
1163 pull=opts.get('pull'),
1164 stream=opts.get('uncompressed'),
1164 stream=opts.get('uncompressed'),
1165 rev=opts.get('rev'),
1165 rev=opts.get('rev'),
1166 update=opts.get('updaterev') or not opts.get('noupdate'),
1166 update=opts.get('updaterev') or not opts.get('noupdate'),
1167 branch=opts.get('branch'))
1167 branch=opts.get('branch'))
1168
1168
1169 return r is None
1169 return r is None
1170
1170
1171 @command('^commit|ci',
1171 @command('^commit|ci',
1172 [('A', 'addremove', None,
1172 [('A', 'addremove', None,
1173 _('mark new/missing files as added/removed before committing')),
1173 _('mark new/missing files as added/removed before committing')),
1174 ('', 'close-branch', None,
1174 ('', 'close-branch', None,
1175 _('mark a branch as closed, hiding it from the branch list')),
1175 _('mark a branch as closed, hiding it from the branch list')),
1176 ('', 'amend', None, _('amend the parent of the working dir')),
1176 ('', 'amend', None, _('amend the parent of the working dir')),
1177 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1177 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1178 _('[OPTION]... [FILE]...'))
1178 _('[OPTION]... [FILE]...'))
1179 def commit(ui, repo, *pats, **opts):
1179 def commit(ui, repo, *pats, **opts):
1180 """commit the specified files or all outstanding changes
1180 """commit the specified files or all outstanding changes
1181
1181
1182 Commit changes to the given files into the repository. Unlike a
1182 Commit changes to the given files into the repository. Unlike a
1183 centralized SCM, this operation is a local operation. See
1183 centralized SCM, this operation is a local operation. See
1184 :hg:`push` for a way to actively distribute your changes.
1184 :hg:`push` for a way to actively distribute your changes.
1185
1185
1186 If a list of files is omitted, all changes reported by :hg:`status`
1186 If a list of files is omitted, all changes reported by :hg:`status`
1187 will be committed.
1187 will be committed.
1188
1188
1189 If you are committing the result of a merge, do not provide any
1189 If you are committing the result of a merge, do not provide any
1190 filenames or -I/-X filters.
1190 filenames or -I/-X filters.
1191
1191
1192 If no commit message is specified, Mercurial starts your
1192 If no commit message is specified, Mercurial starts your
1193 configured editor where you can enter a message. In case your
1193 configured editor where you can enter a message. In case your
1194 commit fails, you will find a backup of your message in
1194 commit fails, you will find a backup of your message in
1195 ``.hg/last-message.txt``.
1195 ``.hg/last-message.txt``.
1196
1196
1197 The --amend flag can be used to amend the parent of the
1197 The --amend flag can be used to amend the parent of the
1198 working directory with a new commit that contains the changes
1198 working directory with a new commit that contains the changes
1199 in the parent in addition to those currently reported by :hg:`status`,
1199 in the parent in addition to those currently reported by :hg:`status`,
1200 if there are any. The old commit is stored in a backup bundle in
1200 if there are any. The old commit is stored in a backup bundle in
1201 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1201 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1202 on how to restore it).
1202 on how to restore it).
1203
1203
1204 Message, user and date are taken from the amended commit unless
1204 Message, user and date are taken from the amended commit unless
1205 specified. When a message isn't specified on the command line,
1205 specified. When a message isn't specified on the command line,
1206 the editor will open with the message of the amended commit.
1206 the editor will open with the message of the amended commit.
1207
1207
1208 It is not possible to amend public changesets (see :hg:`help phases`)
1208 It is not possible to amend public changesets (see :hg:`help phases`)
1209 or changesets that have children.
1209 or changesets that have children.
1210
1210
1211 See :hg:`help dates` for a list of formats valid for -d/--date.
1211 See :hg:`help dates` for a list of formats valid for -d/--date.
1212
1212
1213 Returns 0 on success, 1 if nothing changed.
1213 Returns 0 on success, 1 if nothing changed.
1214 """
1214 """
1215 if opts.get('subrepos'):
1215 if opts.get('subrepos'):
1216 # Let --subrepos on the command line overide config setting.
1216 # Let --subrepos on the command line overide config setting.
1217 ui.setconfig('ui', 'commitsubrepos', True)
1217 ui.setconfig('ui', 'commitsubrepos', True)
1218
1218
1219 extra = {}
1219 extra = {}
1220 if opts.get('close_branch'):
1220 if opts.get('close_branch'):
1221 if repo['.'].node() not in repo.branchheads():
1221 if repo['.'].node() not in repo.branchheads():
1222 # The topo heads set is included in the branch heads set of the
1222 # The topo heads set is included in the branch heads set of the
1223 # current branch, so it's sufficient to test branchheads
1223 # current branch, so it's sufficient to test branchheads
1224 raise util.Abort(_('can only close branch heads'))
1224 raise util.Abort(_('can only close branch heads'))
1225 extra['close'] = 1
1225 extra['close'] = 1
1226
1226
1227 branch = repo[None].branch()
1227 branch = repo[None].branch()
1228 bheads = repo.branchheads(branch)
1228 bheads = repo.branchheads(branch)
1229
1229
1230 if opts.get('amend'):
1230 if opts.get('amend'):
1231 if ui.configbool('ui', 'commitsubrepos'):
1231 if ui.configbool('ui', 'commitsubrepos'):
1232 raise util.Abort(_('cannot amend recursively'))
1232 raise util.Abort(_('cannot amend recursively'))
1233
1233
1234 old = repo['.']
1234 old = repo['.']
1235 if old.phase() == phases.public:
1235 if old.phase() == phases.public:
1236 raise util.Abort(_('cannot amend public changesets'))
1236 raise util.Abort(_('cannot amend public changesets'))
1237 if len(old.parents()) > 1:
1237 if len(old.parents()) > 1:
1238 raise util.Abort(_('cannot amend merge changesets'))
1238 raise util.Abort(_('cannot amend merge changesets'))
1239 if len(repo[None].parents()) > 1:
1239 if len(repo[None].parents()) > 1:
1240 raise util.Abort(_('cannot amend while merging'))
1240 raise util.Abort(_('cannot amend while merging'))
1241 if old.children():
1241 if old.children():
1242 raise util.Abort(_('cannot amend changeset with children'))
1242 raise util.Abort(_('cannot amend changeset with children'))
1243
1243
1244 e = cmdutil.commiteditor
1244 e = cmdutil.commiteditor
1245 if opts.get('force_editor'):
1245 if opts.get('force_editor'):
1246 e = cmdutil.commitforceeditor
1246 e = cmdutil.commitforceeditor
1247
1247
1248 def commitfunc(ui, repo, message, match, opts):
1248 def commitfunc(ui, repo, message, match, opts):
1249 editor = e
1249 editor = e
1250 # message contains text from -m or -l, if it's empty,
1250 # message contains text from -m or -l, if it's empty,
1251 # open the editor with the old message
1251 # open the editor with the old message
1252 if not message:
1252 if not message:
1253 message = old.description()
1253 message = old.description()
1254 editor = cmdutil.commitforceeditor
1254 editor = cmdutil.commitforceeditor
1255 return repo.commit(message,
1255 return repo.commit(message,
1256 opts.get('user') or old.user(),
1256 opts.get('user') or old.user(),
1257 opts.get('date') or old.date(),
1257 opts.get('date') or old.date(),
1258 match,
1258 match,
1259 editor=editor,
1259 editor=editor,
1260 extra=extra)
1260 extra=extra)
1261
1261
1262 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1262 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1263 if node == old.node():
1263 if node == old.node():
1264 ui.status(_("nothing changed\n"))
1264 ui.status(_("nothing changed\n"))
1265 return 1
1265 return 1
1266 else:
1266 else:
1267 e = cmdutil.commiteditor
1267 e = cmdutil.commiteditor
1268 if opts.get('force_editor'):
1268 if opts.get('force_editor'):
1269 e = cmdutil.commitforceeditor
1269 e = cmdutil.commitforceeditor
1270
1270
1271 def commitfunc(ui, repo, message, match, opts):
1271 def commitfunc(ui, repo, message, match, opts):
1272 return repo.commit(message, opts.get('user'), opts.get('date'),
1272 return repo.commit(message, opts.get('user'), opts.get('date'),
1273 match, editor=e, extra=extra)
1273 match, editor=e, extra=extra)
1274
1274
1275 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1275 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1276
1276
1277 if not node:
1277 if not node:
1278 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1278 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1279 if stat[3]:
1279 if stat[3]:
1280 ui.status(_("nothing changed (%d missing files, see "
1280 ui.status(_("nothing changed (%d missing files, see "
1281 "'hg status')\n") % len(stat[3]))
1281 "'hg status')\n") % len(stat[3]))
1282 else:
1282 else:
1283 ui.status(_("nothing changed\n"))
1283 ui.status(_("nothing changed\n"))
1284 return 1
1284 return 1
1285
1285
1286 ctx = repo[node]
1286 ctx = repo[node]
1287 parents = ctx.parents()
1287 parents = ctx.parents()
1288
1288
1289 if (not opts.get('amend') and bheads and node not in bheads and not
1289 if (not opts.get('amend') and bheads and node not in bheads and not
1290 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1290 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1291 ui.status(_('created new head\n'))
1291 ui.status(_('created new head\n'))
1292 # The message is not printed for initial roots. For the other
1292 # The message is not printed for initial roots. For the other
1293 # changesets, it is printed in the following situations:
1293 # changesets, it is printed in the following situations:
1294 #
1294 #
1295 # Par column: for the 2 parents with ...
1295 # Par column: for the 2 parents with ...
1296 # N: null or no parent
1296 # N: null or no parent
1297 # B: parent is on another named branch
1297 # B: parent is on another named branch
1298 # C: parent is a regular non head changeset
1298 # C: parent is a regular non head changeset
1299 # H: parent was a branch head of the current branch
1299 # H: parent was a branch head of the current branch
1300 # Msg column: whether we print "created new head" message
1300 # Msg column: whether we print "created new head" message
1301 # In the following, it is assumed that there already exists some
1301 # In the following, it is assumed that there already exists some
1302 # initial branch heads of the current branch, otherwise nothing is
1302 # initial branch heads of the current branch, otherwise nothing is
1303 # printed anyway.
1303 # printed anyway.
1304 #
1304 #
1305 # Par Msg Comment
1305 # Par Msg Comment
1306 # NN y additional topo root
1306 # NN y additional topo root
1307 #
1307 #
1308 # BN y additional branch root
1308 # BN y additional branch root
1309 # CN y additional topo head
1309 # CN y additional topo head
1310 # HN n usual case
1310 # HN n usual case
1311 #
1311 #
1312 # BB y weird additional branch root
1312 # BB y weird additional branch root
1313 # CB y branch merge
1313 # CB y branch merge
1314 # HB n merge with named branch
1314 # HB n merge with named branch
1315 #
1315 #
1316 # CC y additional head from merge
1316 # CC y additional head from merge
1317 # CH n merge with a head
1317 # CH n merge with a head
1318 #
1318 #
1319 # HH n head merge: head count decreases
1319 # HH n head merge: head count decreases
1320
1320
1321 if not opts.get('close_branch'):
1321 if not opts.get('close_branch'):
1322 for r in parents:
1322 for r in parents:
1323 if r.extra().get('close') and r.branch() == branch:
1323 if r.extra().get('close') and r.branch() == branch:
1324 ui.status(_('reopening closed branch head %d\n') % r)
1324 ui.status(_('reopening closed branch head %d\n') % r)
1325
1325
1326 if ui.debugflag:
1326 if ui.debugflag:
1327 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1327 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1328 elif ui.verbose:
1328 elif ui.verbose:
1329 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1329 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1330
1330
1331 @command('copy|cp',
1331 @command('copy|cp',
1332 [('A', 'after', None, _('record a copy that has already occurred')),
1332 [('A', 'after', None, _('record a copy that has already occurred')),
1333 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1333 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1334 ] + walkopts + dryrunopts,
1334 ] + walkopts + dryrunopts,
1335 _('[OPTION]... [SOURCE]... DEST'))
1335 _('[OPTION]... [SOURCE]... DEST'))
1336 def copy(ui, repo, *pats, **opts):
1336 def copy(ui, repo, *pats, **opts):
1337 """mark files as copied for the next commit
1337 """mark files as copied for the next commit
1338
1338
1339 Mark dest as having copies of source files. If dest is a
1339 Mark dest as having copies of source files. If dest is a
1340 directory, copies are put in that directory. If dest is a file,
1340 directory, copies are put in that directory. If dest is a file,
1341 the source must be a single file.
1341 the source must be a single file.
1342
1342
1343 By default, this command copies the contents of files as they
1343 By default, this command copies the contents of files as they
1344 exist in the working directory. If invoked with -A/--after, the
1344 exist in the working directory. If invoked with -A/--after, the
1345 operation is recorded, but no copying is performed.
1345 operation is recorded, but no copying is performed.
1346
1346
1347 This command takes effect with the next commit. To undo a copy
1347 This command takes effect with the next commit. To undo a copy
1348 before that, see :hg:`revert`.
1348 before that, see :hg:`revert`.
1349
1349
1350 Returns 0 on success, 1 if errors are encountered.
1350 Returns 0 on success, 1 if errors are encountered.
1351 """
1351 """
1352 wlock = repo.wlock(False)
1352 wlock = repo.wlock(False)
1353 try:
1353 try:
1354 return cmdutil.copy(ui, repo, pats, opts)
1354 return cmdutil.copy(ui, repo, pats, opts)
1355 finally:
1355 finally:
1356 wlock.release()
1356 wlock.release()
1357
1357
1358 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1358 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1359 def debugancestor(ui, repo, *args):
1359 def debugancestor(ui, repo, *args):
1360 """find the ancestor revision of two revisions in a given index"""
1360 """find the ancestor revision of two revisions in a given index"""
1361 if len(args) == 3:
1361 if len(args) == 3:
1362 index, rev1, rev2 = args
1362 index, rev1, rev2 = args
1363 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1363 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1364 lookup = r.lookup
1364 lookup = r.lookup
1365 elif len(args) == 2:
1365 elif len(args) == 2:
1366 if not repo:
1366 if not repo:
1367 raise util.Abort(_("there is no Mercurial repository here "
1367 raise util.Abort(_("there is no Mercurial repository here "
1368 "(.hg not found)"))
1368 "(.hg not found)"))
1369 rev1, rev2 = args
1369 rev1, rev2 = args
1370 r = repo.changelog
1370 r = repo.changelog
1371 lookup = repo.lookup
1371 lookup = repo.lookup
1372 else:
1372 else:
1373 raise util.Abort(_('either two or three arguments required'))
1373 raise util.Abort(_('either two or three arguments required'))
1374 a = r.ancestor(lookup(rev1), lookup(rev2))
1374 a = r.ancestor(lookup(rev1), lookup(rev2))
1375 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1375 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1376
1376
1377 @command('debugbuilddag',
1377 @command('debugbuilddag',
1378 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1378 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1379 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1379 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1380 ('n', 'new-file', None, _('add new file at each rev'))],
1380 ('n', 'new-file', None, _('add new file at each rev'))],
1381 _('[OPTION]... [TEXT]'))
1381 _('[OPTION]... [TEXT]'))
1382 def debugbuilddag(ui, repo, text=None,
1382 def debugbuilddag(ui, repo, text=None,
1383 mergeable_file=False,
1383 mergeable_file=False,
1384 overwritten_file=False,
1384 overwritten_file=False,
1385 new_file=False):
1385 new_file=False):
1386 """builds a repo with a given DAG from scratch in the current empty repo
1386 """builds a repo with a given DAG from scratch in the current empty repo
1387
1387
1388 The description of the DAG is read from stdin if not given on the
1388 The description of the DAG is read from stdin if not given on the
1389 command line.
1389 command line.
1390
1390
1391 Elements:
1391 Elements:
1392
1392
1393 - "+n" is a linear run of n nodes based on the current default parent
1393 - "+n" is a linear run of n nodes based on the current default parent
1394 - "." is a single node based on the current default parent
1394 - "." is a single node based on the current default parent
1395 - "$" resets the default parent to null (implied at the start);
1395 - "$" resets the default parent to null (implied at the start);
1396 otherwise the default parent is always the last node created
1396 otherwise the default parent is always the last node created
1397 - "<p" sets the default parent to the backref p
1397 - "<p" sets the default parent to the backref p
1398 - "*p" is a fork at parent p, which is a backref
1398 - "*p" is a fork at parent p, which is a backref
1399 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1399 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1400 - "/p2" is a merge of the preceding node and p2
1400 - "/p2" is a merge of the preceding node and p2
1401 - ":tag" defines a local tag for the preceding node
1401 - ":tag" defines a local tag for the preceding node
1402 - "@branch" sets the named branch for subsequent nodes
1402 - "@branch" sets the named branch for subsequent nodes
1403 - "#...\\n" is a comment up to the end of the line
1403 - "#...\\n" is a comment up to the end of the line
1404
1404
1405 Whitespace between the above elements is ignored.
1405 Whitespace between the above elements is ignored.
1406
1406
1407 A backref is either
1407 A backref is either
1408
1408
1409 - a number n, which references the node curr-n, where curr is the current
1409 - a number n, which references the node curr-n, where curr is the current
1410 node, or
1410 node, or
1411 - the name of a local tag you placed earlier using ":tag", or
1411 - the name of a local tag you placed earlier using ":tag", or
1412 - empty to denote the default parent.
1412 - empty to denote the default parent.
1413
1413
1414 All string valued-elements are either strictly alphanumeric, or must
1414 All string valued-elements are either strictly alphanumeric, or must
1415 be enclosed in double quotes ("..."), with "\\" as escape character.
1415 be enclosed in double quotes ("..."), with "\\" as escape character.
1416 """
1416 """
1417
1417
1418 if text is None:
1418 if text is None:
1419 ui.status(_("reading DAG from stdin\n"))
1419 ui.status(_("reading DAG from stdin\n"))
1420 text = ui.fin.read()
1420 text = ui.fin.read()
1421
1421
1422 cl = repo.changelog
1422 cl = repo.changelog
1423 if len(cl) > 0:
1423 if len(cl) > 0:
1424 raise util.Abort(_('repository is not empty'))
1424 raise util.Abort(_('repository is not empty'))
1425
1425
1426 # determine number of revs in DAG
1426 # determine number of revs in DAG
1427 total = 0
1427 total = 0
1428 for type, data in dagparser.parsedag(text):
1428 for type, data in dagparser.parsedag(text):
1429 if type == 'n':
1429 if type == 'n':
1430 total += 1
1430 total += 1
1431
1431
1432 if mergeable_file:
1432 if mergeable_file:
1433 linesperrev = 2
1433 linesperrev = 2
1434 # make a file with k lines per rev
1434 # make a file with k lines per rev
1435 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1435 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1436 initialmergedlines.append("")
1436 initialmergedlines.append("")
1437
1437
1438 tags = []
1438 tags = []
1439
1439
1440 lock = tr = None
1440 lock = tr = None
1441 try:
1441 try:
1442 lock = repo.lock()
1442 lock = repo.lock()
1443 tr = repo.transaction("builddag")
1443 tr = repo.transaction("builddag")
1444
1444
1445 at = -1
1445 at = -1
1446 atbranch = 'default'
1446 atbranch = 'default'
1447 nodeids = []
1447 nodeids = []
1448 id = 0
1448 id = 0
1449 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1449 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1450 for type, data in dagparser.parsedag(text):
1450 for type, data in dagparser.parsedag(text):
1451 if type == 'n':
1451 if type == 'n':
1452 ui.note('node %s\n' % str(data))
1452 ui.note('node %s\n' % str(data))
1453 id, ps = data
1453 id, ps = data
1454
1454
1455 files = []
1455 files = []
1456 fctxs = {}
1456 fctxs = {}
1457
1457
1458 p2 = None
1458 p2 = None
1459 if mergeable_file:
1459 if mergeable_file:
1460 fn = "mf"
1460 fn = "mf"
1461 p1 = repo[ps[0]]
1461 p1 = repo[ps[0]]
1462 if len(ps) > 1:
1462 if len(ps) > 1:
1463 p2 = repo[ps[1]]
1463 p2 = repo[ps[1]]
1464 pa = p1.ancestor(p2)
1464 pa = p1.ancestor(p2)
1465 base, local, other = [x[fn].data() for x in pa, p1, p2]
1465 base, local, other = [x[fn].data() for x in pa, p1, p2]
1466 m3 = simplemerge.Merge3Text(base, local, other)
1466 m3 = simplemerge.Merge3Text(base, local, other)
1467 ml = [l.strip() for l in m3.merge_lines()]
1467 ml = [l.strip() for l in m3.merge_lines()]
1468 ml.append("")
1468 ml.append("")
1469 elif at > 0:
1469 elif at > 0:
1470 ml = p1[fn].data().split("\n")
1470 ml = p1[fn].data().split("\n")
1471 else:
1471 else:
1472 ml = initialmergedlines
1472 ml = initialmergedlines
1473 ml[id * linesperrev] += " r%i" % id
1473 ml[id * linesperrev] += " r%i" % id
1474 mergedtext = "\n".join(ml)
1474 mergedtext = "\n".join(ml)
1475 files.append(fn)
1475 files.append(fn)
1476 fctxs[fn] = context.memfilectx(fn, mergedtext)
1476 fctxs[fn] = context.memfilectx(fn, mergedtext)
1477
1477
1478 if overwritten_file:
1478 if overwritten_file:
1479 fn = "of"
1479 fn = "of"
1480 files.append(fn)
1480 files.append(fn)
1481 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1481 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1482
1482
1483 if new_file:
1483 if new_file:
1484 fn = "nf%i" % id
1484 fn = "nf%i" % id
1485 files.append(fn)
1485 files.append(fn)
1486 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1486 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1487 if len(ps) > 1:
1487 if len(ps) > 1:
1488 if not p2:
1488 if not p2:
1489 p2 = repo[ps[1]]
1489 p2 = repo[ps[1]]
1490 for fn in p2:
1490 for fn in p2:
1491 if fn.startswith("nf"):
1491 if fn.startswith("nf"):
1492 files.append(fn)
1492 files.append(fn)
1493 fctxs[fn] = p2[fn]
1493 fctxs[fn] = p2[fn]
1494
1494
1495 def fctxfn(repo, cx, path):
1495 def fctxfn(repo, cx, path):
1496 return fctxs.get(path)
1496 return fctxs.get(path)
1497
1497
1498 if len(ps) == 0 or ps[0] < 0:
1498 if len(ps) == 0 or ps[0] < 0:
1499 pars = [None, None]
1499 pars = [None, None]
1500 elif len(ps) == 1:
1500 elif len(ps) == 1:
1501 pars = [nodeids[ps[0]], None]
1501 pars = [nodeids[ps[0]], None]
1502 else:
1502 else:
1503 pars = [nodeids[p] for p in ps]
1503 pars = [nodeids[p] for p in ps]
1504 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1504 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1505 date=(id, 0),
1505 date=(id, 0),
1506 user="debugbuilddag",
1506 user="debugbuilddag",
1507 extra={'branch': atbranch})
1507 extra={'branch': atbranch})
1508 nodeid = repo.commitctx(cx)
1508 nodeid = repo.commitctx(cx)
1509 nodeids.append(nodeid)
1509 nodeids.append(nodeid)
1510 at = id
1510 at = id
1511 elif type == 'l':
1511 elif type == 'l':
1512 id, name = data
1512 id, name = data
1513 ui.note('tag %s\n' % name)
1513 ui.note('tag %s\n' % name)
1514 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1514 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1515 elif type == 'a':
1515 elif type == 'a':
1516 ui.note('branch %s\n' % data)
1516 ui.note('branch %s\n' % data)
1517 atbranch = data
1517 atbranch = data
1518 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1518 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1519 tr.close()
1519 tr.close()
1520
1520
1521 if tags:
1521 if tags:
1522 repo.opener.write("localtags", "".join(tags))
1522 repo.opener.write("localtags", "".join(tags))
1523 finally:
1523 finally:
1524 ui.progress(_('building'), None)
1524 ui.progress(_('building'), None)
1525 release(tr, lock)
1525 release(tr, lock)
1526
1526
1527 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1527 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1528 def debugbundle(ui, bundlepath, all=None, **opts):
1528 def debugbundle(ui, bundlepath, all=None, **opts):
1529 """lists the contents of a bundle"""
1529 """lists the contents of a bundle"""
1530 f = url.open(ui, bundlepath)
1530 f = url.open(ui, bundlepath)
1531 try:
1531 try:
1532 gen = changegroup.readbundle(f, bundlepath)
1532 gen = changegroup.readbundle(f, bundlepath)
1533 if all:
1533 if all:
1534 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1534 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1535
1535
1536 def showchunks(named):
1536 def showchunks(named):
1537 ui.write("\n%s\n" % named)
1537 ui.write("\n%s\n" % named)
1538 chain = None
1538 chain = None
1539 while True:
1539 while True:
1540 chunkdata = gen.deltachunk(chain)
1540 chunkdata = gen.deltachunk(chain)
1541 if not chunkdata:
1541 if not chunkdata:
1542 break
1542 break
1543 node = chunkdata['node']
1543 node = chunkdata['node']
1544 p1 = chunkdata['p1']
1544 p1 = chunkdata['p1']
1545 p2 = chunkdata['p2']
1545 p2 = chunkdata['p2']
1546 cs = chunkdata['cs']
1546 cs = chunkdata['cs']
1547 deltabase = chunkdata['deltabase']
1547 deltabase = chunkdata['deltabase']
1548 delta = chunkdata['delta']
1548 delta = chunkdata['delta']
1549 ui.write("%s %s %s %s %s %s\n" %
1549 ui.write("%s %s %s %s %s %s\n" %
1550 (hex(node), hex(p1), hex(p2),
1550 (hex(node), hex(p1), hex(p2),
1551 hex(cs), hex(deltabase), len(delta)))
1551 hex(cs), hex(deltabase), len(delta)))
1552 chain = node
1552 chain = node
1553
1553
1554 chunkdata = gen.changelogheader()
1554 chunkdata = gen.changelogheader()
1555 showchunks("changelog")
1555 showchunks("changelog")
1556 chunkdata = gen.manifestheader()
1556 chunkdata = gen.manifestheader()
1557 showchunks("manifest")
1557 showchunks("manifest")
1558 while True:
1558 while True:
1559 chunkdata = gen.filelogheader()
1559 chunkdata = gen.filelogheader()
1560 if not chunkdata:
1560 if not chunkdata:
1561 break
1561 break
1562 fname = chunkdata['filename']
1562 fname = chunkdata['filename']
1563 showchunks(fname)
1563 showchunks(fname)
1564 else:
1564 else:
1565 chunkdata = gen.changelogheader()
1565 chunkdata = gen.changelogheader()
1566 chain = None
1566 chain = None
1567 while True:
1567 while True:
1568 chunkdata = gen.deltachunk(chain)
1568 chunkdata = gen.deltachunk(chain)
1569 if not chunkdata:
1569 if not chunkdata:
1570 break
1570 break
1571 node = chunkdata['node']
1571 node = chunkdata['node']
1572 ui.write("%s\n" % hex(node))
1572 ui.write("%s\n" % hex(node))
1573 chain = node
1573 chain = node
1574 finally:
1574 finally:
1575 f.close()
1575 f.close()
1576
1576
1577 @command('debugcheckstate', [], '')
1577 @command('debugcheckstate', [], '')
1578 def debugcheckstate(ui, repo):
1578 def debugcheckstate(ui, repo):
1579 """validate the correctness of the current dirstate"""
1579 """validate the correctness of the current dirstate"""
1580 parent1, parent2 = repo.dirstate.parents()
1580 parent1, parent2 = repo.dirstate.parents()
1581 m1 = repo[parent1].manifest()
1581 m1 = repo[parent1].manifest()
1582 m2 = repo[parent2].manifest()
1582 m2 = repo[parent2].manifest()
1583 errors = 0
1583 errors = 0
1584 for f in repo.dirstate:
1584 for f in repo.dirstate:
1585 state = repo.dirstate[f]
1585 state = repo.dirstate[f]
1586 if state in "nr" and f not in m1:
1586 if state in "nr" and f not in m1:
1587 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1587 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1588 errors += 1
1588 errors += 1
1589 if state in "a" and f in m1:
1589 if state in "a" and f in m1:
1590 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1590 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1591 errors += 1
1591 errors += 1
1592 if state in "m" and f not in m1 and f not in m2:
1592 if state in "m" and f not in m1 and f not in m2:
1593 ui.warn(_("%s in state %s, but not in either manifest\n") %
1593 ui.warn(_("%s in state %s, but not in either manifest\n") %
1594 (f, state))
1594 (f, state))
1595 errors += 1
1595 errors += 1
1596 for f in m1:
1596 for f in m1:
1597 state = repo.dirstate[f]
1597 state = repo.dirstate[f]
1598 if state not in "nrm":
1598 if state not in "nrm":
1599 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1599 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1600 errors += 1
1600 errors += 1
1601 if errors:
1601 if errors:
1602 error = _(".hg/dirstate inconsistent with current parent's manifest")
1602 error = _(".hg/dirstate inconsistent with current parent's manifest")
1603 raise util.Abort(error)
1603 raise util.Abort(error)
1604
1604
1605 @command('debugcommands', [], _('[COMMAND]'))
1605 @command('debugcommands', [], _('[COMMAND]'))
1606 def debugcommands(ui, cmd='', *args):
1606 def debugcommands(ui, cmd='', *args):
1607 """list all available commands and options"""
1607 """list all available commands and options"""
1608 for cmd, vals in sorted(table.iteritems()):
1608 for cmd, vals in sorted(table.iteritems()):
1609 cmd = cmd.split('|')[0].strip('^')
1609 cmd = cmd.split('|')[0].strip('^')
1610 opts = ', '.join([i[1] for i in vals[1]])
1610 opts = ', '.join([i[1] for i in vals[1]])
1611 ui.write('%s: %s\n' % (cmd, opts))
1611 ui.write('%s: %s\n' % (cmd, opts))
1612
1612
1613 @command('debugcomplete',
1613 @command('debugcomplete',
1614 [('o', 'options', None, _('show the command options'))],
1614 [('o', 'options', None, _('show the command options'))],
1615 _('[-o] CMD'))
1615 _('[-o] CMD'))
1616 def debugcomplete(ui, cmd='', **opts):
1616 def debugcomplete(ui, cmd='', **opts):
1617 """returns the completion list associated with the given command"""
1617 """returns the completion list associated with the given command"""
1618
1618
1619 if opts.get('options'):
1619 if opts.get('options'):
1620 options = []
1620 options = []
1621 otables = [globalopts]
1621 otables = [globalopts]
1622 if cmd:
1622 if cmd:
1623 aliases, entry = cmdutil.findcmd(cmd, table, False)
1623 aliases, entry = cmdutil.findcmd(cmd, table, False)
1624 otables.append(entry[1])
1624 otables.append(entry[1])
1625 for t in otables:
1625 for t in otables:
1626 for o in t:
1626 for o in t:
1627 if "(DEPRECATED)" in o[3]:
1627 if "(DEPRECATED)" in o[3]:
1628 continue
1628 continue
1629 if o[0]:
1629 if o[0]:
1630 options.append('-%s' % o[0])
1630 options.append('-%s' % o[0])
1631 options.append('--%s' % o[1])
1631 options.append('--%s' % o[1])
1632 ui.write("%s\n" % "\n".join(options))
1632 ui.write("%s\n" % "\n".join(options))
1633 return
1633 return
1634
1634
1635 cmdlist = cmdutil.findpossible(cmd, table)
1635 cmdlist = cmdutil.findpossible(cmd, table)
1636 if ui.verbose:
1636 if ui.verbose:
1637 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1637 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1638 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1638 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1639
1639
1640 @command('debugdag',
1640 @command('debugdag',
1641 [('t', 'tags', None, _('use tags as labels')),
1641 [('t', 'tags', None, _('use tags as labels')),
1642 ('b', 'branches', None, _('annotate with branch names')),
1642 ('b', 'branches', None, _('annotate with branch names')),
1643 ('', 'dots', None, _('use dots for runs')),
1643 ('', 'dots', None, _('use dots for runs')),
1644 ('s', 'spaces', None, _('separate elements by spaces'))],
1644 ('s', 'spaces', None, _('separate elements by spaces'))],
1645 _('[OPTION]... [FILE [REV]...]'))
1645 _('[OPTION]... [FILE [REV]...]'))
1646 def debugdag(ui, repo, file_=None, *revs, **opts):
1646 def debugdag(ui, repo, file_=None, *revs, **opts):
1647 """format the changelog or an index DAG as a concise textual description
1647 """format the changelog or an index DAG as a concise textual description
1648
1648
1649 If you pass a revlog index, the revlog's DAG is emitted. If you list
1649 If you pass a revlog index, the revlog's DAG is emitted. If you list
1650 revision numbers, they get labelled in the output as rN.
1650 revision numbers, they get labelled in the output as rN.
1651
1651
1652 Otherwise, the changelog DAG of the current repo is emitted.
1652 Otherwise, the changelog DAG of the current repo is emitted.
1653 """
1653 """
1654 spaces = opts.get('spaces')
1654 spaces = opts.get('spaces')
1655 dots = opts.get('dots')
1655 dots = opts.get('dots')
1656 if file_:
1656 if file_:
1657 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1657 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1658 revs = set((int(r) for r in revs))
1658 revs = set((int(r) for r in revs))
1659 def events():
1659 def events():
1660 for r in rlog:
1660 for r in rlog:
1661 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1661 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1662 if r in revs:
1662 if r in revs:
1663 yield 'l', (r, "r%i" % r)
1663 yield 'l', (r, "r%i" % r)
1664 elif repo:
1664 elif repo:
1665 cl = repo.changelog
1665 cl = repo.changelog
1666 tags = opts.get('tags')
1666 tags = opts.get('tags')
1667 branches = opts.get('branches')
1667 branches = opts.get('branches')
1668 if tags:
1668 if tags:
1669 labels = {}
1669 labels = {}
1670 for l, n in repo.tags().items():
1670 for l, n in repo.tags().items():
1671 labels.setdefault(cl.rev(n), []).append(l)
1671 labels.setdefault(cl.rev(n), []).append(l)
1672 def events():
1672 def events():
1673 b = "default"
1673 b = "default"
1674 for r in cl:
1674 for r in cl:
1675 if branches:
1675 if branches:
1676 newb = cl.read(cl.node(r))[5]['branch']
1676 newb = cl.read(cl.node(r))[5]['branch']
1677 if newb != b:
1677 if newb != b:
1678 yield 'a', newb
1678 yield 'a', newb
1679 b = newb
1679 b = newb
1680 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1680 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1681 if tags:
1681 if tags:
1682 ls = labels.get(r)
1682 ls = labels.get(r)
1683 if ls:
1683 if ls:
1684 for l in ls:
1684 for l in ls:
1685 yield 'l', (r, l)
1685 yield 'l', (r, l)
1686 else:
1686 else:
1687 raise util.Abort(_('need repo for changelog dag'))
1687 raise util.Abort(_('need repo for changelog dag'))
1688
1688
1689 for line in dagparser.dagtextlines(events(),
1689 for line in dagparser.dagtextlines(events(),
1690 addspaces=spaces,
1690 addspaces=spaces,
1691 wraplabels=True,
1691 wraplabels=True,
1692 wrapannotations=True,
1692 wrapannotations=True,
1693 wrapnonlinear=dots,
1693 wrapnonlinear=dots,
1694 usedots=dots,
1694 usedots=dots,
1695 maxlinewidth=70):
1695 maxlinewidth=70):
1696 ui.write(line)
1696 ui.write(line)
1697 ui.write("\n")
1697 ui.write("\n")
1698
1698
1699 @command('debugdata',
1699 @command('debugdata',
1700 [('c', 'changelog', False, _('open changelog')),
1700 [('c', 'changelog', False, _('open changelog')),
1701 ('m', 'manifest', False, _('open manifest'))],
1701 ('m', 'manifest', False, _('open manifest'))],
1702 _('-c|-m|FILE REV'))
1702 _('-c|-m|FILE REV'))
1703 def debugdata(ui, repo, file_, rev = None, **opts):
1703 def debugdata(ui, repo, file_, rev = None, **opts):
1704 """dump the contents of a data file revision"""
1704 """dump the contents of a data file revision"""
1705 if opts.get('changelog') or opts.get('manifest'):
1705 if opts.get('changelog') or opts.get('manifest'):
1706 file_, rev = None, file_
1706 file_, rev = None, file_
1707 elif rev is None:
1707 elif rev is None:
1708 raise error.CommandError('debugdata', _('invalid arguments'))
1708 raise error.CommandError('debugdata', _('invalid arguments'))
1709 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1709 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1710 try:
1710 try:
1711 ui.write(r.revision(r.lookup(rev)))
1711 ui.write(r.revision(r.lookup(rev)))
1712 except KeyError:
1712 except KeyError:
1713 raise util.Abort(_('invalid revision identifier %s') % rev)
1713 raise util.Abort(_('invalid revision identifier %s') % rev)
1714
1714
1715 @command('debugdate',
1715 @command('debugdate',
1716 [('e', 'extended', None, _('try extended date formats'))],
1716 [('e', 'extended', None, _('try extended date formats'))],
1717 _('[-e] DATE [RANGE]'))
1717 _('[-e] DATE [RANGE]'))
1718 def debugdate(ui, date, range=None, **opts):
1718 def debugdate(ui, date, range=None, **opts):
1719 """parse and display a date"""
1719 """parse and display a date"""
1720 if opts["extended"]:
1720 if opts["extended"]:
1721 d = util.parsedate(date, util.extendeddateformats)
1721 d = util.parsedate(date, util.extendeddateformats)
1722 else:
1722 else:
1723 d = util.parsedate(date)
1723 d = util.parsedate(date)
1724 ui.write("internal: %s %s\n" % d)
1724 ui.write("internal: %s %s\n" % d)
1725 ui.write("standard: %s\n" % util.datestr(d))
1725 ui.write("standard: %s\n" % util.datestr(d))
1726 if range:
1726 if range:
1727 m = util.matchdate(range)
1727 m = util.matchdate(range)
1728 ui.write("match: %s\n" % m(d[0]))
1728 ui.write("match: %s\n" % m(d[0]))
1729
1729
1730 @command('debugdiscovery',
1730 @command('debugdiscovery',
1731 [('', 'old', None, _('use old-style discovery')),
1731 [('', 'old', None, _('use old-style discovery')),
1732 ('', 'nonheads', None,
1732 ('', 'nonheads', None,
1733 _('use old-style discovery with non-heads included')),
1733 _('use old-style discovery with non-heads included')),
1734 ] + remoteopts,
1734 ] + remoteopts,
1735 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1735 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1736 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1736 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1737 """runs the changeset discovery protocol in isolation"""
1737 """runs the changeset discovery protocol in isolation"""
1738 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1738 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1739 remote = hg.peer(repo, opts, remoteurl)
1739 remote = hg.peer(repo, opts, remoteurl)
1740 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1740 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1741
1741
1742 # make sure tests are repeatable
1742 # make sure tests are repeatable
1743 random.seed(12323)
1743 random.seed(12323)
1744
1744
1745 def doit(localheads, remoteheads):
1745 def doit(localheads, remoteheads):
1746 if opts.get('old'):
1746 if opts.get('old'):
1747 if localheads:
1747 if localheads:
1748 raise util.Abort('cannot use localheads with old style discovery')
1748 raise util.Abort('cannot use localheads with old style discovery')
1749 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1749 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1750 force=True)
1750 force=True)
1751 common = set(common)
1751 common = set(common)
1752 if not opts.get('nonheads'):
1752 if not opts.get('nonheads'):
1753 ui.write("unpruned common: %s\n" % " ".join([short(n)
1753 ui.write("unpruned common: %s\n" % " ".join([short(n)
1754 for n in common]))
1754 for n in common]))
1755 dag = dagutil.revlogdag(repo.changelog)
1755 dag = dagutil.revlogdag(repo.changelog)
1756 all = dag.ancestorset(dag.internalizeall(common))
1756 all = dag.ancestorset(dag.internalizeall(common))
1757 common = dag.externalizeall(dag.headsetofconnecteds(all))
1757 common = dag.externalizeall(dag.headsetofconnecteds(all))
1758 else:
1758 else:
1759 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1759 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1760 common = set(common)
1760 common = set(common)
1761 rheads = set(hds)
1761 rheads = set(hds)
1762 lheads = set(repo.heads())
1762 lheads = set(repo.heads())
1763 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1763 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1764 if lheads <= common:
1764 if lheads <= common:
1765 ui.write("local is subset\n")
1765 ui.write("local is subset\n")
1766 elif rheads <= common:
1766 elif rheads <= common:
1767 ui.write("remote is subset\n")
1767 ui.write("remote is subset\n")
1768
1768
1769 serverlogs = opts.get('serverlog')
1769 serverlogs = opts.get('serverlog')
1770 if serverlogs:
1770 if serverlogs:
1771 for filename in serverlogs:
1771 for filename in serverlogs:
1772 logfile = open(filename, 'r')
1772 logfile = open(filename, 'r')
1773 try:
1773 try:
1774 line = logfile.readline()
1774 line = logfile.readline()
1775 while line:
1775 while line:
1776 parts = line.strip().split(';')
1776 parts = line.strip().split(';')
1777 op = parts[1]
1777 op = parts[1]
1778 if op == 'cg':
1778 if op == 'cg':
1779 pass
1779 pass
1780 elif op == 'cgss':
1780 elif op == 'cgss':
1781 doit(parts[2].split(' '), parts[3].split(' '))
1781 doit(parts[2].split(' '), parts[3].split(' '))
1782 elif op == 'unb':
1782 elif op == 'unb':
1783 doit(parts[3].split(' '), parts[2].split(' '))
1783 doit(parts[3].split(' '), parts[2].split(' '))
1784 line = logfile.readline()
1784 line = logfile.readline()
1785 finally:
1785 finally:
1786 logfile.close()
1786 logfile.close()
1787
1787
1788 else:
1788 else:
1789 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1789 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1790 opts.get('remote_head'))
1790 opts.get('remote_head'))
1791 localrevs = opts.get('local_head')
1791 localrevs = opts.get('local_head')
1792 doit(localrevs, remoterevs)
1792 doit(localrevs, remoterevs)
1793
1793
1794 @command('debugfileset', [], ('REVSPEC'))
1794 @command('debugfileset', [], ('REVSPEC'))
1795 def debugfileset(ui, repo, expr):
1795 def debugfileset(ui, repo, expr):
1796 '''parse and apply a fileset specification'''
1796 '''parse and apply a fileset specification'''
1797 if ui.verbose:
1797 if ui.verbose:
1798 tree = fileset.parse(expr)[0]
1798 tree = fileset.parse(expr)[0]
1799 ui.note(tree, "\n")
1799 ui.note(tree, "\n")
1800
1800
1801 for f in fileset.getfileset(repo[None], expr):
1801 for f in fileset.getfileset(repo[None], expr):
1802 ui.write("%s\n" % f)
1802 ui.write("%s\n" % f)
1803
1803
1804 @command('debugfsinfo', [], _('[PATH]'))
1804 @command('debugfsinfo', [], _('[PATH]'))
1805 def debugfsinfo(ui, path = "."):
1805 def debugfsinfo(ui, path = "."):
1806 """show information detected about current filesystem"""
1806 """show information detected about current filesystem"""
1807 util.writefile('.debugfsinfo', '')
1807 util.writefile('.debugfsinfo', '')
1808 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1808 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1809 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1809 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1810 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1810 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1811 and 'yes' or 'no'))
1811 and 'yes' or 'no'))
1812 os.unlink('.debugfsinfo')
1812 os.unlink('.debugfsinfo')
1813
1813
1814 @command('debuggetbundle',
1814 @command('debuggetbundle',
1815 [('H', 'head', [], _('id of head node'), _('ID')),
1815 [('H', 'head', [], _('id of head node'), _('ID')),
1816 ('C', 'common', [], _('id of common node'), _('ID')),
1816 ('C', 'common', [], _('id of common node'), _('ID')),
1817 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1817 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1818 _('REPO FILE [-H|-C ID]...'))
1818 _('REPO FILE [-H|-C ID]...'))
1819 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1819 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1820 """retrieves a bundle from a repo
1820 """retrieves a bundle from a repo
1821
1821
1822 Every ID must be a full-length hex node id string. Saves the bundle to the
1822 Every ID must be a full-length hex node id string. Saves the bundle to the
1823 given file.
1823 given file.
1824 """
1824 """
1825 repo = hg.peer(ui, opts, repopath)
1825 repo = hg.peer(ui, opts, repopath)
1826 if not repo.capable('getbundle'):
1826 if not repo.capable('getbundle'):
1827 raise util.Abort("getbundle() not supported by target repository")
1827 raise util.Abort("getbundle() not supported by target repository")
1828 args = {}
1828 args = {}
1829 if common:
1829 if common:
1830 args['common'] = [bin(s) for s in common]
1830 args['common'] = [bin(s) for s in common]
1831 if head:
1831 if head:
1832 args['heads'] = [bin(s) for s in head]
1832 args['heads'] = [bin(s) for s in head]
1833 bundle = repo.getbundle('debug', **args)
1833 bundle = repo.getbundle('debug', **args)
1834
1834
1835 bundletype = opts.get('type', 'bzip2').lower()
1835 bundletype = opts.get('type', 'bzip2').lower()
1836 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1836 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1837 bundletype = btypes.get(bundletype)
1837 bundletype = btypes.get(bundletype)
1838 if bundletype not in changegroup.bundletypes:
1838 if bundletype not in changegroup.bundletypes:
1839 raise util.Abort(_('unknown bundle type specified with --type'))
1839 raise util.Abort(_('unknown bundle type specified with --type'))
1840 changegroup.writebundle(bundle, bundlepath, bundletype)
1840 changegroup.writebundle(bundle, bundlepath, bundletype)
1841
1841
1842 @command('debugignore', [], '')
1842 @command('debugignore', [], '')
1843 def debugignore(ui, repo, *values, **opts):
1843 def debugignore(ui, repo, *values, **opts):
1844 """display the combined ignore pattern"""
1844 """display the combined ignore pattern"""
1845 ignore = repo.dirstate._ignore
1845 ignore = repo.dirstate._ignore
1846 includepat = getattr(ignore, 'includepat', None)
1846 includepat = getattr(ignore, 'includepat', None)
1847 if includepat is not None:
1847 if includepat is not None:
1848 ui.write("%s\n" % includepat)
1848 ui.write("%s\n" % includepat)
1849 else:
1849 else:
1850 raise util.Abort(_("no ignore patterns found"))
1850 raise util.Abort(_("no ignore patterns found"))
1851
1851
1852 @command('debugindex',
1852 @command('debugindex',
1853 [('c', 'changelog', False, _('open changelog')),
1853 [('c', 'changelog', False, _('open changelog')),
1854 ('m', 'manifest', False, _('open manifest')),
1854 ('m', 'manifest', False, _('open manifest')),
1855 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1855 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1856 _('[-f FORMAT] -c|-m|FILE'))
1856 _('[-f FORMAT] -c|-m|FILE'))
1857 def debugindex(ui, repo, file_ = None, **opts):
1857 def debugindex(ui, repo, file_ = None, **opts):
1858 """dump the contents of an index file"""
1858 """dump the contents of an index file"""
1859 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1859 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1860 format = opts.get('format', 0)
1860 format = opts.get('format', 0)
1861 if format not in (0, 1):
1861 if format not in (0, 1):
1862 raise util.Abort(_("unknown format %d") % format)
1862 raise util.Abort(_("unknown format %d") % format)
1863
1863
1864 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1864 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1865 if generaldelta:
1865 if generaldelta:
1866 basehdr = ' delta'
1866 basehdr = ' delta'
1867 else:
1867 else:
1868 basehdr = ' base'
1868 basehdr = ' base'
1869
1869
1870 if format == 0:
1870 if format == 0:
1871 ui.write(" rev offset length " + basehdr + " linkrev"
1871 ui.write(" rev offset length " + basehdr + " linkrev"
1872 " nodeid p1 p2\n")
1872 " nodeid p1 p2\n")
1873 elif format == 1:
1873 elif format == 1:
1874 ui.write(" rev flag offset length"
1874 ui.write(" rev flag offset length"
1875 " size " + basehdr + " link p1 p2 nodeid\n")
1875 " size " + basehdr + " link p1 p2 nodeid\n")
1876
1876
1877 for i in r:
1877 for i in r:
1878 node = r.node(i)
1878 node = r.node(i)
1879 if generaldelta:
1879 if generaldelta:
1880 base = r.deltaparent(i)
1880 base = r.deltaparent(i)
1881 else:
1881 else:
1882 base = r.chainbase(i)
1882 base = r.chainbase(i)
1883 if format == 0:
1883 if format == 0:
1884 try:
1884 try:
1885 pp = r.parents(node)
1885 pp = r.parents(node)
1886 except:
1886 except:
1887 pp = [nullid, nullid]
1887 pp = [nullid, nullid]
1888 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1888 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1889 i, r.start(i), r.length(i), base, r.linkrev(i),
1889 i, r.start(i), r.length(i), base, r.linkrev(i),
1890 short(node), short(pp[0]), short(pp[1])))
1890 short(node), short(pp[0]), short(pp[1])))
1891 elif format == 1:
1891 elif format == 1:
1892 pr = r.parentrevs(i)
1892 pr = r.parentrevs(i)
1893 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1893 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1894 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1894 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1895 base, r.linkrev(i), pr[0], pr[1], short(node)))
1895 base, r.linkrev(i), pr[0], pr[1], short(node)))
1896
1896
1897 @command('debugindexdot', [], _('FILE'))
1897 @command('debugindexdot', [], _('FILE'))
1898 def debugindexdot(ui, repo, file_):
1898 def debugindexdot(ui, repo, file_):
1899 """dump an index DAG as a graphviz dot file"""
1899 """dump an index DAG as a graphviz dot file"""
1900 r = None
1900 r = None
1901 if repo:
1901 if repo:
1902 filelog = repo.file(file_)
1902 filelog = repo.file(file_)
1903 if len(filelog):
1903 if len(filelog):
1904 r = filelog
1904 r = filelog
1905 if not r:
1905 if not r:
1906 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1906 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1907 ui.write("digraph G {\n")
1907 ui.write("digraph G {\n")
1908 for i in r:
1908 for i in r:
1909 node = r.node(i)
1909 node = r.node(i)
1910 pp = r.parents(node)
1910 pp = r.parents(node)
1911 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1911 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1912 if pp[1] != nullid:
1912 if pp[1] != nullid:
1913 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1913 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1914 ui.write("}\n")
1914 ui.write("}\n")
1915
1915
1916 @command('debuginstall', [], '')
1916 @command('debuginstall', [], '')
1917 def debuginstall(ui):
1917 def debuginstall(ui):
1918 '''test Mercurial installation
1918 '''test Mercurial installation
1919
1919
1920 Returns 0 on success.
1920 Returns 0 on success.
1921 '''
1921 '''
1922
1922
1923 def writetemp(contents):
1923 def writetemp(contents):
1924 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1924 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1925 f = os.fdopen(fd, "wb")
1925 f = os.fdopen(fd, "wb")
1926 f.write(contents)
1926 f.write(contents)
1927 f.close()
1927 f.close()
1928 return name
1928 return name
1929
1929
1930 problems = 0
1930 problems = 0
1931
1931
1932 # encoding
1932 # encoding
1933 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1933 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1934 try:
1934 try:
1935 encoding.fromlocal("test")
1935 encoding.fromlocal("test")
1936 except util.Abort, inst:
1936 except util.Abort, inst:
1937 ui.write(" %s\n" % inst)
1937 ui.write(" %s\n" % inst)
1938 ui.write(_(" (check that your locale is properly set)\n"))
1938 ui.write(_(" (check that your locale is properly set)\n"))
1939 problems += 1
1939 problems += 1
1940
1940
1941 # compiled modules
1941 # compiled modules
1942 ui.status(_("Checking installed modules (%s)...\n")
1942 ui.status(_("Checking installed modules (%s)...\n")
1943 % os.path.dirname(__file__))
1943 % os.path.dirname(__file__))
1944 try:
1944 try:
1945 import bdiff, mpatch, base85, osutil
1945 import bdiff, mpatch, base85, osutil
1946 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1946 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1947 except Exception, inst:
1947 except Exception, inst:
1948 ui.write(" %s\n" % inst)
1948 ui.write(" %s\n" % inst)
1949 ui.write(_(" One or more extensions could not be found"))
1949 ui.write(_(" One or more extensions could not be found"))
1950 ui.write(_(" (check that you compiled the extensions)\n"))
1950 ui.write(_(" (check that you compiled the extensions)\n"))
1951 problems += 1
1951 problems += 1
1952
1952
1953 # templates
1953 # templates
1954 import templater
1954 import templater
1955 p = templater.templatepath()
1955 p = templater.templatepath()
1956 ui.status(_("Checking templates (%s)...\n") % ' '.join(p))
1956 ui.status(_("Checking templates (%s)...\n") % ' '.join(p))
1957 try:
1957 try:
1958 templater.templater(templater.templatepath("map-cmdline.default"))
1958 templater.templater(templater.templatepath("map-cmdline.default"))
1959 except Exception, inst:
1959 except Exception, inst:
1960 ui.write(" %s\n" % inst)
1960 ui.write(" %s\n" % inst)
1961 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1961 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1962 problems += 1
1962 problems += 1
1963
1963
1964 # editor
1964 # editor
1965 ui.status(_("Checking commit editor...\n"))
1965 ui.status(_("Checking commit editor...\n"))
1966 editor = ui.geteditor()
1966 editor = ui.geteditor()
1967 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1967 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1968 if not cmdpath:
1968 if not cmdpath:
1969 if editor == 'vi':
1969 if editor == 'vi':
1970 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1970 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1971 ui.write(_(" (specify a commit editor in your configuration"
1971 ui.write(_(" (specify a commit editor in your configuration"
1972 " file)\n"))
1972 " file)\n"))
1973 else:
1973 else:
1974 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1974 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1975 ui.write(_(" (specify a commit editor in your configuration"
1975 ui.write(_(" (specify a commit editor in your configuration"
1976 " file)\n"))
1976 " file)\n"))
1977 problems += 1
1977 problems += 1
1978
1978
1979 # check username
1979 # check username
1980 ui.status(_("Checking username...\n"))
1980 ui.status(_("Checking username...\n"))
1981 try:
1981 try:
1982 ui.username()
1982 ui.username()
1983 except util.Abort, e:
1983 except util.Abort, e:
1984 ui.write(" %s\n" % e)
1984 ui.write(" %s\n" % e)
1985 ui.write(_(" (specify a username in your configuration file)\n"))
1985 ui.write(_(" (specify a username in your configuration file)\n"))
1986 problems += 1
1986 problems += 1
1987
1987
1988 if not problems:
1988 if not problems:
1989 ui.status(_("No problems detected\n"))
1989 ui.status(_("No problems detected\n"))
1990 else:
1990 else:
1991 ui.write(_("%s problems detected,"
1991 ui.write(_("%s problems detected,"
1992 " please check your install!\n") % problems)
1992 " please check your install!\n") % problems)
1993
1993
1994 return problems
1994 return problems
1995
1995
1996 @command('debugknown', [], _('REPO ID...'))
1996 @command('debugknown', [], _('REPO ID...'))
1997 def debugknown(ui, repopath, *ids, **opts):
1997 def debugknown(ui, repopath, *ids, **opts):
1998 """test whether node ids are known to a repo
1998 """test whether node ids are known to a repo
1999
1999
2000 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
2000 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
2001 indicating unknown/known.
2001 indicating unknown/known.
2002 """
2002 """
2003 repo = hg.peer(ui, opts, repopath)
2003 repo = hg.peer(ui, opts, repopath)
2004 if not repo.capable('known'):
2004 if not repo.capable('known'):
2005 raise util.Abort("known() not supported by target repository")
2005 raise util.Abort("known() not supported by target repository")
2006 flags = repo.known([bin(s) for s in ids])
2006 flags = repo.known([bin(s) for s in ids])
2007 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2007 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2008
2008
2009 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2009 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2010 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2010 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2011 '''access the pushkey key/value protocol
2011 '''access the pushkey key/value protocol
2012
2012
2013 With two args, list the keys in the given namespace.
2013 With two args, list the keys in the given namespace.
2014
2014
2015 With five args, set a key to new if it currently is set to old.
2015 With five args, set a key to new if it currently is set to old.
2016 Reports success or failure.
2016 Reports success or failure.
2017 '''
2017 '''
2018
2018
2019 target = hg.peer(ui, {}, repopath)
2019 target = hg.peer(ui, {}, repopath)
2020 if keyinfo:
2020 if keyinfo:
2021 key, old, new = keyinfo
2021 key, old, new = keyinfo
2022 r = target.pushkey(namespace, key, old, new)
2022 r = target.pushkey(namespace, key, old, new)
2023 ui.status(str(r) + '\n')
2023 ui.status(str(r) + '\n')
2024 return not r
2024 return not r
2025 else:
2025 else:
2026 for k, v in target.listkeys(namespace).iteritems():
2026 for k, v in target.listkeys(namespace).iteritems():
2027 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2027 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2028 v.encode('string-escape')))
2028 v.encode('string-escape')))
2029
2029
2030 @command('debugpvec', [], _('A B'))
2030 @command('debugpvec', [], _('A B'))
2031 def debugpvec(ui, repo, a, b=None):
2031 def debugpvec(ui, repo, a, b=None):
2032 ca = scmutil.revsingle(repo, a)
2032 ca = scmutil.revsingle(repo, a)
2033 cb = scmutil.revsingle(repo, b)
2033 cb = scmutil.revsingle(repo, b)
2034 pa = pvec.ctxpvec(ca)
2034 pa = pvec.ctxpvec(ca)
2035 pb = pvec.ctxpvec(cb)
2035 pb = pvec.ctxpvec(cb)
2036 if pa == pb:
2036 if pa == pb:
2037 rel = "="
2037 rel = "="
2038 elif pa > pb:
2038 elif pa > pb:
2039 rel = ">"
2039 rel = ">"
2040 elif pa < pb:
2040 elif pa < pb:
2041 rel = "<"
2041 rel = "<"
2042 elif pa | pb:
2042 elif pa | pb:
2043 rel = "|"
2043 rel = "|"
2044 ui.write(_("a: %s\n") % pa)
2044 ui.write(_("a: %s\n") % pa)
2045 ui.write(_("b: %s\n") % pb)
2045 ui.write(_("b: %s\n") % pb)
2046 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2046 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2047 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2047 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2048 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2048 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2049 pa.distance(pb), rel))
2049 pa.distance(pb), rel))
2050
2050
2051 @command('debugrebuildstate',
2051 @command('debugrebuildstate',
2052 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2052 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2053 _('[-r REV] [REV]'))
2053 _('[-r REV] [REV]'))
2054 def debugrebuildstate(ui, repo, rev="tip"):
2054 def debugrebuildstate(ui, repo, rev="tip"):
2055 """rebuild the dirstate as it would look like for the given revision"""
2055 """rebuild the dirstate as it would look like for the given revision"""
2056 ctx = scmutil.revsingle(repo, rev)
2056 ctx = scmutil.revsingle(repo, rev)
2057 wlock = repo.wlock()
2057 wlock = repo.wlock()
2058 try:
2058 try:
2059 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2059 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2060 finally:
2060 finally:
2061 wlock.release()
2061 wlock.release()
2062
2062
2063 @command('debugrename',
2063 @command('debugrename',
2064 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2064 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2065 _('[-r REV] FILE'))
2065 _('[-r REV] FILE'))
2066 def debugrename(ui, repo, file1, *pats, **opts):
2066 def debugrename(ui, repo, file1, *pats, **opts):
2067 """dump rename information"""
2067 """dump rename information"""
2068
2068
2069 ctx = scmutil.revsingle(repo, opts.get('rev'))
2069 ctx = scmutil.revsingle(repo, opts.get('rev'))
2070 m = scmutil.match(ctx, (file1,) + pats, opts)
2070 m = scmutil.match(ctx, (file1,) + pats, opts)
2071 for abs in ctx.walk(m):
2071 for abs in ctx.walk(m):
2072 fctx = ctx[abs]
2072 fctx = ctx[abs]
2073 o = fctx.filelog().renamed(fctx.filenode())
2073 o = fctx.filelog().renamed(fctx.filenode())
2074 rel = m.rel(abs)
2074 rel = m.rel(abs)
2075 if o:
2075 if o:
2076 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2076 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2077 else:
2077 else:
2078 ui.write(_("%s not renamed\n") % rel)
2078 ui.write(_("%s not renamed\n") % rel)
2079
2079
2080 @command('debugrevlog',
2080 @command('debugrevlog',
2081 [('c', 'changelog', False, _('open changelog')),
2081 [('c', 'changelog', False, _('open changelog')),
2082 ('m', 'manifest', False, _('open manifest')),
2082 ('m', 'manifest', False, _('open manifest')),
2083 ('d', 'dump', False, _('dump index data'))],
2083 ('d', 'dump', False, _('dump index data'))],
2084 _('-c|-m|FILE'))
2084 _('-c|-m|FILE'))
2085 def debugrevlog(ui, repo, file_ = None, **opts):
2085 def debugrevlog(ui, repo, file_ = None, **opts):
2086 """show data and statistics about a revlog"""
2086 """show data and statistics about a revlog"""
2087 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2087 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2088
2088
2089 if opts.get("dump"):
2089 if opts.get("dump"):
2090 numrevs = len(r)
2090 numrevs = len(r)
2091 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2091 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2092 " rawsize totalsize compression heads\n")
2092 " rawsize totalsize compression heads\n")
2093 ts = 0
2093 ts = 0
2094 heads = set()
2094 heads = set()
2095 for rev in xrange(numrevs):
2095 for rev in xrange(numrevs):
2096 dbase = r.deltaparent(rev)
2096 dbase = r.deltaparent(rev)
2097 if dbase == -1:
2097 if dbase == -1:
2098 dbase = rev
2098 dbase = rev
2099 cbase = r.chainbase(rev)
2099 cbase = r.chainbase(rev)
2100 p1, p2 = r.parentrevs(rev)
2100 p1, p2 = r.parentrevs(rev)
2101 rs = r.rawsize(rev)
2101 rs = r.rawsize(rev)
2102 ts = ts + rs
2102 ts = ts + rs
2103 heads -= set(r.parentrevs(rev))
2103 heads -= set(r.parentrevs(rev))
2104 heads.add(rev)
2104 heads.add(rev)
2105 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2105 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2106 (rev, p1, p2, r.start(rev), r.end(rev),
2106 (rev, p1, p2, r.start(rev), r.end(rev),
2107 r.start(dbase), r.start(cbase),
2107 r.start(dbase), r.start(cbase),
2108 r.start(p1), r.start(p2),
2108 r.start(p1), r.start(p2),
2109 rs, ts, ts / r.end(rev), len(heads)))
2109 rs, ts, ts / r.end(rev), len(heads)))
2110 return 0
2110 return 0
2111
2111
2112 v = r.version
2112 v = r.version
2113 format = v & 0xFFFF
2113 format = v & 0xFFFF
2114 flags = []
2114 flags = []
2115 gdelta = False
2115 gdelta = False
2116 if v & revlog.REVLOGNGINLINEDATA:
2116 if v & revlog.REVLOGNGINLINEDATA:
2117 flags.append('inline')
2117 flags.append('inline')
2118 if v & revlog.REVLOGGENERALDELTA:
2118 if v & revlog.REVLOGGENERALDELTA:
2119 gdelta = True
2119 gdelta = True
2120 flags.append('generaldelta')
2120 flags.append('generaldelta')
2121 if not flags:
2121 if not flags:
2122 flags = ['(none)']
2122 flags = ['(none)']
2123
2123
2124 nummerges = 0
2124 nummerges = 0
2125 numfull = 0
2125 numfull = 0
2126 numprev = 0
2126 numprev = 0
2127 nump1 = 0
2127 nump1 = 0
2128 nump2 = 0
2128 nump2 = 0
2129 numother = 0
2129 numother = 0
2130 nump1prev = 0
2130 nump1prev = 0
2131 nump2prev = 0
2131 nump2prev = 0
2132 chainlengths = []
2132 chainlengths = []
2133
2133
2134 datasize = [None, 0, 0L]
2134 datasize = [None, 0, 0L]
2135 fullsize = [None, 0, 0L]
2135 fullsize = [None, 0, 0L]
2136 deltasize = [None, 0, 0L]
2136 deltasize = [None, 0, 0L]
2137
2137
2138 def addsize(size, l):
2138 def addsize(size, l):
2139 if l[0] is None or size < l[0]:
2139 if l[0] is None or size < l[0]:
2140 l[0] = size
2140 l[0] = size
2141 if size > l[1]:
2141 if size > l[1]:
2142 l[1] = size
2142 l[1] = size
2143 l[2] += size
2143 l[2] += size
2144
2144
2145 numrevs = len(r)
2145 numrevs = len(r)
2146 for rev in xrange(numrevs):
2146 for rev in xrange(numrevs):
2147 p1, p2 = r.parentrevs(rev)
2147 p1, p2 = r.parentrevs(rev)
2148 delta = r.deltaparent(rev)
2148 delta = r.deltaparent(rev)
2149 if format > 0:
2149 if format > 0:
2150 addsize(r.rawsize(rev), datasize)
2150 addsize(r.rawsize(rev), datasize)
2151 if p2 != nullrev:
2151 if p2 != nullrev:
2152 nummerges += 1
2152 nummerges += 1
2153 size = r.length(rev)
2153 size = r.length(rev)
2154 if delta == nullrev:
2154 if delta == nullrev:
2155 chainlengths.append(0)
2155 chainlengths.append(0)
2156 numfull += 1
2156 numfull += 1
2157 addsize(size, fullsize)
2157 addsize(size, fullsize)
2158 else:
2158 else:
2159 chainlengths.append(chainlengths[delta] + 1)
2159 chainlengths.append(chainlengths[delta] + 1)
2160 addsize(size, deltasize)
2160 addsize(size, deltasize)
2161 if delta == rev - 1:
2161 if delta == rev - 1:
2162 numprev += 1
2162 numprev += 1
2163 if delta == p1:
2163 if delta == p1:
2164 nump1prev += 1
2164 nump1prev += 1
2165 elif delta == p2:
2165 elif delta == p2:
2166 nump2prev += 1
2166 nump2prev += 1
2167 elif delta == p1:
2167 elif delta == p1:
2168 nump1 += 1
2168 nump1 += 1
2169 elif delta == p2:
2169 elif delta == p2:
2170 nump2 += 1
2170 nump2 += 1
2171 elif delta != nullrev:
2171 elif delta != nullrev:
2172 numother += 1
2172 numother += 1
2173
2173
2174 numdeltas = numrevs - numfull
2174 numdeltas = numrevs - numfull
2175 numoprev = numprev - nump1prev - nump2prev
2175 numoprev = numprev - nump1prev - nump2prev
2176 totalrawsize = datasize[2]
2176 totalrawsize = datasize[2]
2177 datasize[2] /= numrevs
2177 datasize[2] /= numrevs
2178 fulltotal = fullsize[2]
2178 fulltotal = fullsize[2]
2179 fullsize[2] /= numfull
2179 fullsize[2] /= numfull
2180 deltatotal = deltasize[2]
2180 deltatotal = deltasize[2]
2181 deltasize[2] /= numrevs - numfull
2181 deltasize[2] /= numrevs - numfull
2182 totalsize = fulltotal + deltatotal
2182 totalsize = fulltotal + deltatotal
2183 avgchainlen = sum(chainlengths) / numrevs
2183 avgchainlen = sum(chainlengths) / numrevs
2184 compratio = totalrawsize / totalsize
2184 compratio = totalrawsize / totalsize
2185
2185
2186 basedfmtstr = '%%%dd\n'
2186 basedfmtstr = '%%%dd\n'
2187 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2187 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2188
2188
2189 def dfmtstr(max):
2189 def dfmtstr(max):
2190 return basedfmtstr % len(str(max))
2190 return basedfmtstr % len(str(max))
2191 def pcfmtstr(max, padding=0):
2191 def pcfmtstr(max, padding=0):
2192 return basepcfmtstr % (len(str(max)), ' ' * padding)
2192 return basepcfmtstr % (len(str(max)), ' ' * padding)
2193
2193
2194 def pcfmt(value, total):
2194 def pcfmt(value, total):
2195 return (value, 100 * float(value) / total)
2195 return (value, 100 * float(value) / total)
2196
2196
2197 ui.write('format : %d\n' % format)
2197 ui.write('format : %d\n' % format)
2198 ui.write('flags : %s\n' % ', '.join(flags))
2198 ui.write('flags : %s\n' % ', '.join(flags))
2199
2199
2200 ui.write('\n')
2200 ui.write('\n')
2201 fmt = pcfmtstr(totalsize)
2201 fmt = pcfmtstr(totalsize)
2202 fmt2 = dfmtstr(totalsize)
2202 fmt2 = dfmtstr(totalsize)
2203 ui.write('revisions : ' + fmt2 % numrevs)
2203 ui.write('revisions : ' + fmt2 % numrevs)
2204 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2204 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2205 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2205 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2206 ui.write('revisions : ' + fmt2 % numrevs)
2206 ui.write('revisions : ' + fmt2 % numrevs)
2207 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2207 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2208 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2208 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2209 ui.write('revision size : ' + fmt2 % totalsize)
2209 ui.write('revision size : ' + fmt2 % totalsize)
2210 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2210 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2211 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2211 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2212
2212
2213 ui.write('\n')
2213 ui.write('\n')
2214 fmt = dfmtstr(max(avgchainlen, compratio))
2214 fmt = dfmtstr(max(avgchainlen, compratio))
2215 ui.write('avg chain length : ' + fmt % avgchainlen)
2215 ui.write('avg chain length : ' + fmt % avgchainlen)
2216 ui.write('compression ratio : ' + fmt % compratio)
2216 ui.write('compression ratio : ' + fmt % compratio)
2217
2217
2218 if format > 0:
2218 if format > 0:
2219 ui.write('\n')
2219 ui.write('\n')
2220 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2220 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2221 % tuple(datasize))
2221 % tuple(datasize))
2222 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2222 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2223 % tuple(fullsize))
2223 % tuple(fullsize))
2224 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2224 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2225 % tuple(deltasize))
2225 % tuple(deltasize))
2226
2226
2227 if numdeltas > 0:
2227 if numdeltas > 0:
2228 ui.write('\n')
2228 ui.write('\n')
2229 fmt = pcfmtstr(numdeltas)
2229 fmt = pcfmtstr(numdeltas)
2230 fmt2 = pcfmtstr(numdeltas, 4)
2230 fmt2 = pcfmtstr(numdeltas, 4)
2231 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2231 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2232 if numprev > 0:
2232 if numprev > 0:
2233 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2233 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2234 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2234 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2235 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2235 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2236 if gdelta:
2236 if gdelta:
2237 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2237 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2238 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2238 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2239 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2239 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2240
2240
2241 @command('debugrevspec', [], ('REVSPEC'))
2241 @command('debugrevspec', [], ('REVSPEC'))
2242 def debugrevspec(ui, repo, expr):
2242 def debugrevspec(ui, repo, expr):
2243 """parse and apply a revision specification
2243 """parse and apply a revision specification
2244
2244
2245 Use --verbose to print the parsed tree before and after aliases
2245 Use --verbose to print the parsed tree before and after aliases
2246 expansion.
2246 expansion.
2247 """
2247 """
2248 if ui.verbose:
2248 if ui.verbose:
2249 tree = revset.parse(expr)[0]
2249 tree = revset.parse(expr)[0]
2250 ui.note(revset.prettyformat(tree), "\n")
2250 ui.note(revset.prettyformat(tree), "\n")
2251 newtree = revset.findaliases(ui, tree)
2251 newtree = revset.findaliases(ui, tree)
2252 if newtree != tree:
2252 if newtree != tree:
2253 ui.note(revset.prettyformat(newtree), "\n")
2253 ui.note(revset.prettyformat(newtree), "\n")
2254 func = revset.match(ui, expr)
2254 func = revset.match(ui, expr)
2255 for c in func(repo, range(len(repo))):
2255 for c in func(repo, range(len(repo))):
2256 ui.write("%s\n" % c)
2256 ui.write("%s\n" % c)
2257
2257
2258 @command('debugsetparents', [], _('REV1 [REV2]'))
2258 @command('debugsetparents', [], _('REV1 [REV2]'))
2259 def debugsetparents(ui, repo, rev1, rev2=None):
2259 def debugsetparents(ui, repo, rev1, rev2=None):
2260 """manually set the parents of the current working directory
2260 """manually set the parents of the current working directory
2261
2261
2262 This is useful for writing repository conversion tools, but should
2262 This is useful for writing repository conversion tools, but should
2263 be used with care.
2263 be used with care.
2264
2264
2265 Returns 0 on success.
2265 Returns 0 on success.
2266 """
2266 """
2267
2267
2268 r1 = scmutil.revsingle(repo, rev1).node()
2268 r1 = scmutil.revsingle(repo, rev1).node()
2269 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2269 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2270
2270
2271 wlock = repo.wlock()
2271 wlock = repo.wlock()
2272 try:
2272 try:
2273 repo.dirstate.setparents(r1, r2)
2273 repo.setparents(r1, r2)
2274 finally:
2274 finally:
2275 wlock.release()
2275 wlock.release()
2276
2276
2277 @command('debugstate',
2277 @command('debugstate',
2278 [('', 'nodates', None, _('do not display the saved mtime')),
2278 [('', 'nodates', None, _('do not display the saved mtime')),
2279 ('', 'datesort', None, _('sort by saved mtime'))],
2279 ('', 'datesort', None, _('sort by saved mtime'))],
2280 _('[OPTION]...'))
2280 _('[OPTION]...'))
2281 def debugstate(ui, repo, nodates=None, datesort=None):
2281 def debugstate(ui, repo, nodates=None, datesort=None):
2282 """show the contents of the current dirstate"""
2282 """show the contents of the current dirstate"""
2283 timestr = ""
2283 timestr = ""
2284 showdate = not nodates
2284 showdate = not nodates
2285 if datesort:
2285 if datesort:
2286 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2286 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2287 else:
2287 else:
2288 keyfunc = None # sort by filename
2288 keyfunc = None # sort by filename
2289 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2289 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2290 if showdate:
2290 if showdate:
2291 if ent[3] == -1:
2291 if ent[3] == -1:
2292 # Pad or slice to locale representation
2292 # Pad or slice to locale representation
2293 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2293 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2294 time.localtime(0)))
2294 time.localtime(0)))
2295 timestr = 'unset'
2295 timestr = 'unset'
2296 timestr = (timestr[:locale_len] +
2296 timestr = (timestr[:locale_len] +
2297 ' ' * (locale_len - len(timestr)))
2297 ' ' * (locale_len - len(timestr)))
2298 else:
2298 else:
2299 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2299 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2300 time.localtime(ent[3]))
2300 time.localtime(ent[3]))
2301 if ent[1] & 020000:
2301 if ent[1] & 020000:
2302 mode = 'lnk'
2302 mode = 'lnk'
2303 else:
2303 else:
2304 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2304 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2305 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2305 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2306 for f in repo.dirstate.copies():
2306 for f in repo.dirstate.copies():
2307 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2307 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2308
2308
2309 @command('debugsub',
2309 @command('debugsub',
2310 [('r', 'rev', '',
2310 [('r', 'rev', '',
2311 _('revision to check'), _('REV'))],
2311 _('revision to check'), _('REV'))],
2312 _('[-r REV] [REV]'))
2312 _('[-r REV] [REV]'))
2313 def debugsub(ui, repo, rev=None):
2313 def debugsub(ui, repo, rev=None):
2314 ctx = scmutil.revsingle(repo, rev, None)
2314 ctx = scmutil.revsingle(repo, rev, None)
2315 for k, v in sorted(ctx.substate.items()):
2315 for k, v in sorted(ctx.substate.items()):
2316 ui.write('path %s\n' % k)
2316 ui.write('path %s\n' % k)
2317 ui.write(' source %s\n' % v[0])
2317 ui.write(' source %s\n' % v[0])
2318 ui.write(' revision %s\n' % v[1])
2318 ui.write(' revision %s\n' % v[1])
2319
2319
2320 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2320 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2321 def debugwalk(ui, repo, *pats, **opts):
2321 def debugwalk(ui, repo, *pats, **opts):
2322 """show how files match on given patterns"""
2322 """show how files match on given patterns"""
2323 m = scmutil.match(repo[None], pats, opts)
2323 m = scmutil.match(repo[None], pats, opts)
2324 items = list(repo.walk(m))
2324 items = list(repo.walk(m))
2325 if not items:
2325 if not items:
2326 return
2326 return
2327 fmt = 'f %%-%ds %%-%ds %%s' % (
2327 fmt = 'f %%-%ds %%-%ds %%s' % (
2328 max([len(abs) for abs in items]),
2328 max([len(abs) for abs in items]),
2329 max([len(m.rel(abs)) for abs in items]))
2329 max([len(m.rel(abs)) for abs in items]))
2330 for abs in items:
2330 for abs in items:
2331 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2331 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2332 ui.write("%s\n" % line.rstrip())
2332 ui.write("%s\n" % line.rstrip())
2333
2333
2334 @command('debugwireargs',
2334 @command('debugwireargs',
2335 [('', 'three', '', 'three'),
2335 [('', 'three', '', 'three'),
2336 ('', 'four', '', 'four'),
2336 ('', 'four', '', 'four'),
2337 ('', 'five', '', 'five'),
2337 ('', 'five', '', 'five'),
2338 ] + remoteopts,
2338 ] + remoteopts,
2339 _('REPO [OPTIONS]... [ONE [TWO]]'))
2339 _('REPO [OPTIONS]... [ONE [TWO]]'))
2340 def debugwireargs(ui, repopath, *vals, **opts):
2340 def debugwireargs(ui, repopath, *vals, **opts):
2341 repo = hg.peer(ui, opts, repopath)
2341 repo = hg.peer(ui, opts, repopath)
2342 for opt in remoteopts:
2342 for opt in remoteopts:
2343 del opts[opt[1]]
2343 del opts[opt[1]]
2344 args = {}
2344 args = {}
2345 for k, v in opts.iteritems():
2345 for k, v in opts.iteritems():
2346 if v:
2346 if v:
2347 args[k] = v
2347 args[k] = v
2348 # run twice to check that we don't mess up the stream for the next command
2348 # run twice to check that we don't mess up the stream for the next command
2349 res1 = repo.debugwireargs(*vals, **args)
2349 res1 = repo.debugwireargs(*vals, **args)
2350 res2 = repo.debugwireargs(*vals, **args)
2350 res2 = repo.debugwireargs(*vals, **args)
2351 ui.write("%s\n" % res1)
2351 ui.write("%s\n" % res1)
2352 if res1 != res2:
2352 if res1 != res2:
2353 ui.warn("%s\n" % res2)
2353 ui.warn("%s\n" % res2)
2354
2354
2355 @command('^diff',
2355 @command('^diff',
2356 [('r', 'rev', [], _('revision'), _('REV')),
2356 [('r', 'rev', [], _('revision'), _('REV')),
2357 ('c', 'change', '', _('change made by revision'), _('REV'))
2357 ('c', 'change', '', _('change made by revision'), _('REV'))
2358 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2358 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2359 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2359 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2360 def diff(ui, repo, *pats, **opts):
2360 def diff(ui, repo, *pats, **opts):
2361 """diff repository (or selected files)
2361 """diff repository (or selected files)
2362
2362
2363 Show differences between revisions for the specified files.
2363 Show differences between revisions for the specified files.
2364
2364
2365 Differences between files are shown using the unified diff format.
2365 Differences between files are shown using the unified diff format.
2366
2366
2367 .. note::
2367 .. note::
2368 diff may generate unexpected results for merges, as it will
2368 diff may generate unexpected results for merges, as it will
2369 default to comparing against the working directory's first
2369 default to comparing against the working directory's first
2370 parent changeset if no revisions are specified.
2370 parent changeset if no revisions are specified.
2371
2371
2372 When two revision arguments are given, then changes are shown
2372 When two revision arguments are given, then changes are shown
2373 between those revisions. If only one revision is specified then
2373 between those revisions. If only one revision is specified then
2374 that revision is compared to the working directory, and, when no
2374 that revision is compared to the working directory, and, when no
2375 revisions are specified, the working directory files are compared
2375 revisions are specified, the working directory files are compared
2376 to its parent.
2376 to its parent.
2377
2377
2378 Alternatively you can specify -c/--change with a revision to see
2378 Alternatively you can specify -c/--change with a revision to see
2379 the changes in that changeset relative to its first parent.
2379 the changes in that changeset relative to its first parent.
2380
2380
2381 Without the -a/--text option, diff will avoid generating diffs of
2381 Without the -a/--text option, diff will avoid generating diffs of
2382 files it detects as binary. With -a, diff will generate a diff
2382 files it detects as binary. With -a, diff will generate a diff
2383 anyway, probably with undesirable results.
2383 anyway, probably with undesirable results.
2384
2384
2385 Use the -g/--git option to generate diffs in the git extended diff
2385 Use the -g/--git option to generate diffs in the git extended diff
2386 format. For more information, read :hg:`help diffs`.
2386 format. For more information, read :hg:`help diffs`.
2387
2387
2388 .. container:: verbose
2388 .. container:: verbose
2389
2389
2390 Examples:
2390 Examples:
2391
2391
2392 - compare a file in the current working directory to its parent::
2392 - compare a file in the current working directory to its parent::
2393
2393
2394 hg diff foo.c
2394 hg diff foo.c
2395
2395
2396 - compare two historical versions of a directory, with rename info::
2396 - compare two historical versions of a directory, with rename info::
2397
2397
2398 hg diff --git -r 1.0:1.2 lib/
2398 hg diff --git -r 1.0:1.2 lib/
2399
2399
2400 - get change stats relative to the last change on some date::
2400 - get change stats relative to the last change on some date::
2401
2401
2402 hg diff --stat -r "date('may 2')"
2402 hg diff --stat -r "date('may 2')"
2403
2403
2404 - diff all newly-added files that contain a keyword::
2404 - diff all newly-added files that contain a keyword::
2405
2405
2406 hg diff "set:added() and grep(GNU)"
2406 hg diff "set:added() and grep(GNU)"
2407
2407
2408 - compare a revision and its parents::
2408 - compare a revision and its parents::
2409
2409
2410 hg diff -c 9353 # compare against first parent
2410 hg diff -c 9353 # compare against first parent
2411 hg diff -r 9353^:9353 # same using revset syntax
2411 hg diff -r 9353^:9353 # same using revset syntax
2412 hg diff -r 9353^2:9353 # compare against the second parent
2412 hg diff -r 9353^2:9353 # compare against the second parent
2413
2413
2414 Returns 0 on success.
2414 Returns 0 on success.
2415 """
2415 """
2416
2416
2417 revs = opts.get('rev')
2417 revs = opts.get('rev')
2418 change = opts.get('change')
2418 change = opts.get('change')
2419 stat = opts.get('stat')
2419 stat = opts.get('stat')
2420 reverse = opts.get('reverse')
2420 reverse = opts.get('reverse')
2421
2421
2422 if revs and change:
2422 if revs and change:
2423 msg = _('cannot specify --rev and --change at the same time')
2423 msg = _('cannot specify --rev and --change at the same time')
2424 raise util.Abort(msg)
2424 raise util.Abort(msg)
2425 elif change:
2425 elif change:
2426 node2 = scmutil.revsingle(repo, change, None).node()
2426 node2 = scmutil.revsingle(repo, change, None).node()
2427 node1 = repo[node2].p1().node()
2427 node1 = repo[node2].p1().node()
2428 else:
2428 else:
2429 node1, node2 = scmutil.revpair(repo, revs)
2429 node1, node2 = scmutil.revpair(repo, revs)
2430
2430
2431 if reverse:
2431 if reverse:
2432 node1, node2 = node2, node1
2432 node1, node2 = node2, node1
2433
2433
2434 diffopts = patch.diffopts(ui, opts)
2434 diffopts = patch.diffopts(ui, opts)
2435 m = scmutil.match(repo[node2], pats, opts)
2435 m = scmutil.match(repo[node2], pats, opts)
2436 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2436 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2437 listsubrepos=opts.get('subrepos'))
2437 listsubrepos=opts.get('subrepos'))
2438
2438
2439 @command('^export',
2439 @command('^export',
2440 [('o', 'output', '',
2440 [('o', 'output', '',
2441 _('print output to file with formatted name'), _('FORMAT')),
2441 _('print output to file with formatted name'), _('FORMAT')),
2442 ('', 'switch-parent', None, _('diff against the second parent')),
2442 ('', 'switch-parent', None, _('diff against the second parent')),
2443 ('r', 'rev', [], _('revisions to export'), _('REV')),
2443 ('r', 'rev', [], _('revisions to export'), _('REV')),
2444 ] + diffopts,
2444 ] + diffopts,
2445 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2445 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2446 def export(ui, repo, *changesets, **opts):
2446 def export(ui, repo, *changesets, **opts):
2447 """dump the header and diffs for one or more changesets
2447 """dump the header and diffs for one or more changesets
2448
2448
2449 Print the changeset header and diffs for one or more revisions.
2449 Print the changeset header and diffs for one or more revisions.
2450
2450
2451 The information shown in the changeset header is: author, date,
2451 The information shown in the changeset header is: author, date,
2452 branch name (if non-default), changeset hash, parent(s) and commit
2452 branch name (if non-default), changeset hash, parent(s) and commit
2453 comment.
2453 comment.
2454
2454
2455 .. note::
2455 .. note::
2456 export may generate unexpected diff output for merge
2456 export may generate unexpected diff output for merge
2457 changesets, as it will compare the merge changeset against its
2457 changesets, as it will compare the merge changeset against its
2458 first parent only.
2458 first parent only.
2459
2459
2460 Output may be to a file, in which case the name of the file is
2460 Output may be to a file, in which case the name of the file is
2461 given using a format string. The formatting rules are as follows:
2461 given using a format string. The formatting rules are as follows:
2462
2462
2463 :``%%``: literal "%" character
2463 :``%%``: literal "%" character
2464 :``%H``: changeset hash (40 hexadecimal digits)
2464 :``%H``: changeset hash (40 hexadecimal digits)
2465 :``%N``: number of patches being generated
2465 :``%N``: number of patches being generated
2466 :``%R``: changeset revision number
2466 :``%R``: changeset revision number
2467 :``%b``: basename of the exporting repository
2467 :``%b``: basename of the exporting repository
2468 :``%h``: short-form changeset hash (12 hexadecimal digits)
2468 :``%h``: short-form changeset hash (12 hexadecimal digits)
2469 :``%m``: first line of the commit message (only alphanumeric characters)
2469 :``%m``: first line of the commit message (only alphanumeric characters)
2470 :``%n``: zero-padded sequence number, starting at 1
2470 :``%n``: zero-padded sequence number, starting at 1
2471 :``%r``: zero-padded changeset revision number
2471 :``%r``: zero-padded changeset revision number
2472
2472
2473 Without the -a/--text option, export will avoid generating diffs
2473 Without the -a/--text option, export will avoid generating diffs
2474 of files it detects as binary. With -a, export will generate a
2474 of files it detects as binary. With -a, export will generate a
2475 diff anyway, probably with undesirable results.
2475 diff anyway, probably with undesirable results.
2476
2476
2477 Use the -g/--git option to generate diffs in the git extended diff
2477 Use the -g/--git option to generate diffs in the git extended diff
2478 format. See :hg:`help diffs` for more information.
2478 format. See :hg:`help diffs` for more information.
2479
2479
2480 With the --switch-parent option, the diff will be against the
2480 With the --switch-parent option, the diff will be against the
2481 second parent. It can be useful to review a merge.
2481 second parent. It can be useful to review a merge.
2482
2482
2483 .. container:: verbose
2483 .. container:: verbose
2484
2484
2485 Examples:
2485 Examples:
2486
2486
2487 - use export and import to transplant a bugfix to the current
2487 - use export and import to transplant a bugfix to the current
2488 branch::
2488 branch::
2489
2489
2490 hg export -r 9353 | hg import -
2490 hg export -r 9353 | hg import -
2491
2491
2492 - export all the changesets between two revisions to a file with
2492 - export all the changesets between two revisions to a file with
2493 rename information::
2493 rename information::
2494
2494
2495 hg export --git -r 123:150 > changes.txt
2495 hg export --git -r 123:150 > changes.txt
2496
2496
2497 - split outgoing changes into a series of patches with
2497 - split outgoing changes into a series of patches with
2498 descriptive names::
2498 descriptive names::
2499
2499
2500 hg export -r "outgoing()" -o "%n-%m.patch"
2500 hg export -r "outgoing()" -o "%n-%m.patch"
2501
2501
2502 Returns 0 on success.
2502 Returns 0 on success.
2503 """
2503 """
2504 changesets += tuple(opts.get('rev', []))
2504 changesets += tuple(opts.get('rev', []))
2505 revs = scmutil.revrange(repo, changesets)
2505 revs = scmutil.revrange(repo, changesets)
2506 if not revs:
2506 if not revs:
2507 raise util.Abort(_("export requires at least one changeset"))
2507 raise util.Abort(_("export requires at least one changeset"))
2508 if len(revs) > 1:
2508 if len(revs) > 1:
2509 ui.note(_('exporting patches:\n'))
2509 ui.note(_('exporting patches:\n'))
2510 else:
2510 else:
2511 ui.note(_('exporting patch:\n'))
2511 ui.note(_('exporting patch:\n'))
2512 cmdutil.export(repo, revs, template=opts.get('output'),
2512 cmdutil.export(repo, revs, template=opts.get('output'),
2513 switch_parent=opts.get('switch_parent'),
2513 switch_parent=opts.get('switch_parent'),
2514 opts=patch.diffopts(ui, opts))
2514 opts=patch.diffopts(ui, opts))
2515
2515
2516 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2516 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2517 def forget(ui, repo, *pats, **opts):
2517 def forget(ui, repo, *pats, **opts):
2518 """forget the specified files on the next commit
2518 """forget the specified files on the next commit
2519
2519
2520 Mark the specified files so they will no longer be tracked
2520 Mark the specified files so they will no longer be tracked
2521 after the next commit.
2521 after the next commit.
2522
2522
2523 This only removes files from the current branch, not from the
2523 This only removes files from the current branch, not from the
2524 entire project history, and it does not delete them from the
2524 entire project history, and it does not delete them from the
2525 working directory.
2525 working directory.
2526
2526
2527 To undo a forget before the next commit, see :hg:`add`.
2527 To undo a forget before the next commit, see :hg:`add`.
2528
2528
2529 .. container:: verbose
2529 .. container:: verbose
2530
2530
2531 Examples:
2531 Examples:
2532
2532
2533 - forget newly-added binary files::
2533 - forget newly-added binary files::
2534
2534
2535 hg forget "set:added() and binary()"
2535 hg forget "set:added() and binary()"
2536
2536
2537 - forget files that would be excluded by .hgignore::
2537 - forget files that would be excluded by .hgignore::
2538
2538
2539 hg forget "set:hgignore()"
2539 hg forget "set:hgignore()"
2540
2540
2541 Returns 0 on success.
2541 Returns 0 on success.
2542 """
2542 """
2543
2543
2544 if not pats:
2544 if not pats:
2545 raise util.Abort(_('no files specified'))
2545 raise util.Abort(_('no files specified'))
2546
2546
2547 m = scmutil.match(repo[None], pats, opts)
2547 m = scmutil.match(repo[None], pats, opts)
2548 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2548 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2549 return rejected and 1 or 0
2549 return rejected and 1 or 0
2550
2550
2551 @command(
2551 @command(
2552 'graft',
2552 'graft',
2553 [('c', 'continue', False, _('resume interrupted graft')),
2553 [('c', 'continue', False, _('resume interrupted graft')),
2554 ('e', 'edit', False, _('invoke editor on commit messages')),
2554 ('e', 'edit', False, _('invoke editor on commit messages')),
2555 ('D', 'currentdate', False,
2555 ('D', 'currentdate', False,
2556 _('record the current date as commit date')),
2556 _('record the current date as commit date')),
2557 ('U', 'currentuser', False,
2557 ('U', 'currentuser', False,
2558 _('record the current user as committer'), _('DATE'))]
2558 _('record the current user as committer'), _('DATE'))]
2559 + commitopts2 + mergetoolopts + dryrunopts,
2559 + commitopts2 + mergetoolopts + dryrunopts,
2560 _('[OPTION]... REVISION...'))
2560 _('[OPTION]... REVISION...'))
2561 def graft(ui, repo, *revs, **opts):
2561 def graft(ui, repo, *revs, **opts):
2562 '''copy changes from other branches onto the current branch
2562 '''copy changes from other branches onto the current branch
2563
2563
2564 This command uses Mercurial's merge logic to copy individual
2564 This command uses Mercurial's merge logic to copy individual
2565 changes from other branches without merging branches in the
2565 changes from other branches without merging branches in the
2566 history graph. This is sometimes known as 'backporting' or
2566 history graph. This is sometimes known as 'backporting' or
2567 'cherry-picking'. By default, graft will copy user, date, and
2567 'cherry-picking'. By default, graft will copy user, date, and
2568 description from the source changesets.
2568 description from the source changesets.
2569
2569
2570 Changesets that are ancestors of the current revision, that have
2570 Changesets that are ancestors of the current revision, that have
2571 already been grafted, or that are merges will be skipped.
2571 already been grafted, or that are merges will be skipped.
2572
2572
2573 If a graft merge results in conflicts, the graft process is
2573 If a graft merge results in conflicts, the graft process is
2574 interrupted so that the current merge can be manually resolved.
2574 interrupted so that the current merge can be manually resolved.
2575 Once all conflicts are addressed, the graft process can be
2575 Once all conflicts are addressed, the graft process can be
2576 continued with the -c/--continue option.
2576 continued with the -c/--continue option.
2577
2577
2578 .. note::
2578 .. note::
2579 The -c/--continue option does not reapply earlier options.
2579 The -c/--continue option does not reapply earlier options.
2580
2580
2581 .. container:: verbose
2581 .. container:: verbose
2582
2582
2583 Examples:
2583 Examples:
2584
2584
2585 - copy a single change to the stable branch and edit its description::
2585 - copy a single change to the stable branch and edit its description::
2586
2586
2587 hg update stable
2587 hg update stable
2588 hg graft --edit 9393
2588 hg graft --edit 9393
2589
2589
2590 - graft a range of changesets with one exception, updating dates::
2590 - graft a range of changesets with one exception, updating dates::
2591
2591
2592 hg graft -D "2085::2093 and not 2091"
2592 hg graft -D "2085::2093 and not 2091"
2593
2593
2594 - continue a graft after resolving conflicts::
2594 - continue a graft after resolving conflicts::
2595
2595
2596 hg graft -c
2596 hg graft -c
2597
2597
2598 - show the source of a grafted changeset::
2598 - show the source of a grafted changeset::
2599
2599
2600 hg log --debug -r tip
2600 hg log --debug -r tip
2601
2601
2602 Returns 0 on successful completion.
2602 Returns 0 on successful completion.
2603 '''
2603 '''
2604
2604
2605 if not opts.get('user') and opts.get('currentuser'):
2605 if not opts.get('user') and opts.get('currentuser'):
2606 opts['user'] = ui.username()
2606 opts['user'] = ui.username()
2607 if not opts.get('date') and opts.get('currentdate'):
2607 if not opts.get('date') and opts.get('currentdate'):
2608 opts['date'] = "%d %d" % util.makedate()
2608 opts['date'] = "%d %d" % util.makedate()
2609
2609
2610 editor = None
2610 editor = None
2611 if opts.get('edit'):
2611 if opts.get('edit'):
2612 editor = cmdutil.commitforceeditor
2612 editor = cmdutil.commitforceeditor
2613
2613
2614 cont = False
2614 cont = False
2615 if opts['continue']:
2615 if opts['continue']:
2616 cont = True
2616 cont = True
2617 if revs:
2617 if revs:
2618 raise util.Abort(_("can't specify --continue and revisions"))
2618 raise util.Abort(_("can't specify --continue and revisions"))
2619 # read in unfinished revisions
2619 # read in unfinished revisions
2620 try:
2620 try:
2621 nodes = repo.opener.read('graftstate').splitlines()
2621 nodes = repo.opener.read('graftstate').splitlines()
2622 revs = [repo[node].rev() for node in nodes]
2622 revs = [repo[node].rev() for node in nodes]
2623 except IOError, inst:
2623 except IOError, inst:
2624 if inst.errno != errno.ENOENT:
2624 if inst.errno != errno.ENOENT:
2625 raise
2625 raise
2626 raise util.Abort(_("no graft state found, can't continue"))
2626 raise util.Abort(_("no graft state found, can't continue"))
2627 else:
2627 else:
2628 cmdutil.bailifchanged(repo)
2628 cmdutil.bailifchanged(repo)
2629 if not revs:
2629 if not revs:
2630 raise util.Abort(_('no revisions specified'))
2630 raise util.Abort(_('no revisions specified'))
2631 revs = scmutil.revrange(repo, revs)
2631 revs = scmutil.revrange(repo, revs)
2632
2632
2633 # check for merges
2633 # check for merges
2634 for rev in repo.revs('%ld and merge()', revs):
2634 for rev in repo.revs('%ld and merge()', revs):
2635 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2635 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2636 revs.remove(rev)
2636 revs.remove(rev)
2637 if not revs:
2637 if not revs:
2638 return -1
2638 return -1
2639
2639
2640 # check for ancestors of dest branch
2640 # check for ancestors of dest branch
2641 for rev in repo.revs('::. and %ld', revs):
2641 for rev in repo.revs('::. and %ld', revs):
2642 ui.warn(_('skipping ancestor revision %s\n') % rev)
2642 ui.warn(_('skipping ancestor revision %s\n') % rev)
2643 revs.remove(rev)
2643 revs.remove(rev)
2644 if not revs:
2644 if not revs:
2645 return -1
2645 return -1
2646
2646
2647 # analyze revs for earlier grafts
2647 # analyze revs for earlier grafts
2648 ids = {}
2648 ids = {}
2649 for ctx in repo.set("%ld", revs):
2649 for ctx in repo.set("%ld", revs):
2650 ids[ctx.hex()] = ctx.rev()
2650 ids[ctx.hex()] = ctx.rev()
2651 n = ctx.extra().get('source')
2651 n = ctx.extra().get('source')
2652 if n:
2652 if n:
2653 ids[n] = ctx.rev()
2653 ids[n] = ctx.rev()
2654
2654
2655 # check ancestors for earlier grafts
2655 # check ancestors for earlier grafts
2656 ui.debug('scanning for duplicate grafts\n')
2656 ui.debug('scanning for duplicate grafts\n')
2657 for ctx in repo.set("::. - ::%ld", revs):
2657 for ctx in repo.set("::. - ::%ld", revs):
2658 n = ctx.extra().get('source')
2658 n = ctx.extra().get('source')
2659 if n in ids:
2659 if n in ids:
2660 r = repo[n].rev()
2660 r = repo[n].rev()
2661 if r in revs:
2661 if r in revs:
2662 ui.warn(_('skipping already grafted revision %s\n') % r)
2662 ui.warn(_('skipping already grafted revision %s\n') % r)
2663 revs.remove(r)
2663 revs.remove(r)
2664 elif ids[n] in revs:
2664 elif ids[n] in revs:
2665 ui.warn(_('skipping already grafted revision %s '
2665 ui.warn(_('skipping already grafted revision %s '
2666 '(same origin %d)\n') % (ids[n], r))
2666 '(same origin %d)\n') % (ids[n], r))
2667 revs.remove(ids[n])
2667 revs.remove(ids[n])
2668 elif ctx.hex() in ids:
2668 elif ctx.hex() in ids:
2669 r = ids[ctx.hex()]
2669 r = ids[ctx.hex()]
2670 ui.warn(_('skipping already grafted revision %s '
2670 ui.warn(_('skipping already grafted revision %s '
2671 '(was grafted from %d)\n') % (r, ctx.rev()))
2671 '(was grafted from %d)\n') % (r, ctx.rev()))
2672 revs.remove(r)
2672 revs.remove(r)
2673 if not revs:
2673 if not revs:
2674 return -1
2674 return -1
2675
2675
2676 wlock = repo.wlock()
2676 wlock = repo.wlock()
2677 try:
2677 try:
2678 for pos, ctx in enumerate(repo.set("%ld", revs)):
2678 for pos, ctx in enumerate(repo.set("%ld", revs)):
2679 current = repo['.']
2679 current = repo['.']
2680
2680
2681 ui.status(_('grafting revision %s\n') % ctx.rev())
2681 ui.status(_('grafting revision %s\n') % ctx.rev())
2682 if opts.get('dry_run'):
2682 if opts.get('dry_run'):
2683 continue
2683 continue
2684
2684
2685 # we don't merge the first commit when continuing
2685 # we don't merge the first commit when continuing
2686 if not cont:
2686 if not cont:
2687 # perform the graft merge with p1(rev) as 'ancestor'
2687 # perform the graft merge with p1(rev) as 'ancestor'
2688 try:
2688 try:
2689 # ui.forcemerge is an internal variable, do not document
2689 # ui.forcemerge is an internal variable, do not document
2690 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2690 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2691 stats = mergemod.update(repo, ctx.node(), True, True, False,
2691 stats = mergemod.update(repo, ctx.node(), True, True, False,
2692 ctx.p1().node())
2692 ctx.p1().node())
2693 finally:
2693 finally:
2694 ui.setconfig('ui', 'forcemerge', '')
2694 ui.setconfig('ui', 'forcemerge', '')
2695 # drop the second merge parent
2695 # drop the second merge parent
2696 repo.dirstate.setparents(current.node(), nullid)
2696 repo.setparents(current.node(), nullid)
2697 repo.dirstate.write()
2697 repo.dirstate.write()
2698 # fix up dirstate for copies and renames
2698 # fix up dirstate for copies and renames
2699 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2699 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2700 # report any conflicts
2700 # report any conflicts
2701 if stats and stats[3] > 0:
2701 if stats and stats[3] > 0:
2702 # write out state for --continue
2702 # write out state for --continue
2703 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2703 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2704 repo.opener.write('graftstate', ''.join(nodelines))
2704 repo.opener.write('graftstate', ''.join(nodelines))
2705 raise util.Abort(
2705 raise util.Abort(
2706 _("unresolved conflicts, can't continue"),
2706 _("unresolved conflicts, can't continue"),
2707 hint=_('use hg resolve and hg graft --continue'))
2707 hint=_('use hg resolve and hg graft --continue'))
2708 else:
2708 else:
2709 cont = False
2709 cont = False
2710
2710
2711 # commit
2711 # commit
2712 source = ctx.extra().get('source')
2712 source = ctx.extra().get('source')
2713 if not source:
2713 if not source:
2714 source = ctx.hex()
2714 source = ctx.hex()
2715 extra = {'source': source}
2715 extra = {'source': source}
2716 user = ctx.user()
2716 user = ctx.user()
2717 if opts.get('user'):
2717 if opts.get('user'):
2718 user = opts['user']
2718 user = opts['user']
2719 date = ctx.date()
2719 date = ctx.date()
2720 if opts.get('date'):
2720 if opts.get('date'):
2721 date = opts['date']
2721 date = opts['date']
2722 repo.commit(text=ctx.description(), user=user,
2722 repo.commit(text=ctx.description(), user=user,
2723 date=date, extra=extra, editor=editor)
2723 date=date, extra=extra, editor=editor)
2724 finally:
2724 finally:
2725 wlock.release()
2725 wlock.release()
2726
2726
2727 # remove state when we complete successfully
2727 # remove state when we complete successfully
2728 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2728 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2729 util.unlinkpath(repo.join('graftstate'))
2729 util.unlinkpath(repo.join('graftstate'))
2730
2730
2731 return 0
2731 return 0
2732
2732
2733 @command('grep',
2733 @command('grep',
2734 [('0', 'print0', None, _('end fields with NUL')),
2734 [('0', 'print0', None, _('end fields with NUL')),
2735 ('', 'all', None, _('print all revisions that match')),
2735 ('', 'all', None, _('print all revisions that match')),
2736 ('a', 'text', None, _('treat all files as text')),
2736 ('a', 'text', None, _('treat all files as text')),
2737 ('f', 'follow', None,
2737 ('f', 'follow', None,
2738 _('follow changeset history,'
2738 _('follow changeset history,'
2739 ' or file history across copies and renames')),
2739 ' or file history across copies and renames')),
2740 ('i', 'ignore-case', None, _('ignore case when matching')),
2740 ('i', 'ignore-case', None, _('ignore case when matching')),
2741 ('l', 'files-with-matches', None,
2741 ('l', 'files-with-matches', None,
2742 _('print only filenames and revisions that match')),
2742 _('print only filenames and revisions that match')),
2743 ('n', 'line-number', None, _('print matching line numbers')),
2743 ('n', 'line-number', None, _('print matching line numbers')),
2744 ('r', 'rev', [],
2744 ('r', 'rev', [],
2745 _('only search files changed within revision range'), _('REV')),
2745 _('only search files changed within revision range'), _('REV')),
2746 ('u', 'user', None, _('list the author (long with -v)')),
2746 ('u', 'user', None, _('list the author (long with -v)')),
2747 ('d', 'date', None, _('list the date (short with -q)')),
2747 ('d', 'date', None, _('list the date (short with -q)')),
2748 ] + walkopts,
2748 ] + walkopts,
2749 _('[OPTION]... PATTERN [FILE]...'))
2749 _('[OPTION]... PATTERN [FILE]...'))
2750 def grep(ui, repo, pattern, *pats, **opts):
2750 def grep(ui, repo, pattern, *pats, **opts):
2751 """search for a pattern in specified files and revisions
2751 """search for a pattern in specified files and revisions
2752
2752
2753 Search revisions of files for a regular expression.
2753 Search revisions of files for a regular expression.
2754
2754
2755 This command behaves differently than Unix grep. It only accepts
2755 This command behaves differently than Unix grep. It only accepts
2756 Python/Perl regexps. It searches repository history, not the
2756 Python/Perl regexps. It searches repository history, not the
2757 working directory. It always prints the revision number in which a
2757 working directory. It always prints the revision number in which a
2758 match appears.
2758 match appears.
2759
2759
2760 By default, grep only prints output for the first revision of a
2760 By default, grep only prints output for the first revision of a
2761 file in which it finds a match. To get it to print every revision
2761 file in which it finds a match. To get it to print every revision
2762 that contains a change in match status ("-" for a match that
2762 that contains a change in match status ("-" for a match that
2763 becomes a non-match, or "+" for a non-match that becomes a match),
2763 becomes a non-match, or "+" for a non-match that becomes a match),
2764 use the --all flag.
2764 use the --all flag.
2765
2765
2766 Returns 0 if a match is found, 1 otherwise.
2766 Returns 0 if a match is found, 1 otherwise.
2767 """
2767 """
2768 reflags = re.M
2768 reflags = re.M
2769 if opts.get('ignore_case'):
2769 if opts.get('ignore_case'):
2770 reflags |= re.I
2770 reflags |= re.I
2771 try:
2771 try:
2772 regexp = re.compile(pattern, reflags)
2772 regexp = re.compile(pattern, reflags)
2773 except re.error, inst:
2773 except re.error, inst:
2774 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2774 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2775 return 1
2775 return 1
2776 sep, eol = ':', '\n'
2776 sep, eol = ':', '\n'
2777 if opts.get('print0'):
2777 if opts.get('print0'):
2778 sep = eol = '\0'
2778 sep = eol = '\0'
2779
2779
2780 getfile = util.lrucachefunc(repo.file)
2780 getfile = util.lrucachefunc(repo.file)
2781
2781
2782 def matchlines(body):
2782 def matchlines(body):
2783 begin = 0
2783 begin = 0
2784 linenum = 0
2784 linenum = 0
2785 while True:
2785 while True:
2786 match = regexp.search(body, begin)
2786 match = regexp.search(body, begin)
2787 if not match:
2787 if not match:
2788 break
2788 break
2789 mstart, mend = match.span()
2789 mstart, mend = match.span()
2790 linenum += body.count('\n', begin, mstart) + 1
2790 linenum += body.count('\n', begin, mstart) + 1
2791 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2791 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2792 begin = body.find('\n', mend) + 1 or len(body) + 1
2792 begin = body.find('\n', mend) + 1 or len(body) + 1
2793 lend = begin - 1
2793 lend = begin - 1
2794 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2794 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2795
2795
2796 class linestate(object):
2796 class linestate(object):
2797 def __init__(self, line, linenum, colstart, colend):
2797 def __init__(self, line, linenum, colstart, colend):
2798 self.line = line
2798 self.line = line
2799 self.linenum = linenum
2799 self.linenum = linenum
2800 self.colstart = colstart
2800 self.colstart = colstart
2801 self.colend = colend
2801 self.colend = colend
2802
2802
2803 def __hash__(self):
2803 def __hash__(self):
2804 return hash((self.linenum, self.line))
2804 return hash((self.linenum, self.line))
2805
2805
2806 def __eq__(self, other):
2806 def __eq__(self, other):
2807 return self.line == other.line
2807 return self.line == other.line
2808
2808
2809 matches = {}
2809 matches = {}
2810 copies = {}
2810 copies = {}
2811 def grepbody(fn, rev, body):
2811 def grepbody(fn, rev, body):
2812 matches[rev].setdefault(fn, [])
2812 matches[rev].setdefault(fn, [])
2813 m = matches[rev][fn]
2813 m = matches[rev][fn]
2814 for lnum, cstart, cend, line in matchlines(body):
2814 for lnum, cstart, cend, line in matchlines(body):
2815 s = linestate(line, lnum, cstart, cend)
2815 s = linestate(line, lnum, cstart, cend)
2816 m.append(s)
2816 m.append(s)
2817
2817
2818 def difflinestates(a, b):
2818 def difflinestates(a, b):
2819 sm = difflib.SequenceMatcher(None, a, b)
2819 sm = difflib.SequenceMatcher(None, a, b)
2820 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2820 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2821 if tag == 'insert':
2821 if tag == 'insert':
2822 for i in xrange(blo, bhi):
2822 for i in xrange(blo, bhi):
2823 yield ('+', b[i])
2823 yield ('+', b[i])
2824 elif tag == 'delete':
2824 elif tag == 'delete':
2825 for i in xrange(alo, ahi):
2825 for i in xrange(alo, ahi):
2826 yield ('-', a[i])
2826 yield ('-', a[i])
2827 elif tag == 'replace':
2827 elif tag == 'replace':
2828 for i in xrange(alo, ahi):
2828 for i in xrange(alo, ahi):
2829 yield ('-', a[i])
2829 yield ('-', a[i])
2830 for i in xrange(blo, bhi):
2830 for i in xrange(blo, bhi):
2831 yield ('+', b[i])
2831 yield ('+', b[i])
2832
2832
2833 def display(fn, ctx, pstates, states):
2833 def display(fn, ctx, pstates, states):
2834 rev = ctx.rev()
2834 rev = ctx.rev()
2835 datefunc = ui.quiet and util.shortdate or util.datestr
2835 datefunc = ui.quiet and util.shortdate or util.datestr
2836 found = False
2836 found = False
2837 filerevmatches = {}
2837 filerevmatches = {}
2838 def binary():
2838 def binary():
2839 flog = getfile(fn)
2839 flog = getfile(fn)
2840 return util.binary(flog.read(ctx.filenode(fn)))
2840 return util.binary(flog.read(ctx.filenode(fn)))
2841
2841
2842 if opts.get('all'):
2842 if opts.get('all'):
2843 iter = difflinestates(pstates, states)
2843 iter = difflinestates(pstates, states)
2844 else:
2844 else:
2845 iter = [('', l) for l in states]
2845 iter = [('', l) for l in states]
2846 for change, l in iter:
2846 for change, l in iter:
2847 cols = [fn, str(rev)]
2847 cols = [fn, str(rev)]
2848 before, match, after = None, None, None
2848 before, match, after = None, None, None
2849 if opts.get('line_number'):
2849 if opts.get('line_number'):
2850 cols.append(str(l.linenum))
2850 cols.append(str(l.linenum))
2851 if opts.get('all'):
2851 if opts.get('all'):
2852 cols.append(change)
2852 cols.append(change)
2853 if opts.get('user'):
2853 if opts.get('user'):
2854 cols.append(ui.shortuser(ctx.user()))
2854 cols.append(ui.shortuser(ctx.user()))
2855 if opts.get('date'):
2855 if opts.get('date'):
2856 cols.append(datefunc(ctx.date()))
2856 cols.append(datefunc(ctx.date()))
2857 if opts.get('files_with_matches'):
2857 if opts.get('files_with_matches'):
2858 c = (fn, rev)
2858 c = (fn, rev)
2859 if c in filerevmatches:
2859 if c in filerevmatches:
2860 continue
2860 continue
2861 filerevmatches[c] = 1
2861 filerevmatches[c] = 1
2862 else:
2862 else:
2863 before = l.line[:l.colstart]
2863 before = l.line[:l.colstart]
2864 match = l.line[l.colstart:l.colend]
2864 match = l.line[l.colstart:l.colend]
2865 after = l.line[l.colend:]
2865 after = l.line[l.colend:]
2866 ui.write(sep.join(cols))
2866 ui.write(sep.join(cols))
2867 if before is not None:
2867 if before is not None:
2868 if not opts.get('text') and binary():
2868 if not opts.get('text') and binary():
2869 ui.write(sep + " Binary file matches")
2869 ui.write(sep + " Binary file matches")
2870 else:
2870 else:
2871 ui.write(sep + before)
2871 ui.write(sep + before)
2872 ui.write(match, label='grep.match')
2872 ui.write(match, label='grep.match')
2873 ui.write(after)
2873 ui.write(after)
2874 ui.write(eol)
2874 ui.write(eol)
2875 found = True
2875 found = True
2876 return found
2876 return found
2877
2877
2878 skip = {}
2878 skip = {}
2879 revfiles = {}
2879 revfiles = {}
2880 matchfn = scmutil.match(repo[None], pats, opts)
2880 matchfn = scmutil.match(repo[None], pats, opts)
2881 found = False
2881 found = False
2882 follow = opts.get('follow')
2882 follow = opts.get('follow')
2883
2883
2884 def prep(ctx, fns):
2884 def prep(ctx, fns):
2885 rev = ctx.rev()
2885 rev = ctx.rev()
2886 pctx = ctx.p1()
2886 pctx = ctx.p1()
2887 parent = pctx.rev()
2887 parent = pctx.rev()
2888 matches.setdefault(rev, {})
2888 matches.setdefault(rev, {})
2889 matches.setdefault(parent, {})
2889 matches.setdefault(parent, {})
2890 files = revfiles.setdefault(rev, [])
2890 files = revfiles.setdefault(rev, [])
2891 for fn in fns:
2891 for fn in fns:
2892 flog = getfile(fn)
2892 flog = getfile(fn)
2893 try:
2893 try:
2894 fnode = ctx.filenode(fn)
2894 fnode = ctx.filenode(fn)
2895 except error.LookupError:
2895 except error.LookupError:
2896 continue
2896 continue
2897
2897
2898 copied = flog.renamed(fnode)
2898 copied = flog.renamed(fnode)
2899 copy = follow and copied and copied[0]
2899 copy = follow and copied and copied[0]
2900 if copy:
2900 if copy:
2901 copies.setdefault(rev, {})[fn] = copy
2901 copies.setdefault(rev, {})[fn] = copy
2902 if fn in skip:
2902 if fn in skip:
2903 if copy:
2903 if copy:
2904 skip[copy] = True
2904 skip[copy] = True
2905 continue
2905 continue
2906 files.append(fn)
2906 files.append(fn)
2907
2907
2908 if fn not in matches[rev]:
2908 if fn not in matches[rev]:
2909 grepbody(fn, rev, flog.read(fnode))
2909 grepbody(fn, rev, flog.read(fnode))
2910
2910
2911 pfn = copy or fn
2911 pfn = copy or fn
2912 if pfn not in matches[parent]:
2912 if pfn not in matches[parent]:
2913 try:
2913 try:
2914 fnode = pctx.filenode(pfn)
2914 fnode = pctx.filenode(pfn)
2915 grepbody(pfn, parent, flog.read(fnode))
2915 grepbody(pfn, parent, flog.read(fnode))
2916 except error.LookupError:
2916 except error.LookupError:
2917 pass
2917 pass
2918
2918
2919 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2919 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2920 rev = ctx.rev()
2920 rev = ctx.rev()
2921 parent = ctx.p1().rev()
2921 parent = ctx.p1().rev()
2922 for fn in sorted(revfiles.get(rev, [])):
2922 for fn in sorted(revfiles.get(rev, [])):
2923 states = matches[rev][fn]
2923 states = matches[rev][fn]
2924 copy = copies.get(rev, {}).get(fn)
2924 copy = copies.get(rev, {}).get(fn)
2925 if fn in skip:
2925 if fn in skip:
2926 if copy:
2926 if copy:
2927 skip[copy] = True
2927 skip[copy] = True
2928 continue
2928 continue
2929 pstates = matches.get(parent, {}).get(copy or fn, [])
2929 pstates = matches.get(parent, {}).get(copy or fn, [])
2930 if pstates or states:
2930 if pstates or states:
2931 r = display(fn, ctx, pstates, states)
2931 r = display(fn, ctx, pstates, states)
2932 found = found or r
2932 found = found or r
2933 if r and not opts.get('all'):
2933 if r and not opts.get('all'):
2934 skip[fn] = True
2934 skip[fn] = True
2935 if copy:
2935 if copy:
2936 skip[copy] = True
2936 skip[copy] = True
2937 del matches[rev]
2937 del matches[rev]
2938 del revfiles[rev]
2938 del revfiles[rev]
2939
2939
2940 return not found
2940 return not found
2941
2941
2942 @command('heads',
2942 @command('heads',
2943 [('r', 'rev', '',
2943 [('r', 'rev', '',
2944 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2944 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2945 ('t', 'topo', False, _('show topological heads only')),
2945 ('t', 'topo', False, _('show topological heads only')),
2946 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2946 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2947 ('c', 'closed', False, _('show normal and closed branch heads')),
2947 ('c', 'closed', False, _('show normal and closed branch heads')),
2948 ] + templateopts,
2948 ] + templateopts,
2949 _('[-ac] [-r STARTREV] [REV]...'))
2949 _('[-ac] [-r STARTREV] [REV]...'))
2950 def heads(ui, repo, *branchrevs, **opts):
2950 def heads(ui, repo, *branchrevs, **opts):
2951 """show current repository heads or show branch heads
2951 """show current repository heads or show branch heads
2952
2952
2953 With no arguments, show all repository branch heads.
2953 With no arguments, show all repository branch heads.
2954
2954
2955 Repository "heads" are changesets with no child changesets. They are
2955 Repository "heads" are changesets with no child changesets. They are
2956 where development generally takes place and are the usual targets
2956 where development generally takes place and are the usual targets
2957 for update and merge operations. Branch heads are changesets that have
2957 for update and merge operations. Branch heads are changesets that have
2958 no child changeset on the same branch.
2958 no child changeset on the same branch.
2959
2959
2960 If one or more REVs are given, only branch heads on the branches
2960 If one or more REVs are given, only branch heads on the branches
2961 associated with the specified changesets are shown. This means
2961 associated with the specified changesets are shown. This means
2962 that you can use :hg:`heads foo` to see the heads on a branch
2962 that you can use :hg:`heads foo` to see the heads on a branch
2963 named ``foo``.
2963 named ``foo``.
2964
2964
2965 If -c/--closed is specified, also show branch heads marked closed
2965 If -c/--closed is specified, also show branch heads marked closed
2966 (see :hg:`commit --close-branch`).
2966 (see :hg:`commit --close-branch`).
2967
2967
2968 If STARTREV is specified, only those heads that are descendants of
2968 If STARTREV is specified, only those heads that are descendants of
2969 STARTREV will be displayed.
2969 STARTREV will be displayed.
2970
2970
2971 If -t/--topo is specified, named branch mechanics will be ignored and only
2971 If -t/--topo is specified, named branch mechanics will be ignored and only
2972 changesets without children will be shown.
2972 changesets without children will be shown.
2973
2973
2974 Returns 0 if matching heads are found, 1 if not.
2974 Returns 0 if matching heads are found, 1 if not.
2975 """
2975 """
2976
2976
2977 start = None
2977 start = None
2978 if 'rev' in opts:
2978 if 'rev' in opts:
2979 start = scmutil.revsingle(repo, opts['rev'], None).node()
2979 start = scmutil.revsingle(repo, opts['rev'], None).node()
2980
2980
2981 if opts.get('topo'):
2981 if opts.get('topo'):
2982 heads = [repo[h] for h in repo.heads(start)]
2982 heads = [repo[h] for h in repo.heads(start)]
2983 else:
2983 else:
2984 heads = []
2984 heads = []
2985 for branch in repo.branchmap():
2985 for branch in repo.branchmap():
2986 heads += repo.branchheads(branch, start, opts.get('closed'))
2986 heads += repo.branchheads(branch, start, opts.get('closed'))
2987 heads = [repo[h] for h in heads]
2987 heads = [repo[h] for h in heads]
2988
2988
2989 if branchrevs:
2989 if branchrevs:
2990 branches = set(repo[br].branch() for br in branchrevs)
2990 branches = set(repo[br].branch() for br in branchrevs)
2991 heads = [h for h in heads if h.branch() in branches]
2991 heads = [h for h in heads if h.branch() in branches]
2992
2992
2993 if opts.get('active') and branchrevs:
2993 if opts.get('active') and branchrevs:
2994 dagheads = repo.heads(start)
2994 dagheads = repo.heads(start)
2995 heads = [h for h in heads if h.node() in dagheads]
2995 heads = [h for h in heads if h.node() in dagheads]
2996
2996
2997 if branchrevs:
2997 if branchrevs:
2998 haveheads = set(h.branch() for h in heads)
2998 haveheads = set(h.branch() for h in heads)
2999 if branches - haveheads:
2999 if branches - haveheads:
3000 headless = ', '.join(b for b in branches - haveheads)
3000 headless = ', '.join(b for b in branches - haveheads)
3001 msg = _('no open branch heads found on branches %s')
3001 msg = _('no open branch heads found on branches %s')
3002 if opts.get('rev'):
3002 if opts.get('rev'):
3003 msg += _(' (started at %s)') % opts['rev']
3003 msg += _(' (started at %s)') % opts['rev']
3004 ui.warn((msg + '\n') % headless)
3004 ui.warn((msg + '\n') % headless)
3005
3005
3006 if not heads:
3006 if not heads:
3007 return 1
3007 return 1
3008
3008
3009 heads = sorted(heads, key=lambda x: -x.rev())
3009 heads = sorted(heads, key=lambda x: -x.rev())
3010 displayer = cmdutil.show_changeset(ui, repo, opts)
3010 displayer = cmdutil.show_changeset(ui, repo, opts)
3011 for ctx in heads:
3011 for ctx in heads:
3012 displayer.show(ctx)
3012 displayer.show(ctx)
3013 displayer.close()
3013 displayer.close()
3014
3014
3015 @command('help',
3015 @command('help',
3016 [('e', 'extension', None, _('show only help for extensions')),
3016 [('e', 'extension', None, _('show only help for extensions')),
3017 ('c', 'command', None, _('show only help for commands'))],
3017 ('c', 'command', None, _('show only help for commands'))],
3018 _('[-ec] [TOPIC]'))
3018 _('[-ec] [TOPIC]'))
3019 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3019 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3020 """show help for a given topic or a help overview
3020 """show help for a given topic or a help overview
3021
3021
3022 With no arguments, print a list of commands with short help messages.
3022 With no arguments, print a list of commands with short help messages.
3023
3023
3024 Given a topic, extension, or command name, print help for that
3024 Given a topic, extension, or command name, print help for that
3025 topic.
3025 topic.
3026
3026
3027 Returns 0 if successful.
3027 Returns 0 if successful.
3028 """
3028 """
3029
3029
3030 textwidth = min(ui.termwidth(), 80) - 2
3030 textwidth = min(ui.termwidth(), 80) - 2
3031
3031
3032 def optrst(options):
3032 def optrst(options):
3033 data = []
3033 data = []
3034 multioccur = False
3034 multioccur = False
3035 for option in options:
3035 for option in options:
3036 if len(option) == 5:
3036 if len(option) == 5:
3037 shortopt, longopt, default, desc, optlabel = option
3037 shortopt, longopt, default, desc, optlabel = option
3038 else:
3038 else:
3039 shortopt, longopt, default, desc = option
3039 shortopt, longopt, default, desc = option
3040 optlabel = _("VALUE") # default label
3040 optlabel = _("VALUE") # default label
3041
3041
3042 if _("DEPRECATED") in desc and not ui.verbose:
3042 if _("DEPRECATED") in desc and not ui.verbose:
3043 continue
3043 continue
3044
3044
3045 so = ''
3045 so = ''
3046 if shortopt:
3046 if shortopt:
3047 so = '-' + shortopt
3047 so = '-' + shortopt
3048 lo = '--' + longopt
3048 lo = '--' + longopt
3049 if default:
3049 if default:
3050 desc += _(" (default: %s)") % default
3050 desc += _(" (default: %s)") % default
3051
3051
3052 if isinstance(default, list):
3052 if isinstance(default, list):
3053 lo += " %s [+]" % optlabel
3053 lo += " %s [+]" % optlabel
3054 multioccur = True
3054 multioccur = True
3055 elif (default is not None) and not isinstance(default, bool):
3055 elif (default is not None) and not isinstance(default, bool):
3056 lo += " %s" % optlabel
3056 lo += " %s" % optlabel
3057
3057
3058 data.append((so, lo, desc))
3058 data.append((so, lo, desc))
3059
3059
3060 rst = minirst.maketable(data, 1)
3060 rst = minirst.maketable(data, 1)
3061
3061
3062 if multioccur:
3062 if multioccur:
3063 rst += _("\n[+] marked option can be specified multiple times\n")
3063 rst += _("\n[+] marked option can be specified multiple times\n")
3064
3064
3065 return rst
3065 return rst
3066
3066
3067 # list all option lists
3067 # list all option lists
3068 def opttext(optlist, width):
3068 def opttext(optlist, width):
3069 rst = ''
3069 rst = ''
3070 if not optlist:
3070 if not optlist:
3071 return ''
3071 return ''
3072
3072
3073 for title, options in optlist:
3073 for title, options in optlist:
3074 rst += '\n%s\n' % title
3074 rst += '\n%s\n' % title
3075 if options:
3075 if options:
3076 rst += "\n"
3076 rst += "\n"
3077 rst += optrst(options)
3077 rst += optrst(options)
3078 rst += '\n'
3078 rst += '\n'
3079
3079
3080 return '\n' + minirst.format(rst, width)
3080 return '\n' + minirst.format(rst, width)
3081
3081
3082 def addglobalopts(optlist, aliases):
3082 def addglobalopts(optlist, aliases):
3083 if ui.quiet:
3083 if ui.quiet:
3084 return []
3084 return []
3085
3085
3086 if ui.verbose:
3086 if ui.verbose:
3087 optlist.append((_("global options:"), globalopts))
3087 optlist.append((_("global options:"), globalopts))
3088 if name == 'shortlist':
3088 if name == 'shortlist':
3089 optlist.append((_('use "hg help" for the full list '
3089 optlist.append((_('use "hg help" for the full list '
3090 'of commands'), ()))
3090 'of commands'), ()))
3091 else:
3091 else:
3092 if name == 'shortlist':
3092 if name == 'shortlist':
3093 msg = _('use "hg help" for the full list of commands '
3093 msg = _('use "hg help" for the full list of commands '
3094 'or "hg -v" for details')
3094 'or "hg -v" for details')
3095 elif name and not full:
3095 elif name and not full:
3096 msg = _('use "hg help %s" to show the full help text') % name
3096 msg = _('use "hg help %s" to show the full help text') % name
3097 elif aliases:
3097 elif aliases:
3098 msg = _('use "hg -v help%s" to show builtin aliases and '
3098 msg = _('use "hg -v help%s" to show builtin aliases and '
3099 'global options') % (name and " " + name or "")
3099 'global options') % (name and " " + name or "")
3100 else:
3100 else:
3101 msg = _('use "hg -v help %s" to show more info') % name
3101 msg = _('use "hg -v help %s" to show more info') % name
3102 optlist.append((msg, ()))
3102 optlist.append((msg, ()))
3103
3103
3104 def helpcmd(name):
3104 def helpcmd(name):
3105 try:
3105 try:
3106 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3106 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3107 except error.AmbiguousCommand, inst:
3107 except error.AmbiguousCommand, inst:
3108 # py3k fix: except vars can't be used outside the scope of the
3108 # py3k fix: except vars can't be used outside the scope of the
3109 # except block, nor can be used inside a lambda. python issue4617
3109 # except block, nor can be used inside a lambda. python issue4617
3110 prefix = inst.args[0]
3110 prefix = inst.args[0]
3111 select = lambda c: c.lstrip('^').startswith(prefix)
3111 select = lambda c: c.lstrip('^').startswith(prefix)
3112 helplist(select)
3112 helplist(select)
3113 return
3113 return
3114
3114
3115 # check if it's an invalid alias and display its error if it is
3115 # check if it's an invalid alias and display its error if it is
3116 if getattr(entry[0], 'badalias', False):
3116 if getattr(entry[0], 'badalias', False):
3117 if not unknowncmd:
3117 if not unknowncmd:
3118 entry[0](ui)
3118 entry[0](ui)
3119 return
3119 return
3120
3120
3121 rst = ""
3121 rst = ""
3122
3122
3123 # synopsis
3123 # synopsis
3124 if len(entry) > 2:
3124 if len(entry) > 2:
3125 if entry[2].startswith('hg'):
3125 if entry[2].startswith('hg'):
3126 rst += "%s\n" % entry[2]
3126 rst += "%s\n" % entry[2]
3127 else:
3127 else:
3128 rst += 'hg %s %s\n' % (aliases[0], entry[2])
3128 rst += 'hg %s %s\n' % (aliases[0], entry[2])
3129 else:
3129 else:
3130 rst += 'hg %s\n' % aliases[0]
3130 rst += 'hg %s\n' % aliases[0]
3131
3131
3132 # aliases
3132 # aliases
3133 if full and not ui.quiet and len(aliases) > 1:
3133 if full and not ui.quiet and len(aliases) > 1:
3134 rst += _("\naliases: %s\n") % ', '.join(aliases[1:])
3134 rst += _("\naliases: %s\n") % ', '.join(aliases[1:])
3135
3135
3136 # description
3136 # description
3137 doc = gettext(entry[0].__doc__)
3137 doc = gettext(entry[0].__doc__)
3138 if not doc:
3138 if not doc:
3139 doc = _("(no help text available)")
3139 doc = _("(no help text available)")
3140 if util.safehasattr(entry[0], 'definition'): # aliased command
3140 if util.safehasattr(entry[0], 'definition'): # aliased command
3141 if entry[0].definition.startswith('!'): # shell alias
3141 if entry[0].definition.startswith('!'): # shell alias
3142 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3142 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3143 else:
3143 else:
3144 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3144 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3145 if ui.quiet or not full:
3145 if ui.quiet or not full:
3146 doc = doc.splitlines()[0]
3146 doc = doc.splitlines()[0]
3147 rst += "\n" + doc + "\n"
3147 rst += "\n" + doc + "\n"
3148
3148
3149 # check if this command shadows a non-trivial (multi-line)
3149 # check if this command shadows a non-trivial (multi-line)
3150 # extension help text
3150 # extension help text
3151 try:
3151 try:
3152 mod = extensions.find(name)
3152 mod = extensions.find(name)
3153 doc = gettext(mod.__doc__) or ''
3153 doc = gettext(mod.__doc__) or ''
3154 if '\n' in doc.strip():
3154 if '\n' in doc.strip():
3155 msg = _('use "hg help -e %s" to show help for '
3155 msg = _('use "hg help -e %s" to show help for '
3156 'the %s extension') % (name, name)
3156 'the %s extension') % (name, name)
3157 rst += '\n%s\n' % msg
3157 rst += '\n%s\n' % msg
3158 except KeyError:
3158 except KeyError:
3159 pass
3159 pass
3160
3160
3161 # options
3161 # options
3162 if not ui.quiet and entry[1]:
3162 if not ui.quiet and entry[1]:
3163 rst += '\n'
3163 rst += '\n'
3164 rst += _("options:")
3164 rst += _("options:")
3165 rst += '\n\n'
3165 rst += '\n\n'
3166 rst += optrst(entry[1])
3166 rst += optrst(entry[1])
3167
3167
3168 if ui.verbose:
3168 if ui.verbose:
3169 rst += '\n'
3169 rst += '\n'
3170 rst += _("global options:")
3170 rst += _("global options:")
3171 rst += '\n\n'
3171 rst += '\n\n'
3172 rst += optrst(globalopts)
3172 rst += optrst(globalopts)
3173
3173
3174 keep = ui.verbose and ['verbose'] or []
3174 keep = ui.verbose and ['verbose'] or []
3175 formatted, pruned = minirst.format(rst, textwidth, keep=keep)
3175 formatted, pruned = minirst.format(rst, textwidth, keep=keep)
3176 ui.write(formatted)
3176 ui.write(formatted)
3177
3177
3178 if not ui.verbose:
3178 if not ui.verbose:
3179 if not full:
3179 if not full:
3180 ui.write(_('\nuse "hg help %s" to show the full help text\n')
3180 ui.write(_('\nuse "hg help %s" to show the full help text\n')
3181 % name)
3181 % name)
3182 elif not ui.quiet:
3182 elif not ui.quiet:
3183 ui.write(_('\nuse "hg -v help %s" to show more info\n') % name)
3183 ui.write(_('\nuse "hg -v help %s" to show more info\n') % name)
3184
3184
3185
3185
3186 def helplist(select=None):
3186 def helplist(select=None):
3187 # list of commands
3187 # list of commands
3188 if name == "shortlist":
3188 if name == "shortlist":
3189 header = _('basic commands:\n\n')
3189 header = _('basic commands:\n\n')
3190 else:
3190 else:
3191 header = _('list of commands:\n\n')
3191 header = _('list of commands:\n\n')
3192
3192
3193 h = {}
3193 h = {}
3194 cmds = {}
3194 cmds = {}
3195 for c, e in table.iteritems():
3195 for c, e in table.iteritems():
3196 f = c.split("|", 1)[0]
3196 f = c.split("|", 1)[0]
3197 if select and not select(f):
3197 if select and not select(f):
3198 continue
3198 continue
3199 if (not select and name != 'shortlist' and
3199 if (not select and name != 'shortlist' and
3200 e[0].__module__ != __name__):
3200 e[0].__module__ != __name__):
3201 continue
3201 continue
3202 if name == "shortlist" and not f.startswith("^"):
3202 if name == "shortlist" and not f.startswith("^"):
3203 continue
3203 continue
3204 f = f.lstrip("^")
3204 f = f.lstrip("^")
3205 if not ui.debugflag and f.startswith("debug"):
3205 if not ui.debugflag and f.startswith("debug"):
3206 continue
3206 continue
3207 doc = e[0].__doc__
3207 doc = e[0].__doc__
3208 if doc and 'DEPRECATED' in doc and not ui.verbose:
3208 if doc and 'DEPRECATED' in doc and not ui.verbose:
3209 continue
3209 continue
3210 doc = gettext(doc)
3210 doc = gettext(doc)
3211 if not doc:
3211 if not doc:
3212 doc = _("(no help text available)")
3212 doc = _("(no help text available)")
3213 h[f] = doc.splitlines()[0].rstrip()
3213 h[f] = doc.splitlines()[0].rstrip()
3214 cmds[f] = c.lstrip("^")
3214 cmds[f] = c.lstrip("^")
3215
3215
3216 if not h:
3216 if not h:
3217 ui.status(_('no commands defined\n'))
3217 ui.status(_('no commands defined\n'))
3218 return
3218 return
3219
3219
3220 ui.status(header)
3220 ui.status(header)
3221 fns = sorted(h)
3221 fns = sorted(h)
3222 m = max(map(len, fns))
3222 m = max(map(len, fns))
3223 for f in fns:
3223 for f in fns:
3224 if ui.verbose:
3224 if ui.verbose:
3225 commands = cmds[f].replace("|",", ")
3225 commands = cmds[f].replace("|",", ")
3226 ui.write(" %s:\n %s\n"%(commands, h[f]))
3226 ui.write(" %s:\n %s\n"%(commands, h[f]))
3227 else:
3227 else:
3228 ui.write('%s\n' % (util.wrap(h[f], textwidth,
3228 ui.write('%s\n' % (util.wrap(h[f], textwidth,
3229 initindent=' %-*s ' % (m, f),
3229 initindent=' %-*s ' % (m, f),
3230 hangindent=' ' * (m + 4))))
3230 hangindent=' ' * (m + 4))))
3231
3231
3232 if not name:
3232 if not name:
3233 text = help.listexts(_('enabled extensions:'), extensions.enabled())
3233 text = help.listexts(_('enabled extensions:'), extensions.enabled())
3234 if text:
3234 if text:
3235 ui.write("\n%s" % minirst.format(text, textwidth))
3235 ui.write("\n%s" % minirst.format(text, textwidth))
3236
3236
3237 ui.write(_("\nadditional help topics:\n\n"))
3237 ui.write(_("\nadditional help topics:\n\n"))
3238 topics = []
3238 topics = []
3239 for names, header, doc in help.helptable:
3239 for names, header, doc in help.helptable:
3240 topics.append((sorted(names, key=len, reverse=True)[0], header))
3240 topics.append((sorted(names, key=len, reverse=True)[0], header))
3241 topics_len = max([len(s[0]) for s in topics])
3241 topics_len = max([len(s[0]) for s in topics])
3242 for t, desc in topics:
3242 for t, desc in topics:
3243 ui.write(" %-*s %s\n" % (topics_len, t, desc))
3243 ui.write(" %-*s %s\n" % (topics_len, t, desc))
3244
3244
3245 optlist = []
3245 optlist = []
3246 addglobalopts(optlist, True)
3246 addglobalopts(optlist, True)
3247 ui.write(opttext(optlist, textwidth))
3247 ui.write(opttext(optlist, textwidth))
3248
3248
3249 def helptopic(name):
3249 def helptopic(name):
3250 for names, header, doc in help.helptable:
3250 for names, header, doc in help.helptable:
3251 if name in names:
3251 if name in names:
3252 break
3252 break
3253 else:
3253 else:
3254 raise error.UnknownCommand(name)
3254 raise error.UnknownCommand(name)
3255
3255
3256 # description
3256 # description
3257 if not doc:
3257 if not doc:
3258 doc = _("(no help text available)")
3258 doc = _("(no help text available)")
3259 if util.safehasattr(doc, '__call__'):
3259 if util.safehasattr(doc, '__call__'):
3260 doc = doc()
3260 doc = doc()
3261
3261
3262 ui.write("%s\n\n" % header)
3262 ui.write("%s\n\n" % header)
3263 ui.write("%s" % minirst.format(doc, textwidth, indent=4))
3263 ui.write("%s" % minirst.format(doc, textwidth, indent=4))
3264 try:
3264 try:
3265 cmdutil.findcmd(name, table)
3265 cmdutil.findcmd(name, table)
3266 ui.write(_('\nuse "hg help -c %s" to see help for '
3266 ui.write(_('\nuse "hg help -c %s" to see help for '
3267 'the %s command\n') % (name, name))
3267 'the %s command\n') % (name, name))
3268 except error.UnknownCommand:
3268 except error.UnknownCommand:
3269 pass
3269 pass
3270
3270
3271 def helpext(name):
3271 def helpext(name):
3272 try:
3272 try:
3273 mod = extensions.find(name)
3273 mod = extensions.find(name)
3274 doc = gettext(mod.__doc__) or _('no help text available')
3274 doc = gettext(mod.__doc__) or _('no help text available')
3275 except KeyError:
3275 except KeyError:
3276 mod = None
3276 mod = None
3277 doc = extensions.disabledext(name)
3277 doc = extensions.disabledext(name)
3278 if not doc:
3278 if not doc:
3279 raise error.UnknownCommand(name)
3279 raise error.UnknownCommand(name)
3280
3280
3281 if '\n' not in doc:
3281 if '\n' not in doc:
3282 head, tail = doc, ""
3282 head, tail = doc, ""
3283 else:
3283 else:
3284 head, tail = doc.split('\n', 1)
3284 head, tail = doc.split('\n', 1)
3285 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
3285 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
3286 if tail:
3286 if tail:
3287 ui.write(minirst.format(tail, textwidth))
3287 ui.write(minirst.format(tail, textwidth))
3288 ui.status('\n')
3288 ui.status('\n')
3289
3289
3290 if mod:
3290 if mod:
3291 try:
3291 try:
3292 ct = mod.cmdtable
3292 ct = mod.cmdtable
3293 except AttributeError:
3293 except AttributeError:
3294 ct = {}
3294 ct = {}
3295 modcmds = set([c.split('|', 1)[0] for c in ct])
3295 modcmds = set([c.split('|', 1)[0] for c in ct])
3296 helplist(modcmds.__contains__)
3296 helplist(modcmds.__contains__)
3297 else:
3297 else:
3298 ui.write(_('use "hg help extensions" for information on enabling '
3298 ui.write(_('use "hg help extensions" for information on enabling '
3299 'extensions\n'))
3299 'extensions\n'))
3300
3300
3301 def helpextcmd(name):
3301 def helpextcmd(name):
3302 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
3302 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
3303 doc = gettext(mod.__doc__).splitlines()[0]
3303 doc = gettext(mod.__doc__).splitlines()[0]
3304
3304
3305 msg = help.listexts(_("'%s' is provided by the following "
3305 msg = help.listexts(_("'%s' is provided by the following "
3306 "extension:") % cmd, {ext: doc}, indent=4)
3306 "extension:") % cmd, {ext: doc}, indent=4)
3307 ui.write(minirst.format(msg, textwidth))
3307 ui.write(minirst.format(msg, textwidth))
3308 ui.write('\n')
3308 ui.write('\n')
3309 ui.write(_('use "hg help extensions" for information on enabling '
3309 ui.write(_('use "hg help extensions" for information on enabling '
3310 'extensions\n'))
3310 'extensions\n'))
3311
3311
3312 if name and name != 'shortlist':
3312 if name and name != 'shortlist':
3313 i = None
3313 i = None
3314 if unknowncmd:
3314 if unknowncmd:
3315 queries = (helpextcmd,)
3315 queries = (helpextcmd,)
3316 elif opts.get('extension'):
3316 elif opts.get('extension'):
3317 queries = (helpext,)
3317 queries = (helpext,)
3318 elif opts.get('command'):
3318 elif opts.get('command'):
3319 queries = (helpcmd,)
3319 queries = (helpcmd,)
3320 else:
3320 else:
3321 queries = (helptopic, helpcmd, helpext, helpextcmd)
3321 queries = (helptopic, helpcmd, helpext, helpextcmd)
3322 for f in queries:
3322 for f in queries:
3323 try:
3323 try:
3324 f(name)
3324 f(name)
3325 i = None
3325 i = None
3326 break
3326 break
3327 except error.UnknownCommand, inst:
3327 except error.UnknownCommand, inst:
3328 i = inst
3328 i = inst
3329 if i:
3329 if i:
3330 raise i
3330 raise i
3331 else:
3331 else:
3332 # program name
3332 # program name
3333 ui.status(_("Mercurial Distributed SCM\n"))
3333 ui.status(_("Mercurial Distributed SCM\n"))
3334 ui.status('\n')
3334 ui.status('\n')
3335 helplist()
3335 helplist()
3336
3336
3337
3337
3338 @command('identify|id',
3338 @command('identify|id',
3339 [('r', 'rev', '',
3339 [('r', 'rev', '',
3340 _('identify the specified revision'), _('REV')),
3340 _('identify the specified revision'), _('REV')),
3341 ('n', 'num', None, _('show local revision number')),
3341 ('n', 'num', None, _('show local revision number')),
3342 ('i', 'id', None, _('show global revision id')),
3342 ('i', 'id', None, _('show global revision id')),
3343 ('b', 'branch', None, _('show branch')),
3343 ('b', 'branch', None, _('show branch')),
3344 ('t', 'tags', None, _('show tags')),
3344 ('t', 'tags', None, _('show tags')),
3345 ('B', 'bookmarks', None, _('show bookmarks')),
3345 ('B', 'bookmarks', None, _('show bookmarks')),
3346 ] + remoteopts,
3346 ] + remoteopts,
3347 _('[-nibtB] [-r REV] [SOURCE]'))
3347 _('[-nibtB] [-r REV] [SOURCE]'))
3348 def identify(ui, repo, source=None, rev=None,
3348 def identify(ui, repo, source=None, rev=None,
3349 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3349 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3350 """identify the working copy or specified revision
3350 """identify the working copy or specified revision
3351
3351
3352 Print a summary identifying the repository state at REV using one or
3352 Print a summary identifying the repository state at REV using one or
3353 two parent hash identifiers, followed by a "+" if the working
3353 two parent hash identifiers, followed by a "+" if the working
3354 directory has uncommitted changes, the branch name (if not default),
3354 directory has uncommitted changes, the branch name (if not default),
3355 a list of tags, and a list of bookmarks.
3355 a list of tags, and a list of bookmarks.
3356
3356
3357 When REV is not given, print a summary of the current state of the
3357 When REV is not given, print a summary of the current state of the
3358 repository.
3358 repository.
3359
3359
3360 Specifying a path to a repository root or Mercurial bundle will
3360 Specifying a path to a repository root or Mercurial bundle will
3361 cause lookup to operate on that repository/bundle.
3361 cause lookup to operate on that repository/bundle.
3362
3362
3363 .. container:: verbose
3363 .. container:: verbose
3364
3364
3365 Examples:
3365 Examples:
3366
3366
3367 - generate a build identifier for the working directory::
3367 - generate a build identifier for the working directory::
3368
3368
3369 hg id --id > build-id.dat
3369 hg id --id > build-id.dat
3370
3370
3371 - find the revision corresponding to a tag::
3371 - find the revision corresponding to a tag::
3372
3372
3373 hg id -n -r 1.3
3373 hg id -n -r 1.3
3374
3374
3375 - check the most recent revision of a remote repository::
3375 - check the most recent revision of a remote repository::
3376
3376
3377 hg id -r tip http://selenic.com/hg/
3377 hg id -r tip http://selenic.com/hg/
3378
3378
3379 Returns 0 if successful.
3379 Returns 0 if successful.
3380 """
3380 """
3381
3381
3382 if not repo and not source:
3382 if not repo and not source:
3383 raise util.Abort(_("there is no Mercurial repository here "
3383 raise util.Abort(_("there is no Mercurial repository here "
3384 "(.hg not found)"))
3384 "(.hg not found)"))
3385
3385
3386 hexfunc = ui.debugflag and hex or short
3386 hexfunc = ui.debugflag and hex or short
3387 default = not (num or id or branch or tags or bookmarks)
3387 default = not (num or id or branch or tags or bookmarks)
3388 output = []
3388 output = []
3389 revs = []
3389 revs = []
3390
3390
3391 if source:
3391 if source:
3392 source, branches = hg.parseurl(ui.expandpath(source))
3392 source, branches = hg.parseurl(ui.expandpath(source))
3393 repo = hg.peer(ui, opts, source)
3393 repo = hg.peer(ui, opts, source)
3394 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3394 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3395
3395
3396 if not repo.local():
3396 if not repo.local():
3397 if num or branch or tags:
3397 if num or branch or tags:
3398 raise util.Abort(
3398 raise util.Abort(
3399 _("can't query remote revision number, branch, or tags"))
3399 _("can't query remote revision number, branch, or tags"))
3400 if not rev and revs:
3400 if not rev and revs:
3401 rev = revs[0]
3401 rev = revs[0]
3402 if not rev:
3402 if not rev:
3403 rev = "tip"
3403 rev = "tip"
3404
3404
3405 remoterev = repo.lookup(rev)
3405 remoterev = repo.lookup(rev)
3406 if default or id:
3406 if default or id:
3407 output = [hexfunc(remoterev)]
3407 output = [hexfunc(remoterev)]
3408
3408
3409 def getbms():
3409 def getbms():
3410 bms = []
3410 bms = []
3411
3411
3412 if 'bookmarks' in repo.listkeys('namespaces'):
3412 if 'bookmarks' in repo.listkeys('namespaces'):
3413 hexremoterev = hex(remoterev)
3413 hexremoterev = hex(remoterev)
3414 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
3414 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
3415 if bmr == hexremoterev]
3415 if bmr == hexremoterev]
3416
3416
3417 return bms
3417 return bms
3418
3418
3419 if bookmarks:
3419 if bookmarks:
3420 output.extend(getbms())
3420 output.extend(getbms())
3421 elif default and not ui.quiet:
3421 elif default and not ui.quiet:
3422 # multiple bookmarks for a single parent separated by '/'
3422 # multiple bookmarks for a single parent separated by '/'
3423 bm = '/'.join(getbms())
3423 bm = '/'.join(getbms())
3424 if bm:
3424 if bm:
3425 output.append(bm)
3425 output.append(bm)
3426 else:
3426 else:
3427 if not rev:
3427 if not rev:
3428 ctx = repo[None]
3428 ctx = repo[None]
3429 parents = ctx.parents()
3429 parents = ctx.parents()
3430 changed = ""
3430 changed = ""
3431 if default or id or num:
3431 if default or id or num:
3432 changed = util.any(repo.status()) and "+" or ""
3432 changed = util.any(repo.status()) and "+" or ""
3433 if default or id:
3433 if default or id:
3434 output = ["%s%s" %
3434 output = ["%s%s" %
3435 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3435 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3436 if num:
3436 if num:
3437 output.append("%s%s" %
3437 output.append("%s%s" %
3438 ('+'.join([str(p.rev()) for p in parents]), changed))
3438 ('+'.join([str(p.rev()) for p in parents]), changed))
3439 else:
3439 else:
3440 ctx = scmutil.revsingle(repo, rev)
3440 ctx = scmutil.revsingle(repo, rev)
3441 if default or id:
3441 if default or id:
3442 output = [hexfunc(ctx.node())]
3442 output = [hexfunc(ctx.node())]
3443 if num:
3443 if num:
3444 output.append(str(ctx.rev()))
3444 output.append(str(ctx.rev()))
3445
3445
3446 if default and not ui.quiet:
3446 if default and not ui.quiet:
3447 b = ctx.branch()
3447 b = ctx.branch()
3448 if b != 'default':
3448 if b != 'default':
3449 output.append("(%s)" % b)
3449 output.append("(%s)" % b)
3450
3450
3451 # multiple tags for a single parent separated by '/'
3451 # multiple tags for a single parent separated by '/'
3452 t = '/'.join(ctx.tags())
3452 t = '/'.join(ctx.tags())
3453 if t:
3453 if t:
3454 output.append(t)
3454 output.append(t)
3455
3455
3456 # multiple bookmarks for a single parent separated by '/'
3456 # multiple bookmarks for a single parent separated by '/'
3457 bm = '/'.join(ctx.bookmarks())
3457 bm = '/'.join(ctx.bookmarks())
3458 if bm:
3458 if bm:
3459 output.append(bm)
3459 output.append(bm)
3460 else:
3460 else:
3461 if branch:
3461 if branch:
3462 output.append(ctx.branch())
3462 output.append(ctx.branch())
3463
3463
3464 if tags:
3464 if tags:
3465 output.extend(ctx.tags())
3465 output.extend(ctx.tags())
3466
3466
3467 if bookmarks:
3467 if bookmarks:
3468 output.extend(ctx.bookmarks())
3468 output.extend(ctx.bookmarks())
3469
3469
3470 ui.write("%s\n" % ' '.join(output))
3470 ui.write("%s\n" % ' '.join(output))
3471
3471
3472 @command('import|patch',
3472 @command('import|patch',
3473 [('p', 'strip', 1,
3473 [('p', 'strip', 1,
3474 _('directory strip option for patch. This has the same '
3474 _('directory strip option for patch. This has the same '
3475 'meaning as the corresponding patch option'), _('NUM')),
3475 'meaning as the corresponding patch option'), _('NUM')),
3476 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3476 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3477 ('e', 'edit', False, _('invoke editor on commit messages')),
3477 ('e', 'edit', False, _('invoke editor on commit messages')),
3478 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3478 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3479 ('', 'no-commit', None,
3479 ('', 'no-commit', None,
3480 _("don't commit, just update the working directory")),
3480 _("don't commit, just update the working directory")),
3481 ('', 'bypass', None,
3481 ('', 'bypass', None,
3482 _("apply patch without touching the working directory")),
3482 _("apply patch without touching the working directory")),
3483 ('', 'exact', None,
3483 ('', 'exact', None,
3484 _('apply patch to the nodes from which it was generated')),
3484 _('apply patch to the nodes from which it was generated')),
3485 ('', 'import-branch', None,
3485 ('', 'import-branch', None,
3486 _('use any branch information in patch (implied by --exact)'))] +
3486 _('use any branch information in patch (implied by --exact)'))] +
3487 commitopts + commitopts2 + similarityopts,
3487 commitopts + commitopts2 + similarityopts,
3488 _('[OPTION]... PATCH...'))
3488 _('[OPTION]... PATCH...'))
3489 def import_(ui, repo, patch1=None, *patches, **opts):
3489 def import_(ui, repo, patch1=None, *patches, **opts):
3490 """import an ordered set of patches
3490 """import an ordered set of patches
3491
3491
3492 Import a list of patches and commit them individually (unless
3492 Import a list of patches and commit them individually (unless
3493 --no-commit is specified).
3493 --no-commit is specified).
3494
3494
3495 If there are outstanding changes in the working directory, import
3495 If there are outstanding changes in the working directory, import
3496 will abort unless given the -f/--force flag.
3496 will abort unless given the -f/--force flag.
3497
3497
3498 You can import a patch straight from a mail message. Even patches
3498 You can import a patch straight from a mail message. Even patches
3499 as attachments work (to use the body part, it must have type
3499 as attachments work (to use the body part, it must have type
3500 text/plain or text/x-patch). From and Subject headers of email
3500 text/plain or text/x-patch). From and Subject headers of email
3501 message are used as default committer and commit message. All
3501 message are used as default committer and commit message. All
3502 text/plain body parts before first diff are added to commit
3502 text/plain body parts before first diff are added to commit
3503 message.
3503 message.
3504
3504
3505 If the imported patch was generated by :hg:`export`, user and
3505 If the imported patch was generated by :hg:`export`, user and
3506 description from patch override values from message headers and
3506 description from patch override values from message headers and
3507 body. Values given on command line with -m/--message and -u/--user
3507 body. Values given on command line with -m/--message and -u/--user
3508 override these.
3508 override these.
3509
3509
3510 If --exact is specified, import will set the working directory to
3510 If --exact is specified, import will set the working directory to
3511 the parent of each patch before applying it, and will abort if the
3511 the parent of each patch before applying it, and will abort if the
3512 resulting changeset has a different ID than the one recorded in
3512 resulting changeset has a different ID than the one recorded in
3513 the patch. This may happen due to character set problems or other
3513 the patch. This may happen due to character set problems or other
3514 deficiencies in the text patch format.
3514 deficiencies in the text patch format.
3515
3515
3516 Use --bypass to apply and commit patches directly to the
3516 Use --bypass to apply and commit patches directly to the
3517 repository, not touching the working directory. Without --exact,
3517 repository, not touching the working directory. Without --exact,
3518 patches will be applied on top of the working directory parent
3518 patches will be applied on top of the working directory parent
3519 revision.
3519 revision.
3520
3520
3521 With -s/--similarity, hg will attempt to discover renames and
3521 With -s/--similarity, hg will attempt to discover renames and
3522 copies in the patch in the same way as :hg:`addremove`.
3522 copies in the patch in the same way as :hg:`addremove`.
3523
3523
3524 To read a patch from standard input, use "-" as the patch name. If
3524 To read a patch from standard input, use "-" as the patch name. If
3525 a URL is specified, the patch will be downloaded from it.
3525 a URL is specified, the patch will be downloaded from it.
3526 See :hg:`help dates` for a list of formats valid for -d/--date.
3526 See :hg:`help dates` for a list of formats valid for -d/--date.
3527
3527
3528 .. container:: verbose
3528 .. container:: verbose
3529
3529
3530 Examples:
3530 Examples:
3531
3531
3532 - import a traditional patch from a website and detect renames::
3532 - import a traditional patch from a website and detect renames::
3533
3533
3534 hg import -s 80 http://example.com/bugfix.patch
3534 hg import -s 80 http://example.com/bugfix.patch
3535
3535
3536 - import a changeset from an hgweb server::
3536 - import a changeset from an hgweb server::
3537
3537
3538 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3538 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3539
3539
3540 - import all the patches in an Unix-style mbox::
3540 - import all the patches in an Unix-style mbox::
3541
3541
3542 hg import incoming-patches.mbox
3542 hg import incoming-patches.mbox
3543
3543
3544 - attempt to exactly restore an exported changeset (not always
3544 - attempt to exactly restore an exported changeset (not always
3545 possible)::
3545 possible)::
3546
3546
3547 hg import --exact proposed-fix.patch
3547 hg import --exact proposed-fix.patch
3548
3548
3549 Returns 0 on success.
3549 Returns 0 on success.
3550 """
3550 """
3551
3551
3552 if not patch1:
3552 if not patch1:
3553 raise util.Abort(_('need at least one patch to import'))
3553 raise util.Abort(_('need at least one patch to import'))
3554
3554
3555 patches = (patch1,) + patches
3555 patches = (patch1,) + patches
3556
3556
3557 date = opts.get('date')
3557 date = opts.get('date')
3558 if date:
3558 if date:
3559 opts['date'] = util.parsedate(date)
3559 opts['date'] = util.parsedate(date)
3560
3560
3561 editor = cmdutil.commiteditor
3561 editor = cmdutil.commiteditor
3562 if opts.get('edit'):
3562 if opts.get('edit'):
3563 editor = cmdutil.commitforceeditor
3563 editor = cmdutil.commitforceeditor
3564
3564
3565 update = not opts.get('bypass')
3565 update = not opts.get('bypass')
3566 if not update and opts.get('no_commit'):
3566 if not update and opts.get('no_commit'):
3567 raise util.Abort(_('cannot use --no-commit with --bypass'))
3567 raise util.Abort(_('cannot use --no-commit with --bypass'))
3568 try:
3568 try:
3569 sim = float(opts.get('similarity') or 0)
3569 sim = float(opts.get('similarity') or 0)
3570 except ValueError:
3570 except ValueError:
3571 raise util.Abort(_('similarity must be a number'))
3571 raise util.Abort(_('similarity must be a number'))
3572 if sim < 0 or sim > 100:
3572 if sim < 0 or sim > 100:
3573 raise util.Abort(_('similarity must be between 0 and 100'))
3573 raise util.Abort(_('similarity must be between 0 and 100'))
3574 if sim and not update:
3574 if sim and not update:
3575 raise util.Abort(_('cannot use --similarity with --bypass'))
3575 raise util.Abort(_('cannot use --similarity with --bypass'))
3576
3576
3577 if (opts.get('exact') or not opts.get('force')) and update:
3577 if (opts.get('exact') or not opts.get('force')) and update:
3578 cmdutil.bailifchanged(repo)
3578 cmdutil.bailifchanged(repo)
3579
3579
3580 base = opts["base"]
3580 base = opts["base"]
3581 strip = opts["strip"]
3581 strip = opts["strip"]
3582 wlock = lock = tr = None
3582 wlock = lock = tr = None
3583 msgs = []
3583 msgs = []
3584
3584
3585 def checkexact(repo, n, nodeid):
3585 def checkexact(repo, n, nodeid):
3586 if opts.get('exact') and hex(n) != nodeid:
3586 if opts.get('exact') and hex(n) != nodeid:
3587 repo.rollback()
3587 repo.rollback()
3588 raise util.Abort(_('patch is damaged or loses information'))
3588 raise util.Abort(_('patch is damaged or loses information'))
3589
3589
3590 def tryone(ui, hunk, parents):
3590 def tryone(ui, hunk, parents):
3591 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3591 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3592 patch.extract(ui, hunk)
3592 patch.extract(ui, hunk)
3593
3593
3594 if not tmpname:
3594 if not tmpname:
3595 return (None, None)
3595 return (None, None)
3596 msg = _('applied to working directory')
3596 msg = _('applied to working directory')
3597
3597
3598 try:
3598 try:
3599 cmdline_message = cmdutil.logmessage(ui, opts)
3599 cmdline_message = cmdutil.logmessage(ui, opts)
3600 if cmdline_message:
3600 if cmdline_message:
3601 # pickup the cmdline msg
3601 # pickup the cmdline msg
3602 message = cmdline_message
3602 message = cmdline_message
3603 elif message:
3603 elif message:
3604 # pickup the patch msg
3604 # pickup the patch msg
3605 message = message.strip()
3605 message = message.strip()
3606 else:
3606 else:
3607 # launch the editor
3607 # launch the editor
3608 message = None
3608 message = None
3609 ui.debug('message:\n%s\n' % message)
3609 ui.debug('message:\n%s\n' % message)
3610
3610
3611 if len(parents) == 1:
3611 if len(parents) == 1:
3612 parents.append(repo[nullid])
3612 parents.append(repo[nullid])
3613 if opts.get('exact'):
3613 if opts.get('exact'):
3614 if not nodeid or not p1:
3614 if not nodeid or not p1:
3615 raise util.Abort(_('not a Mercurial patch'))
3615 raise util.Abort(_('not a Mercurial patch'))
3616 p1 = repo[p1]
3616 p1 = repo[p1]
3617 p2 = repo[p2 or nullid]
3617 p2 = repo[p2 or nullid]
3618 elif p2:
3618 elif p2:
3619 try:
3619 try:
3620 p1 = repo[p1]
3620 p1 = repo[p1]
3621 p2 = repo[p2]
3621 p2 = repo[p2]
3622 # Without any options, consider p2 only if the
3622 # Without any options, consider p2 only if the
3623 # patch is being applied on top of the recorded
3623 # patch is being applied on top of the recorded
3624 # first parent.
3624 # first parent.
3625 if p1 != parents[0]:
3625 if p1 != parents[0]:
3626 p1 = parents[0]
3626 p1 = parents[0]
3627 p2 = repo[nullid]
3627 p2 = repo[nullid]
3628 except error.RepoError:
3628 except error.RepoError:
3629 p1, p2 = parents
3629 p1, p2 = parents
3630 else:
3630 else:
3631 p1, p2 = parents
3631 p1, p2 = parents
3632
3632
3633 n = None
3633 n = None
3634 if update:
3634 if update:
3635 if p1 != parents[0]:
3635 if p1 != parents[0]:
3636 hg.clean(repo, p1.node())
3636 hg.clean(repo, p1.node())
3637 if p2 != parents[1]:
3637 if p2 != parents[1]:
3638 repo.dirstate.setparents(p1.node(), p2.node())
3638 repo.setparents(p1.node(), p2.node())
3639
3639
3640 if opts.get('exact') or opts.get('import_branch'):
3640 if opts.get('exact') or opts.get('import_branch'):
3641 repo.dirstate.setbranch(branch or 'default')
3641 repo.dirstate.setbranch(branch or 'default')
3642
3642
3643 files = set()
3643 files = set()
3644 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3644 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3645 eolmode=None, similarity=sim / 100.0)
3645 eolmode=None, similarity=sim / 100.0)
3646 files = list(files)
3646 files = list(files)
3647 if opts.get('no_commit'):
3647 if opts.get('no_commit'):
3648 if message:
3648 if message:
3649 msgs.append(message)
3649 msgs.append(message)
3650 else:
3650 else:
3651 if opts.get('exact') or p2:
3651 if opts.get('exact') or p2:
3652 # If you got here, you either use --force and know what
3652 # If you got here, you either use --force and know what
3653 # you are doing or used --exact or a merge patch while
3653 # you are doing or used --exact or a merge patch while
3654 # being updated to its first parent.
3654 # being updated to its first parent.
3655 m = None
3655 m = None
3656 else:
3656 else:
3657 m = scmutil.matchfiles(repo, files or [])
3657 m = scmutil.matchfiles(repo, files or [])
3658 n = repo.commit(message, opts.get('user') or user,
3658 n = repo.commit(message, opts.get('user') or user,
3659 opts.get('date') or date, match=m,
3659 opts.get('date') or date, match=m,
3660 editor=editor)
3660 editor=editor)
3661 checkexact(repo, n, nodeid)
3661 checkexact(repo, n, nodeid)
3662 else:
3662 else:
3663 if opts.get('exact') or opts.get('import_branch'):
3663 if opts.get('exact') or opts.get('import_branch'):
3664 branch = branch or 'default'
3664 branch = branch or 'default'
3665 else:
3665 else:
3666 branch = p1.branch()
3666 branch = p1.branch()
3667 store = patch.filestore()
3667 store = patch.filestore()
3668 try:
3668 try:
3669 files = set()
3669 files = set()
3670 try:
3670 try:
3671 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3671 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3672 files, eolmode=None)
3672 files, eolmode=None)
3673 except patch.PatchError, e:
3673 except patch.PatchError, e:
3674 raise util.Abort(str(e))
3674 raise util.Abort(str(e))
3675 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3675 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3676 message,
3676 message,
3677 opts.get('user') or user,
3677 opts.get('user') or user,
3678 opts.get('date') or date,
3678 opts.get('date') or date,
3679 branch, files, store,
3679 branch, files, store,
3680 editor=cmdutil.commiteditor)
3680 editor=cmdutil.commiteditor)
3681 repo.savecommitmessage(memctx.description())
3681 repo.savecommitmessage(memctx.description())
3682 n = memctx.commit()
3682 n = memctx.commit()
3683 checkexact(repo, n, nodeid)
3683 checkexact(repo, n, nodeid)
3684 finally:
3684 finally:
3685 store.close()
3685 store.close()
3686 if n:
3686 if n:
3687 # i18n: refers to a short changeset id
3687 # i18n: refers to a short changeset id
3688 msg = _('created %s') % short(n)
3688 msg = _('created %s') % short(n)
3689 return (msg, n)
3689 return (msg, n)
3690 finally:
3690 finally:
3691 os.unlink(tmpname)
3691 os.unlink(tmpname)
3692
3692
3693 try:
3693 try:
3694 try:
3694 try:
3695 wlock = repo.wlock()
3695 wlock = repo.wlock()
3696 if not opts.get('no_commit'):
3696 if not opts.get('no_commit'):
3697 lock = repo.lock()
3697 lock = repo.lock()
3698 tr = repo.transaction('import')
3698 tr = repo.transaction('import')
3699 parents = repo.parents()
3699 parents = repo.parents()
3700 for patchurl in patches:
3700 for patchurl in patches:
3701 if patchurl == '-':
3701 if patchurl == '-':
3702 ui.status(_('applying patch from stdin\n'))
3702 ui.status(_('applying patch from stdin\n'))
3703 patchfile = ui.fin
3703 patchfile = ui.fin
3704 patchurl = 'stdin' # for error message
3704 patchurl = 'stdin' # for error message
3705 else:
3705 else:
3706 patchurl = os.path.join(base, patchurl)
3706 patchurl = os.path.join(base, patchurl)
3707 ui.status(_('applying %s\n') % patchurl)
3707 ui.status(_('applying %s\n') % patchurl)
3708 patchfile = url.open(ui, patchurl)
3708 patchfile = url.open(ui, patchurl)
3709
3709
3710 haspatch = False
3710 haspatch = False
3711 for hunk in patch.split(patchfile):
3711 for hunk in patch.split(patchfile):
3712 (msg, node) = tryone(ui, hunk, parents)
3712 (msg, node) = tryone(ui, hunk, parents)
3713 if msg:
3713 if msg:
3714 haspatch = True
3714 haspatch = True
3715 ui.note(msg + '\n')
3715 ui.note(msg + '\n')
3716 if update or opts.get('exact'):
3716 if update or opts.get('exact'):
3717 parents = repo.parents()
3717 parents = repo.parents()
3718 else:
3718 else:
3719 parents = [repo[node]]
3719 parents = [repo[node]]
3720
3720
3721 if not haspatch:
3721 if not haspatch:
3722 raise util.Abort(_('%s: no diffs found') % patchurl)
3722 raise util.Abort(_('%s: no diffs found') % patchurl)
3723
3723
3724 if tr:
3724 if tr:
3725 tr.close()
3725 tr.close()
3726 if msgs:
3726 if msgs:
3727 repo.savecommitmessage('\n* * *\n'.join(msgs))
3727 repo.savecommitmessage('\n* * *\n'.join(msgs))
3728 except:
3728 except:
3729 # wlock.release() indirectly calls dirstate.write(): since
3729 # wlock.release() indirectly calls dirstate.write(): since
3730 # we're crashing, we do not want to change the working dir
3730 # we're crashing, we do not want to change the working dir
3731 # parent after all, so make sure it writes nothing
3731 # parent after all, so make sure it writes nothing
3732 repo.dirstate.invalidate()
3732 repo.dirstate.invalidate()
3733 raise
3733 raise
3734 finally:
3734 finally:
3735 if tr:
3735 if tr:
3736 tr.release()
3736 tr.release()
3737 release(lock, wlock)
3737 release(lock, wlock)
3738
3738
3739 @command('incoming|in',
3739 @command('incoming|in',
3740 [('f', 'force', None,
3740 [('f', 'force', None,
3741 _('run even if remote repository is unrelated')),
3741 _('run even if remote repository is unrelated')),
3742 ('n', 'newest-first', None, _('show newest record first')),
3742 ('n', 'newest-first', None, _('show newest record first')),
3743 ('', 'bundle', '',
3743 ('', 'bundle', '',
3744 _('file to store the bundles into'), _('FILE')),
3744 _('file to store the bundles into'), _('FILE')),
3745 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3745 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3746 ('B', 'bookmarks', False, _("compare bookmarks")),
3746 ('B', 'bookmarks', False, _("compare bookmarks")),
3747 ('b', 'branch', [],
3747 ('b', 'branch', [],
3748 _('a specific branch you would like to pull'), _('BRANCH')),
3748 _('a specific branch you would like to pull'), _('BRANCH')),
3749 ] + logopts + remoteopts + subrepoopts,
3749 ] + logopts + remoteopts + subrepoopts,
3750 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3750 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3751 def incoming(ui, repo, source="default", **opts):
3751 def incoming(ui, repo, source="default", **opts):
3752 """show new changesets found in source
3752 """show new changesets found in source
3753
3753
3754 Show new changesets found in the specified path/URL or the default
3754 Show new changesets found in the specified path/URL or the default
3755 pull location. These are the changesets that would have been pulled
3755 pull location. These are the changesets that would have been pulled
3756 if a pull at the time you issued this command.
3756 if a pull at the time you issued this command.
3757
3757
3758 For remote repository, using --bundle avoids downloading the
3758 For remote repository, using --bundle avoids downloading the
3759 changesets twice if the incoming is followed by a pull.
3759 changesets twice if the incoming is followed by a pull.
3760
3760
3761 See pull for valid source format details.
3761 See pull for valid source format details.
3762
3762
3763 Returns 0 if there are incoming changes, 1 otherwise.
3763 Returns 0 if there are incoming changes, 1 otherwise.
3764 """
3764 """
3765 if opts.get('bundle') and opts.get('subrepos'):
3765 if opts.get('bundle') and opts.get('subrepos'):
3766 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3766 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3767
3767
3768 if opts.get('bookmarks'):
3768 if opts.get('bookmarks'):
3769 source, branches = hg.parseurl(ui.expandpath(source),
3769 source, branches = hg.parseurl(ui.expandpath(source),
3770 opts.get('branch'))
3770 opts.get('branch'))
3771 other = hg.peer(repo, opts, source)
3771 other = hg.peer(repo, opts, source)
3772 if 'bookmarks' not in other.listkeys('namespaces'):
3772 if 'bookmarks' not in other.listkeys('namespaces'):
3773 ui.warn(_("remote doesn't support bookmarks\n"))
3773 ui.warn(_("remote doesn't support bookmarks\n"))
3774 return 0
3774 return 0
3775 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3775 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3776 return bookmarks.diff(ui, repo, other)
3776 return bookmarks.diff(ui, repo, other)
3777
3777
3778 repo._subtoppath = ui.expandpath(source)
3778 repo._subtoppath = ui.expandpath(source)
3779 try:
3779 try:
3780 return hg.incoming(ui, repo, source, opts)
3780 return hg.incoming(ui, repo, source, opts)
3781 finally:
3781 finally:
3782 del repo._subtoppath
3782 del repo._subtoppath
3783
3783
3784
3784
3785 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3785 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3786 def init(ui, dest=".", **opts):
3786 def init(ui, dest=".", **opts):
3787 """create a new repository in the given directory
3787 """create a new repository in the given directory
3788
3788
3789 Initialize a new repository in the given directory. If the given
3789 Initialize a new repository in the given directory. If the given
3790 directory does not exist, it will be created.
3790 directory does not exist, it will be created.
3791
3791
3792 If no directory is given, the current directory is used.
3792 If no directory is given, the current directory is used.
3793
3793
3794 It is possible to specify an ``ssh://`` URL as the destination.
3794 It is possible to specify an ``ssh://`` URL as the destination.
3795 See :hg:`help urls` for more information.
3795 See :hg:`help urls` for more information.
3796
3796
3797 Returns 0 on success.
3797 Returns 0 on success.
3798 """
3798 """
3799 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3799 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3800
3800
3801 @command('locate',
3801 @command('locate',
3802 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3802 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3803 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3803 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3804 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3804 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3805 ] + walkopts,
3805 ] + walkopts,
3806 _('[OPTION]... [PATTERN]...'))
3806 _('[OPTION]... [PATTERN]...'))
3807 def locate(ui, repo, *pats, **opts):
3807 def locate(ui, repo, *pats, **opts):
3808 """locate files matching specific patterns
3808 """locate files matching specific patterns
3809
3809
3810 Print files under Mercurial control in the working directory whose
3810 Print files under Mercurial control in the working directory whose
3811 names match the given patterns.
3811 names match the given patterns.
3812
3812
3813 By default, this command searches all directories in the working
3813 By default, this command searches all directories in the working
3814 directory. To search just the current directory and its
3814 directory. To search just the current directory and its
3815 subdirectories, use "--include .".
3815 subdirectories, use "--include .".
3816
3816
3817 If no patterns are given to match, this command prints the names
3817 If no patterns are given to match, this command prints the names
3818 of all files under Mercurial control in the working directory.
3818 of all files under Mercurial control in the working directory.
3819
3819
3820 If you want to feed the output of this command into the "xargs"
3820 If you want to feed the output of this command into the "xargs"
3821 command, use the -0 option to both this command and "xargs". This
3821 command, use the -0 option to both this command and "xargs". This
3822 will avoid the problem of "xargs" treating single filenames that
3822 will avoid the problem of "xargs" treating single filenames that
3823 contain whitespace as multiple filenames.
3823 contain whitespace as multiple filenames.
3824
3824
3825 Returns 0 if a match is found, 1 otherwise.
3825 Returns 0 if a match is found, 1 otherwise.
3826 """
3826 """
3827 end = opts.get('print0') and '\0' or '\n'
3827 end = opts.get('print0') and '\0' or '\n'
3828 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3828 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3829
3829
3830 ret = 1
3830 ret = 1
3831 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3831 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3832 m.bad = lambda x, y: False
3832 m.bad = lambda x, y: False
3833 for abs in repo[rev].walk(m):
3833 for abs in repo[rev].walk(m):
3834 if not rev and abs not in repo.dirstate:
3834 if not rev and abs not in repo.dirstate:
3835 continue
3835 continue
3836 if opts.get('fullpath'):
3836 if opts.get('fullpath'):
3837 ui.write(repo.wjoin(abs), end)
3837 ui.write(repo.wjoin(abs), end)
3838 else:
3838 else:
3839 ui.write(((pats and m.rel(abs)) or abs), end)
3839 ui.write(((pats and m.rel(abs)) or abs), end)
3840 ret = 0
3840 ret = 0
3841
3841
3842 return ret
3842 return ret
3843
3843
3844 @command('^log|history',
3844 @command('^log|history',
3845 [('f', 'follow', None,
3845 [('f', 'follow', None,
3846 _('follow changeset history, or file history across copies and renames')),
3846 _('follow changeset history, or file history across copies and renames')),
3847 ('', 'follow-first', None,
3847 ('', 'follow-first', None,
3848 _('only follow the first parent of merge changesets (DEPRECATED)')),
3848 _('only follow the first parent of merge changesets (DEPRECATED)')),
3849 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3849 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3850 ('C', 'copies', None, _('show copied files')),
3850 ('C', 'copies', None, _('show copied files')),
3851 ('k', 'keyword', [],
3851 ('k', 'keyword', [],
3852 _('do case-insensitive search for a given text'), _('TEXT')),
3852 _('do case-insensitive search for a given text'), _('TEXT')),
3853 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3853 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3854 ('', 'removed', None, _('include revisions where files were removed')),
3854 ('', 'removed', None, _('include revisions where files were removed')),
3855 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3855 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3856 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3856 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3857 ('', 'only-branch', [],
3857 ('', 'only-branch', [],
3858 _('show only changesets within the given named branch (DEPRECATED)'),
3858 _('show only changesets within the given named branch (DEPRECATED)'),
3859 _('BRANCH')),
3859 _('BRANCH')),
3860 ('b', 'branch', [],
3860 ('b', 'branch', [],
3861 _('show changesets within the given named branch'), _('BRANCH')),
3861 _('show changesets within the given named branch'), _('BRANCH')),
3862 ('P', 'prune', [],
3862 ('P', 'prune', [],
3863 _('do not display revision or any of its ancestors'), _('REV')),
3863 _('do not display revision or any of its ancestors'), _('REV')),
3864 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3864 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3865 ] + logopts + walkopts,
3865 ] + logopts + walkopts,
3866 _('[OPTION]... [FILE]'))
3866 _('[OPTION]... [FILE]'))
3867 def log(ui, repo, *pats, **opts):
3867 def log(ui, repo, *pats, **opts):
3868 """show revision history of entire repository or files
3868 """show revision history of entire repository or files
3869
3869
3870 Print the revision history of the specified files or the entire
3870 Print the revision history of the specified files or the entire
3871 project.
3871 project.
3872
3872
3873 If no revision range is specified, the default is ``tip:0`` unless
3873 If no revision range is specified, the default is ``tip:0`` unless
3874 --follow is set, in which case the working directory parent is
3874 --follow is set, in which case the working directory parent is
3875 used as the starting revision.
3875 used as the starting revision.
3876
3876
3877 File history is shown without following rename or copy history of
3877 File history is shown without following rename or copy history of
3878 files. Use -f/--follow with a filename to follow history across
3878 files. Use -f/--follow with a filename to follow history across
3879 renames and copies. --follow without a filename will only show
3879 renames and copies. --follow without a filename will only show
3880 ancestors or descendants of the starting revision.
3880 ancestors or descendants of the starting revision.
3881
3881
3882 By default this command prints revision number and changeset id,
3882 By default this command prints revision number and changeset id,
3883 tags, non-trivial parents, user, date and time, and a summary for
3883 tags, non-trivial parents, user, date and time, and a summary for
3884 each commit. When the -v/--verbose switch is used, the list of
3884 each commit. When the -v/--verbose switch is used, the list of
3885 changed files and full commit message are shown.
3885 changed files and full commit message are shown.
3886
3886
3887 .. note::
3887 .. note::
3888 log -p/--patch may generate unexpected diff output for merge
3888 log -p/--patch may generate unexpected diff output for merge
3889 changesets, as it will only compare the merge changeset against
3889 changesets, as it will only compare the merge changeset against
3890 its first parent. Also, only files different from BOTH parents
3890 its first parent. Also, only files different from BOTH parents
3891 will appear in files:.
3891 will appear in files:.
3892
3892
3893 .. note::
3893 .. note::
3894 for performance reasons, log FILE may omit duplicate changes
3894 for performance reasons, log FILE may omit duplicate changes
3895 made on branches and will not show deletions. To see all
3895 made on branches and will not show deletions. To see all
3896 changes including duplicates and deletions, use the --removed
3896 changes including duplicates and deletions, use the --removed
3897 switch.
3897 switch.
3898
3898
3899 .. container:: verbose
3899 .. container:: verbose
3900
3900
3901 Some examples:
3901 Some examples:
3902
3902
3903 - changesets with full descriptions and file lists::
3903 - changesets with full descriptions and file lists::
3904
3904
3905 hg log -v
3905 hg log -v
3906
3906
3907 - changesets ancestral to the working directory::
3907 - changesets ancestral to the working directory::
3908
3908
3909 hg log -f
3909 hg log -f
3910
3910
3911 - last 10 commits on the current branch::
3911 - last 10 commits on the current branch::
3912
3912
3913 hg log -l 10 -b .
3913 hg log -l 10 -b .
3914
3914
3915 - changesets showing all modifications of a file, including removals::
3915 - changesets showing all modifications of a file, including removals::
3916
3916
3917 hg log --removed file.c
3917 hg log --removed file.c
3918
3918
3919 - all changesets that touch a directory, with diffs, excluding merges::
3919 - all changesets that touch a directory, with diffs, excluding merges::
3920
3920
3921 hg log -Mp lib/
3921 hg log -Mp lib/
3922
3922
3923 - all revision numbers that match a keyword::
3923 - all revision numbers that match a keyword::
3924
3924
3925 hg log -k bug --template "{rev}\\n"
3925 hg log -k bug --template "{rev}\\n"
3926
3926
3927 - check if a given changeset is included is a tagged release::
3927 - check if a given changeset is included is a tagged release::
3928
3928
3929 hg log -r "a21ccf and ancestor(1.9)"
3929 hg log -r "a21ccf and ancestor(1.9)"
3930
3930
3931 - find all changesets by some user in a date range::
3931 - find all changesets by some user in a date range::
3932
3932
3933 hg log -k alice -d "may 2008 to jul 2008"
3933 hg log -k alice -d "may 2008 to jul 2008"
3934
3934
3935 - summary of all changesets after the last tag::
3935 - summary of all changesets after the last tag::
3936
3936
3937 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3937 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3938
3938
3939 See :hg:`help dates` for a list of formats valid for -d/--date.
3939 See :hg:`help dates` for a list of formats valid for -d/--date.
3940
3940
3941 See :hg:`help revisions` and :hg:`help revsets` for more about
3941 See :hg:`help revisions` and :hg:`help revsets` for more about
3942 specifying revisions.
3942 specifying revisions.
3943
3943
3944 Returns 0 on success.
3944 Returns 0 on success.
3945 """
3945 """
3946
3946
3947 matchfn = scmutil.match(repo[None], pats, opts)
3947 matchfn = scmutil.match(repo[None], pats, opts)
3948 limit = cmdutil.loglimit(opts)
3948 limit = cmdutil.loglimit(opts)
3949 count = 0
3949 count = 0
3950
3950
3951 getrenamed, endrev = None, None
3951 getrenamed, endrev = None, None
3952 if opts.get('copies'):
3952 if opts.get('copies'):
3953 if opts.get('rev'):
3953 if opts.get('rev'):
3954 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3954 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3955 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3955 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3956
3956
3957 df = False
3957 df = False
3958 if opts["date"]:
3958 if opts["date"]:
3959 df = util.matchdate(opts["date"])
3959 df = util.matchdate(opts["date"])
3960
3960
3961 branches = opts.get('branch', []) + opts.get('only_branch', [])
3961 branches = opts.get('branch', []) + opts.get('only_branch', [])
3962 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3962 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3963
3963
3964 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3964 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3965 def prep(ctx, fns):
3965 def prep(ctx, fns):
3966 rev = ctx.rev()
3966 rev = ctx.rev()
3967 parents = [p for p in repo.changelog.parentrevs(rev)
3967 parents = [p for p in repo.changelog.parentrevs(rev)
3968 if p != nullrev]
3968 if p != nullrev]
3969 if opts.get('no_merges') and len(parents) == 2:
3969 if opts.get('no_merges') and len(parents) == 2:
3970 return
3970 return
3971 if opts.get('only_merges') and len(parents) != 2:
3971 if opts.get('only_merges') and len(parents) != 2:
3972 return
3972 return
3973 if opts.get('branch') and ctx.branch() not in opts['branch']:
3973 if opts.get('branch') and ctx.branch() not in opts['branch']:
3974 return
3974 return
3975 if not opts.get('hidden') and ctx.hidden():
3975 if not opts.get('hidden') and ctx.hidden():
3976 return
3976 return
3977 if df and not df(ctx.date()[0]):
3977 if df and not df(ctx.date()[0]):
3978 return
3978 return
3979
3979
3980 lower = encoding.lower
3980 lower = encoding.lower
3981 if opts.get('user'):
3981 if opts.get('user'):
3982 luser = lower(ctx.user())
3982 luser = lower(ctx.user())
3983 for k in [lower(x) for x in opts['user']]:
3983 for k in [lower(x) for x in opts['user']]:
3984 if (k in luser):
3984 if (k in luser):
3985 break
3985 break
3986 else:
3986 else:
3987 return
3987 return
3988 if opts.get('keyword'):
3988 if opts.get('keyword'):
3989 luser = lower(ctx.user())
3989 luser = lower(ctx.user())
3990 ldesc = lower(ctx.description())
3990 ldesc = lower(ctx.description())
3991 lfiles = lower(" ".join(ctx.files()))
3991 lfiles = lower(" ".join(ctx.files()))
3992 for k in [lower(x) for x in opts['keyword']]:
3992 for k in [lower(x) for x in opts['keyword']]:
3993 if (k in luser or k in ldesc or k in lfiles):
3993 if (k in luser or k in ldesc or k in lfiles):
3994 break
3994 break
3995 else:
3995 else:
3996 return
3996 return
3997
3997
3998 copies = None
3998 copies = None
3999 if getrenamed is not None and rev:
3999 if getrenamed is not None and rev:
4000 copies = []
4000 copies = []
4001 for fn in ctx.files():
4001 for fn in ctx.files():
4002 rename = getrenamed(fn, rev)
4002 rename = getrenamed(fn, rev)
4003 if rename:
4003 if rename:
4004 copies.append((fn, rename[0]))
4004 copies.append((fn, rename[0]))
4005
4005
4006 revmatchfn = None
4006 revmatchfn = None
4007 if opts.get('patch') or opts.get('stat'):
4007 if opts.get('patch') or opts.get('stat'):
4008 if opts.get('follow') or opts.get('follow_first'):
4008 if opts.get('follow') or opts.get('follow_first'):
4009 # note: this might be wrong when following through merges
4009 # note: this might be wrong when following through merges
4010 revmatchfn = scmutil.match(repo[None], fns, default='path')
4010 revmatchfn = scmutil.match(repo[None], fns, default='path')
4011 else:
4011 else:
4012 revmatchfn = matchfn
4012 revmatchfn = matchfn
4013
4013
4014 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4014 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4015
4015
4016 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4016 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4017 if count == limit:
4017 if count == limit:
4018 break
4018 break
4019 if displayer.flush(ctx.rev()):
4019 if displayer.flush(ctx.rev()):
4020 count += 1
4020 count += 1
4021 displayer.close()
4021 displayer.close()
4022
4022
4023 @command('manifest',
4023 @command('manifest',
4024 [('r', 'rev', '', _('revision to display'), _('REV')),
4024 [('r', 'rev', '', _('revision to display'), _('REV')),
4025 ('', 'all', False, _("list files from all revisions"))],
4025 ('', 'all', False, _("list files from all revisions"))],
4026 _('[-r REV]'))
4026 _('[-r REV]'))
4027 def manifest(ui, repo, node=None, rev=None, **opts):
4027 def manifest(ui, repo, node=None, rev=None, **opts):
4028 """output the current or given revision of the project manifest
4028 """output the current or given revision of the project manifest
4029
4029
4030 Print a list of version controlled files for the given revision.
4030 Print a list of version controlled files for the given revision.
4031 If no revision is given, the first parent of the working directory
4031 If no revision is given, the first parent of the working directory
4032 is used, or the null revision if no revision is checked out.
4032 is used, or the null revision if no revision is checked out.
4033
4033
4034 With -v, print file permissions, symlink and executable bits.
4034 With -v, print file permissions, symlink and executable bits.
4035 With --debug, print file revision hashes.
4035 With --debug, print file revision hashes.
4036
4036
4037 If option --all is specified, the list of all files from all revisions
4037 If option --all is specified, the list of all files from all revisions
4038 is printed. This includes deleted and renamed files.
4038 is printed. This includes deleted and renamed files.
4039
4039
4040 Returns 0 on success.
4040 Returns 0 on success.
4041 """
4041 """
4042 if opts.get('all'):
4042 if opts.get('all'):
4043 if rev or node:
4043 if rev or node:
4044 raise util.Abort(_("can't specify a revision with --all"))
4044 raise util.Abort(_("can't specify a revision with --all"))
4045
4045
4046 res = []
4046 res = []
4047 prefix = "data/"
4047 prefix = "data/"
4048 suffix = ".i"
4048 suffix = ".i"
4049 plen = len(prefix)
4049 plen = len(prefix)
4050 slen = len(suffix)
4050 slen = len(suffix)
4051 lock = repo.lock()
4051 lock = repo.lock()
4052 try:
4052 try:
4053 for fn, b, size in repo.store.datafiles():
4053 for fn, b, size in repo.store.datafiles():
4054 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4054 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4055 res.append(fn[plen:-slen])
4055 res.append(fn[plen:-slen])
4056 finally:
4056 finally:
4057 lock.release()
4057 lock.release()
4058 for f in sorted(res):
4058 for f in sorted(res):
4059 ui.write("%s\n" % f)
4059 ui.write("%s\n" % f)
4060 return
4060 return
4061
4061
4062 if rev and node:
4062 if rev and node:
4063 raise util.Abort(_("please specify just one revision"))
4063 raise util.Abort(_("please specify just one revision"))
4064
4064
4065 if not node:
4065 if not node:
4066 node = rev
4066 node = rev
4067
4067
4068 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4068 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4069 ctx = scmutil.revsingle(repo, node)
4069 ctx = scmutil.revsingle(repo, node)
4070 for f in ctx:
4070 for f in ctx:
4071 if ui.debugflag:
4071 if ui.debugflag:
4072 ui.write("%40s " % hex(ctx.manifest()[f]))
4072 ui.write("%40s " % hex(ctx.manifest()[f]))
4073 if ui.verbose:
4073 if ui.verbose:
4074 ui.write(decor[ctx.flags(f)])
4074 ui.write(decor[ctx.flags(f)])
4075 ui.write("%s\n" % f)
4075 ui.write("%s\n" % f)
4076
4076
4077 @command('^merge',
4077 @command('^merge',
4078 [('f', 'force', None, _('force a merge with outstanding changes')),
4078 [('f', 'force', None, _('force a merge with outstanding changes')),
4079 ('r', 'rev', '', _('revision to merge'), _('REV')),
4079 ('r', 'rev', '', _('revision to merge'), _('REV')),
4080 ('P', 'preview', None,
4080 ('P', 'preview', None,
4081 _('review revisions to merge (no merge is performed)'))
4081 _('review revisions to merge (no merge is performed)'))
4082 ] + mergetoolopts,
4082 ] + mergetoolopts,
4083 _('[-P] [-f] [[-r] REV]'))
4083 _('[-P] [-f] [[-r] REV]'))
4084 def merge(ui, repo, node=None, **opts):
4084 def merge(ui, repo, node=None, **opts):
4085 """merge working directory with another revision
4085 """merge working directory with another revision
4086
4086
4087 The current working directory is updated with all changes made in
4087 The current working directory is updated with all changes made in
4088 the requested revision since the last common predecessor revision.
4088 the requested revision since the last common predecessor revision.
4089
4089
4090 Files that changed between either parent are marked as changed for
4090 Files that changed between either parent are marked as changed for
4091 the next commit and a commit must be performed before any further
4091 the next commit and a commit must be performed before any further
4092 updates to the repository are allowed. The next commit will have
4092 updates to the repository are allowed. The next commit will have
4093 two parents.
4093 two parents.
4094
4094
4095 ``--tool`` can be used to specify the merge tool used for file
4095 ``--tool`` can be used to specify the merge tool used for file
4096 merges. It overrides the HGMERGE environment variable and your
4096 merges. It overrides the HGMERGE environment variable and your
4097 configuration files. See :hg:`help merge-tools` for options.
4097 configuration files. See :hg:`help merge-tools` for options.
4098
4098
4099 If no revision is specified, the working directory's parent is a
4099 If no revision is specified, the working directory's parent is a
4100 head revision, and the current branch contains exactly one other
4100 head revision, and the current branch contains exactly one other
4101 head, the other head is merged with by default. Otherwise, an
4101 head, the other head is merged with by default. Otherwise, an
4102 explicit revision with which to merge with must be provided.
4102 explicit revision with which to merge with must be provided.
4103
4103
4104 :hg:`resolve` must be used to resolve unresolved files.
4104 :hg:`resolve` must be used to resolve unresolved files.
4105
4105
4106 To undo an uncommitted merge, use :hg:`update --clean .` which
4106 To undo an uncommitted merge, use :hg:`update --clean .` which
4107 will check out a clean copy of the original merge parent, losing
4107 will check out a clean copy of the original merge parent, losing
4108 all changes.
4108 all changes.
4109
4109
4110 Returns 0 on success, 1 if there are unresolved files.
4110 Returns 0 on success, 1 if there are unresolved files.
4111 """
4111 """
4112
4112
4113 if opts.get('rev') and node:
4113 if opts.get('rev') and node:
4114 raise util.Abort(_("please specify just one revision"))
4114 raise util.Abort(_("please specify just one revision"))
4115 if not node:
4115 if not node:
4116 node = opts.get('rev')
4116 node = opts.get('rev')
4117
4117
4118 if not node:
4118 if not node:
4119 branch = repo[None].branch()
4119 branch = repo[None].branch()
4120 bheads = repo.branchheads(branch)
4120 bheads = repo.branchheads(branch)
4121 if len(bheads) > 2:
4121 if len(bheads) > 2:
4122 raise util.Abort(_("branch '%s' has %d heads - "
4122 raise util.Abort(_("branch '%s' has %d heads - "
4123 "please merge with an explicit rev")
4123 "please merge with an explicit rev")
4124 % (branch, len(bheads)),
4124 % (branch, len(bheads)),
4125 hint=_("run 'hg heads .' to see heads"))
4125 hint=_("run 'hg heads .' to see heads"))
4126
4126
4127 parent = repo.dirstate.p1()
4127 parent = repo.dirstate.p1()
4128 if len(bheads) == 1:
4128 if len(bheads) == 1:
4129 if len(repo.heads()) > 1:
4129 if len(repo.heads()) > 1:
4130 raise util.Abort(_("branch '%s' has one head - "
4130 raise util.Abort(_("branch '%s' has one head - "
4131 "please merge with an explicit rev")
4131 "please merge with an explicit rev")
4132 % branch,
4132 % branch,
4133 hint=_("run 'hg heads' to see all heads"))
4133 hint=_("run 'hg heads' to see all heads"))
4134 msg, hint = _('nothing to merge'), None
4134 msg, hint = _('nothing to merge'), None
4135 if parent != repo.lookup(branch):
4135 if parent != repo.lookup(branch):
4136 hint = _("use 'hg update' instead")
4136 hint = _("use 'hg update' instead")
4137 raise util.Abort(msg, hint=hint)
4137 raise util.Abort(msg, hint=hint)
4138
4138
4139 if parent not in bheads:
4139 if parent not in bheads:
4140 raise util.Abort(_('working directory not at a head revision'),
4140 raise util.Abort(_('working directory not at a head revision'),
4141 hint=_("use 'hg update' or merge with an "
4141 hint=_("use 'hg update' or merge with an "
4142 "explicit revision"))
4142 "explicit revision"))
4143 node = parent == bheads[0] and bheads[-1] or bheads[0]
4143 node = parent == bheads[0] and bheads[-1] or bheads[0]
4144 else:
4144 else:
4145 node = scmutil.revsingle(repo, node).node()
4145 node = scmutil.revsingle(repo, node).node()
4146
4146
4147 if opts.get('preview'):
4147 if opts.get('preview'):
4148 # find nodes that are ancestors of p2 but not of p1
4148 # find nodes that are ancestors of p2 but not of p1
4149 p1 = repo.lookup('.')
4149 p1 = repo.lookup('.')
4150 p2 = repo.lookup(node)
4150 p2 = repo.lookup(node)
4151 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4151 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4152
4152
4153 displayer = cmdutil.show_changeset(ui, repo, opts)
4153 displayer = cmdutil.show_changeset(ui, repo, opts)
4154 for node in nodes:
4154 for node in nodes:
4155 displayer.show(repo[node])
4155 displayer.show(repo[node])
4156 displayer.close()
4156 displayer.close()
4157 return 0
4157 return 0
4158
4158
4159 try:
4159 try:
4160 # ui.forcemerge is an internal variable, do not document
4160 # ui.forcemerge is an internal variable, do not document
4161 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4161 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4162 return hg.merge(repo, node, force=opts.get('force'))
4162 return hg.merge(repo, node, force=opts.get('force'))
4163 finally:
4163 finally:
4164 ui.setconfig('ui', 'forcemerge', '')
4164 ui.setconfig('ui', 'forcemerge', '')
4165
4165
4166 @command('outgoing|out',
4166 @command('outgoing|out',
4167 [('f', 'force', None, _('run even when the destination is unrelated')),
4167 [('f', 'force', None, _('run even when the destination is unrelated')),
4168 ('r', 'rev', [],
4168 ('r', 'rev', [],
4169 _('a changeset intended to be included in the destination'), _('REV')),
4169 _('a changeset intended to be included in the destination'), _('REV')),
4170 ('n', 'newest-first', None, _('show newest record first')),
4170 ('n', 'newest-first', None, _('show newest record first')),
4171 ('B', 'bookmarks', False, _('compare bookmarks')),
4171 ('B', 'bookmarks', False, _('compare bookmarks')),
4172 ('b', 'branch', [], _('a specific branch you would like to push'),
4172 ('b', 'branch', [], _('a specific branch you would like to push'),
4173 _('BRANCH')),
4173 _('BRANCH')),
4174 ] + logopts + remoteopts + subrepoopts,
4174 ] + logopts + remoteopts + subrepoopts,
4175 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4175 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4176 def outgoing(ui, repo, dest=None, **opts):
4176 def outgoing(ui, repo, dest=None, **opts):
4177 """show changesets not found in the destination
4177 """show changesets not found in the destination
4178
4178
4179 Show changesets not found in the specified destination repository
4179 Show changesets not found in the specified destination repository
4180 or the default push location. These are the changesets that would
4180 or the default push location. These are the changesets that would
4181 be pushed if a push was requested.
4181 be pushed if a push was requested.
4182
4182
4183 See pull for details of valid destination formats.
4183 See pull for details of valid destination formats.
4184
4184
4185 Returns 0 if there are outgoing changes, 1 otherwise.
4185 Returns 0 if there are outgoing changes, 1 otherwise.
4186 """
4186 """
4187
4187
4188 if opts.get('bookmarks'):
4188 if opts.get('bookmarks'):
4189 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4189 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4190 dest, branches = hg.parseurl(dest, opts.get('branch'))
4190 dest, branches = hg.parseurl(dest, opts.get('branch'))
4191 other = hg.peer(repo, opts, dest)
4191 other = hg.peer(repo, opts, dest)
4192 if 'bookmarks' not in other.listkeys('namespaces'):
4192 if 'bookmarks' not in other.listkeys('namespaces'):
4193 ui.warn(_("remote doesn't support bookmarks\n"))
4193 ui.warn(_("remote doesn't support bookmarks\n"))
4194 return 0
4194 return 0
4195 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4195 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4196 return bookmarks.diff(ui, other, repo)
4196 return bookmarks.diff(ui, other, repo)
4197
4197
4198 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4198 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4199 try:
4199 try:
4200 return hg.outgoing(ui, repo, dest, opts)
4200 return hg.outgoing(ui, repo, dest, opts)
4201 finally:
4201 finally:
4202 del repo._subtoppath
4202 del repo._subtoppath
4203
4203
4204 @command('parents',
4204 @command('parents',
4205 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4205 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4206 ] + templateopts,
4206 ] + templateopts,
4207 _('[-r REV] [FILE]'))
4207 _('[-r REV] [FILE]'))
4208 def parents(ui, repo, file_=None, **opts):
4208 def parents(ui, repo, file_=None, **opts):
4209 """show the parents of the working directory or revision
4209 """show the parents of the working directory or revision
4210
4210
4211 Print the working directory's parent revisions. If a revision is
4211 Print the working directory's parent revisions. If a revision is
4212 given via -r/--rev, the parent of that revision will be printed.
4212 given via -r/--rev, the parent of that revision will be printed.
4213 If a file argument is given, the revision in which the file was
4213 If a file argument is given, the revision in which the file was
4214 last changed (before the working directory revision or the
4214 last changed (before the working directory revision or the
4215 argument to --rev if given) is printed.
4215 argument to --rev if given) is printed.
4216
4216
4217 Returns 0 on success.
4217 Returns 0 on success.
4218 """
4218 """
4219
4219
4220 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4220 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4221
4221
4222 if file_:
4222 if file_:
4223 m = scmutil.match(ctx, (file_,), opts)
4223 m = scmutil.match(ctx, (file_,), opts)
4224 if m.anypats() or len(m.files()) != 1:
4224 if m.anypats() or len(m.files()) != 1:
4225 raise util.Abort(_('can only specify an explicit filename'))
4225 raise util.Abort(_('can only specify an explicit filename'))
4226 file_ = m.files()[0]
4226 file_ = m.files()[0]
4227 filenodes = []
4227 filenodes = []
4228 for cp in ctx.parents():
4228 for cp in ctx.parents():
4229 if not cp:
4229 if not cp:
4230 continue
4230 continue
4231 try:
4231 try:
4232 filenodes.append(cp.filenode(file_))
4232 filenodes.append(cp.filenode(file_))
4233 except error.LookupError:
4233 except error.LookupError:
4234 pass
4234 pass
4235 if not filenodes:
4235 if not filenodes:
4236 raise util.Abort(_("'%s' not found in manifest!") % file_)
4236 raise util.Abort(_("'%s' not found in manifest!") % file_)
4237 fl = repo.file(file_)
4237 fl = repo.file(file_)
4238 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4238 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4239 else:
4239 else:
4240 p = [cp.node() for cp in ctx.parents()]
4240 p = [cp.node() for cp in ctx.parents()]
4241
4241
4242 displayer = cmdutil.show_changeset(ui, repo, opts)
4242 displayer = cmdutil.show_changeset(ui, repo, opts)
4243 for n in p:
4243 for n in p:
4244 if n != nullid:
4244 if n != nullid:
4245 displayer.show(repo[n])
4245 displayer.show(repo[n])
4246 displayer.close()
4246 displayer.close()
4247
4247
4248 @command('paths', [], _('[NAME]'))
4248 @command('paths', [], _('[NAME]'))
4249 def paths(ui, repo, search=None):
4249 def paths(ui, repo, search=None):
4250 """show aliases for remote repositories
4250 """show aliases for remote repositories
4251
4251
4252 Show definition of symbolic path name NAME. If no name is given,
4252 Show definition of symbolic path name NAME. If no name is given,
4253 show definition of all available names.
4253 show definition of all available names.
4254
4254
4255 Option -q/--quiet suppresses all output when searching for NAME
4255 Option -q/--quiet suppresses all output when searching for NAME
4256 and shows only the path names when listing all definitions.
4256 and shows only the path names when listing all definitions.
4257
4257
4258 Path names are defined in the [paths] section of your
4258 Path names are defined in the [paths] section of your
4259 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4259 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4260 repository, ``.hg/hgrc`` is used, too.
4260 repository, ``.hg/hgrc`` is used, too.
4261
4261
4262 The path names ``default`` and ``default-push`` have a special
4262 The path names ``default`` and ``default-push`` have a special
4263 meaning. When performing a push or pull operation, they are used
4263 meaning. When performing a push or pull operation, they are used
4264 as fallbacks if no location is specified on the command-line.
4264 as fallbacks if no location is specified on the command-line.
4265 When ``default-push`` is set, it will be used for push and
4265 When ``default-push`` is set, it will be used for push and
4266 ``default`` will be used for pull; otherwise ``default`` is used
4266 ``default`` will be used for pull; otherwise ``default`` is used
4267 as the fallback for both. When cloning a repository, the clone
4267 as the fallback for both. When cloning a repository, the clone
4268 source is written as ``default`` in ``.hg/hgrc``. Note that
4268 source is written as ``default`` in ``.hg/hgrc``. Note that
4269 ``default`` and ``default-push`` apply to all inbound (e.g.
4269 ``default`` and ``default-push`` apply to all inbound (e.g.
4270 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4270 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4271 :hg:`bundle`) operations.
4271 :hg:`bundle`) operations.
4272
4272
4273 See :hg:`help urls` for more information.
4273 See :hg:`help urls` for more information.
4274
4274
4275 Returns 0 on success.
4275 Returns 0 on success.
4276 """
4276 """
4277 if search:
4277 if search:
4278 for name, path in ui.configitems("paths"):
4278 for name, path in ui.configitems("paths"):
4279 if name == search:
4279 if name == search:
4280 ui.status("%s\n" % util.hidepassword(path))
4280 ui.status("%s\n" % util.hidepassword(path))
4281 return
4281 return
4282 if not ui.quiet:
4282 if not ui.quiet:
4283 ui.warn(_("not found!\n"))
4283 ui.warn(_("not found!\n"))
4284 return 1
4284 return 1
4285 else:
4285 else:
4286 for name, path in ui.configitems("paths"):
4286 for name, path in ui.configitems("paths"):
4287 if ui.quiet:
4287 if ui.quiet:
4288 ui.write("%s\n" % name)
4288 ui.write("%s\n" % name)
4289 else:
4289 else:
4290 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4290 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4291
4291
4292 @command('^phase',
4292 @command('^phase',
4293 [('p', 'public', False, _('set changeset phase to public')),
4293 [('p', 'public', False, _('set changeset phase to public')),
4294 ('d', 'draft', False, _('set changeset phase to draft')),
4294 ('d', 'draft', False, _('set changeset phase to draft')),
4295 ('s', 'secret', False, _('set changeset phase to secret')),
4295 ('s', 'secret', False, _('set changeset phase to secret')),
4296 ('f', 'force', False, _('allow to move boundary backward')),
4296 ('f', 'force', False, _('allow to move boundary backward')),
4297 ('r', 'rev', [], _('target revision'), _('REV')),
4297 ('r', 'rev', [], _('target revision'), _('REV')),
4298 ],
4298 ],
4299 _('[-p|-d|-s] [-f] [-r] REV...'))
4299 _('[-p|-d|-s] [-f] [-r] REV...'))
4300 def phase(ui, repo, *revs, **opts):
4300 def phase(ui, repo, *revs, **opts):
4301 """set or show the current phase name
4301 """set or show the current phase name
4302
4302
4303 With no argument, show the phase name of specified revisions.
4303 With no argument, show the phase name of specified revisions.
4304
4304
4305 With one of -p/--public, -d/--draft or -s/--secret, change the
4305 With one of -p/--public, -d/--draft or -s/--secret, change the
4306 phase value of the specified revisions.
4306 phase value of the specified revisions.
4307
4307
4308 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4308 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4309 lower phase to an higher phase. Phases are ordered as follows::
4309 lower phase to an higher phase. Phases are ordered as follows::
4310
4310
4311 public < draft < secret
4311 public < draft < secret
4312
4312
4313 Return 0 on success, 1 if no phases were changed or some could not
4313 Return 0 on success, 1 if no phases were changed or some could not
4314 be changed.
4314 be changed.
4315 """
4315 """
4316 # search for a unique phase argument
4316 # search for a unique phase argument
4317 targetphase = None
4317 targetphase = None
4318 for idx, name in enumerate(phases.phasenames):
4318 for idx, name in enumerate(phases.phasenames):
4319 if opts[name]:
4319 if opts[name]:
4320 if targetphase is not None:
4320 if targetphase is not None:
4321 raise util.Abort(_('only one phase can be specified'))
4321 raise util.Abort(_('only one phase can be specified'))
4322 targetphase = idx
4322 targetphase = idx
4323
4323
4324 # look for specified revision
4324 # look for specified revision
4325 revs = list(revs)
4325 revs = list(revs)
4326 revs.extend(opts['rev'])
4326 revs.extend(opts['rev'])
4327 if not revs:
4327 if not revs:
4328 raise util.Abort(_('no revisions specified'))
4328 raise util.Abort(_('no revisions specified'))
4329
4329
4330 revs = scmutil.revrange(repo, revs)
4330 revs = scmutil.revrange(repo, revs)
4331
4331
4332 lock = None
4332 lock = None
4333 ret = 0
4333 ret = 0
4334 if targetphase is None:
4334 if targetphase is None:
4335 # display
4335 # display
4336 for r in revs:
4336 for r in revs:
4337 ctx = repo[r]
4337 ctx = repo[r]
4338 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4338 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4339 else:
4339 else:
4340 lock = repo.lock()
4340 lock = repo.lock()
4341 try:
4341 try:
4342 # set phase
4342 # set phase
4343 nodes = [ctx.node() for ctx in repo.set('%ld', revs)]
4343 nodes = [ctx.node() for ctx in repo.set('%ld', revs)]
4344 if not nodes:
4344 if not nodes:
4345 raise util.Abort(_('empty revision set'))
4345 raise util.Abort(_('empty revision set'))
4346 olddata = repo._phaserev[:]
4346 olddata = repo._phaserev[:]
4347 phases.advanceboundary(repo, targetphase, nodes)
4347 phases.advanceboundary(repo, targetphase, nodes)
4348 if opts['force']:
4348 if opts['force']:
4349 phases.retractboundary(repo, targetphase, nodes)
4349 phases.retractboundary(repo, targetphase, nodes)
4350 finally:
4350 finally:
4351 lock.release()
4351 lock.release()
4352 if olddata is not None:
4352 if olddata is not None:
4353 changes = 0
4353 changes = 0
4354 newdata = repo._phaserev
4354 newdata = repo._phaserev
4355 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4355 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4356 rejected = [n for n in nodes
4356 rejected = [n for n in nodes
4357 if newdata[repo[n].rev()] < targetphase]
4357 if newdata[repo[n].rev()] < targetphase]
4358 if rejected:
4358 if rejected:
4359 ui.warn(_('cannot move %i changesets to a more permissive '
4359 ui.warn(_('cannot move %i changesets to a more permissive '
4360 'phase, use --force\n') % len(rejected))
4360 'phase, use --force\n') % len(rejected))
4361 ret = 1
4361 ret = 1
4362 if changes:
4362 if changes:
4363 msg = _('phase changed for %i changesets\n') % changes
4363 msg = _('phase changed for %i changesets\n') % changes
4364 if ret:
4364 if ret:
4365 ui.status(msg)
4365 ui.status(msg)
4366 else:
4366 else:
4367 ui.note(msg)
4367 ui.note(msg)
4368 else:
4368 else:
4369 ui.warn(_('no phases changed\n'))
4369 ui.warn(_('no phases changed\n'))
4370 ret = 1
4370 ret = 1
4371 return ret
4371 return ret
4372
4372
4373 def postincoming(ui, repo, modheads, optupdate, checkout):
4373 def postincoming(ui, repo, modheads, optupdate, checkout):
4374 if modheads == 0:
4374 if modheads == 0:
4375 return
4375 return
4376 if optupdate:
4376 if optupdate:
4377 movemarkfrom = repo['.'].node()
4377 movemarkfrom = repo['.'].node()
4378 try:
4378 try:
4379 ret = hg.update(repo, checkout)
4379 ret = hg.update(repo, checkout)
4380 except util.Abort, inst:
4380 except util.Abort, inst:
4381 ui.warn(_("not updating: %s\n") % str(inst))
4381 ui.warn(_("not updating: %s\n") % str(inst))
4382 return 0
4382 return 0
4383 if not ret and not checkout:
4383 if not ret and not checkout:
4384 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4384 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4385 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4385 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4386 return ret
4386 return ret
4387 if modheads > 1:
4387 if modheads > 1:
4388 currentbranchheads = len(repo.branchheads())
4388 currentbranchheads = len(repo.branchheads())
4389 if currentbranchheads == modheads:
4389 if currentbranchheads == modheads:
4390 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4390 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4391 elif currentbranchheads > 1:
4391 elif currentbranchheads > 1:
4392 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
4392 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
4393 else:
4393 else:
4394 ui.status(_("(run 'hg heads' to see heads)\n"))
4394 ui.status(_("(run 'hg heads' to see heads)\n"))
4395 else:
4395 else:
4396 ui.status(_("(run 'hg update' to get a working copy)\n"))
4396 ui.status(_("(run 'hg update' to get a working copy)\n"))
4397
4397
4398 @command('^pull',
4398 @command('^pull',
4399 [('u', 'update', None,
4399 [('u', 'update', None,
4400 _('update to new branch head if changesets were pulled')),
4400 _('update to new branch head if changesets were pulled')),
4401 ('f', 'force', None, _('run even when remote repository is unrelated')),
4401 ('f', 'force', None, _('run even when remote repository is unrelated')),
4402 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4402 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4403 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4403 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4404 ('b', 'branch', [], _('a specific branch you would like to pull'),
4404 ('b', 'branch', [], _('a specific branch you would like to pull'),
4405 _('BRANCH')),
4405 _('BRANCH')),
4406 ] + remoteopts,
4406 ] + remoteopts,
4407 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4407 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4408 def pull(ui, repo, source="default", **opts):
4408 def pull(ui, repo, source="default", **opts):
4409 """pull changes from the specified source
4409 """pull changes from the specified source
4410
4410
4411 Pull changes from a remote repository to a local one.
4411 Pull changes from a remote repository to a local one.
4412
4412
4413 This finds all changes from the repository at the specified path
4413 This finds all changes from the repository at the specified path
4414 or URL and adds them to a local repository (the current one unless
4414 or URL and adds them to a local repository (the current one unless
4415 -R is specified). By default, this does not update the copy of the
4415 -R is specified). By default, this does not update the copy of the
4416 project in the working directory.
4416 project in the working directory.
4417
4417
4418 Use :hg:`incoming` if you want to see what would have been added
4418 Use :hg:`incoming` if you want to see what would have been added
4419 by a pull at the time you issued this command. If you then decide
4419 by a pull at the time you issued this command. If you then decide
4420 to add those changes to the repository, you should use :hg:`pull
4420 to add those changes to the repository, you should use :hg:`pull
4421 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4421 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4422
4422
4423 If SOURCE is omitted, the 'default' path will be used.
4423 If SOURCE is omitted, the 'default' path will be used.
4424 See :hg:`help urls` for more information.
4424 See :hg:`help urls` for more information.
4425
4425
4426 Returns 0 on success, 1 if an update had unresolved files.
4426 Returns 0 on success, 1 if an update had unresolved files.
4427 """
4427 """
4428 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4428 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4429 other = hg.peer(repo, opts, source)
4429 other = hg.peer(repo, opts, source)
4430 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4430 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4431 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4431 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4432
4432
4433 if opts.get('bookmark'):
4433 if opts.get('bookmark'):
4434 if not revs:
4434 if not revs:
4435 revs = []
4435 revs = []
4436 rb = other.listkeys('bookmarks')
4436 rb = other.listkeys('bookmarks')
4437 for b in opts['bookmark']:
4437 for b in opts['bookmark']:
4438 if b not in rb:
4438 if b not in rb:
4439 raise util.Abort(_('remote bookmark %s not found!') % b)
4439 raise util.Abort(_('remote bookmark %s not found!') % b)
4440 revs.append(rb[b])
4440 revs.append(rb[b])
4441
4441
4442 if revs:
4442 if revs:
4443 try:
4443 try:
4444 revs = [other.lookup(rev) for rev in revs]
4444 revs = [other.lookup(rev) for rev in revs]
4445 except error.CapabilityError:
4445 except error.CapabilityError:
4446 err = _("other repository doesn't support revision lookup, "
4446 err = _("other repository doesn't support revision lookup, "
4447 "so a rev cannot be specified.")
4447 "so a rev cannot be specified.")
4448 raise util.Abort(err)
4448 raise util.Abort(err)
4449
4449
4450 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4450 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4451 bookmarks.updatefromremote(ui, repo, other, source)
4451 bookmarks.updatefromremote(ui, repo, other, source)
4452 if checkout:
4452 if checkout:
4453 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4453 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4454 repo._subtoppath = source
4454 repo._subtoppath = source
4455 try:
4455 try:
4456 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4456 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4457
4457
4458 finally:
4458 finally:
4459 del repo._subtoppath
4459 del repo._subtoppath
4460
4460
4461 # update specified bookmarks
4461 # update specified bookmarks
4462 if opts.get('bookmark'):
4462 if opts.get('bookmark'):
4463 for b in opts['bookmark']:
4463 for b in opts['bookmark']:
4464 # explicit pull overrides local bookmark if any
4464 # explicit pull overrides local bookmark if any
4465 ui.status(_("importing bookmark %s\n") % b)
4465 ui.status(_("importing bookmark %s\n") % b)
4466 repo._bookmarks[b] = repo[rb[b]].node()
4466 repo._bookmarks[b] = repo[rb[b]].node()
4467 bookmarks.write(repo)
4467 bookmarks.write(repo)
4468
4468
4469 return ret
4469 return ret
4470
4470
4471 @command('^push',
4471 @command('^push',
4472 [('f', 'force', None, _('force push')),
4472 [('f', 'force', None, _('force push')),
4473 ('r', 'rev', [],
4473 ('r', 'rev', [],
4474 _('a changeset intended to be included in the destination'),
4474 _('a changeset intended to be included in the destination'),
4475 _('REV')),
4475 _('REV')),
4476 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4476 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4477 ('b', 'branch', [],
4477 ('b', 'branch', [],
4478 _('a specific branch you would like to push'), _('BRANCH')),
4478 _('a specific branch you would like to push'), _('BRANCH')),
4479 ('', 'new-branch', False, _('allow pushing a new branch')),
4479 ('', 'new-branch', False, _('allow pushing a new branch')),
4480 ] + remoteopts,
4480 ] + remoteopts,
4481 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4481 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4482 def push(ui, repo, dest=None, **opts):
4482 def push(ui, repo, dest=None, **opts):
4483 """push changes to the specified destination
4483 """push changes to the specified destination
4484
4484
4485 Push changesets from the local repository to the specified
4485 Push changesets from the local repository to the specified
4486 destination.
4486 destination.
4487
4487
4488 This operation is symmetrical to pull: it is identical to a pull
4488 This operation is symmetrical to pull: it is identical to a pull
4489 in the destination repository from the current one.
4489 in the destination repository from the current one.
4490
4490
4491 By default, push will not allow creation of new heads at the
4491 By default, push will not allow creation of new heads at the
4492 destination, since multiple heads would make it unclear which head
4492 destination, since multiple heads would make it unclear which head
4493 to use. In this situation, it is recommended to pull and merge
4493 to use. In this situation, it is recommended to pull and merge
4494 before pushing.
4494 before pushing.
4495
4495
4496 Use --new-branch if you want to allow push to create a new named
4496 Use --new-branch if you want to allow push to create a new named
4497 branch that is not present at the destination. This allows you to
4497 branch that is not present at the destination. This allows you to
4498 only create a new branch without forcing other changes.
4498 only create a new branch without forcing other changes.
4499
4499
4500 Use -f/--force to override the default behavior and push all
4500 Use -f/--force to override the default behavior and push all
4501 changesets on all branches.
4501 changesets on all branches.
4502
4502
4503 If -r/--rev is used, the specified revision and all its ancestors
4503 If -r/--rev is used, the specified revision and all its ancestors
4504 will be pushed to the remote repository.
4504 will be pushed to the remote repository.
4505
4505
4506 Please see :hg:`help urls` for important details about ``ssh://``
4506 Please see :hg:`help urls` for important details about ``ssh://``
4507 URLs. If DESTINATION is omitted, a default path will be used.
4507 URLs. If DESTINATION is omitted, a default path will be used.
4508
4508
4509 Returns 0 if push was successful, 1 if nothing to push.
4509 Returns 0 if push was successful, 1 if nothing to push.
4510 """
4510 """
4511
4511
4512 if opts.get('bookmark'):
4512 if opts.get('bookmark'):
4513 for b in opts['bookmark']:
4513 for b in opts['bookmark']:
4514 # translate -B options to -r so changesets get pushed
4514 # translate -B options to -r so changesets get pushed
4515 if b in repo._bookmarks:
4515 if b in repo._bookmarks:
4516 opts.setdefault('rev', []).append(b)
4516 opts.setdefault('rev', []).append(b)
4517 else:
4517 else:
4518 # if we try to push a deleted bookmark, translate it to null
4518 # if we try to push a deleted bookmark, translate it to null
4519 # this lets simultaneous -r, -b options continue working
4519 # this lets simultaneous -r, -b options continue working
4520 opts.setdefault('rev', []).append("null")
4520 opts.setdefault('rev', []).append("null")
4521
4521
4522 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4522 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4523 dest, branches = hg.parseurl(dest, opts.get('branch'))
4523 dest, branches = hg.parseurl(dest, opts.get('branch'))
4524 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4524 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4525 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4525 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4526 other = hg.peer(repo, opts, dest)
4526 other = hg.peer(repo, opts, dest)
4527 if revs:
4527 if revs:
4528 revs = [repo.lookup(rev) for rev in revs]
4528 revs = [repo.lookup(rev) for rev in revs]
4529
4529
4530 repo._subtoppath = dest
4530 repo._subtoppath = dest
4531 try:
4531 try:
4532 # push subrepos depth-first for coherent ordering
4532 # push subrepos depth-first for coherent ordering
4533 c = repo['']
4533 c = repo['']
4534 subs = c.substate # only repos that are committed
4534 subs = c.substate # only repos that are committed
4535 for s in sorted(subs):
4535 for s in sorted(subs):
4536 if c.sub(s).push(opts) == 0:
4536 if c.sub(s).push(opts) == 0:
4537 return False
4537 return False
4538 finally:
4538 finally:
4539 del repo._subtoppath
4539 del repo._subtoppath
4540 result = repo.push(other, opts.get('force'), revs=revs,
4540 result = repo.push(other, opts.get('force'), revs=revs,
4541 newbranch=opts.get('new_branch'))
4541 newbranch=opts.get('new_branch'))
4542
4542
4543 result = not result
4543 result = not result
4544
4544
4545 if opts.get('bookmark'):
4545 if opts.get('bookmark'):
4546 rb = other.listkeys('bookmarks')
4546 rb = other.listkeys('bookmarks')
4547 for b in opts['bookmark']:
4547 for b in opts['bookmark']:
4548 # explicit push overrides remote bookmark if any
4548 # explicit push overrides remote bookmark if any
4549 if b in repo._bookmarks:
4549 if b in repo._bookmarks:
4550 ui.status(_("exporting bookmark %s\n") % b)
4550 ui.status(_("exporting bookmark %s\n") % b)
4551 new = repo[b].hex()
4551 new = repo[b].hex()
4552 elif b in rb:
4552 elif b in rb:
4553 ui.status(_("deleting remote bookmark %s\n") % b)
4553 ui.status(_("deleting remote bookmark %s\n") % b)
4554 new = '' # delete
4554 new = '' # delete
4555 else:
4555 else:
4556 ui.warn(_('bookmark %s does not exist on the local '
4556 ui.warn(_('bookmark %s does not exist on the local '
4557 'or remote repository!\n') % b)
4557 'or remote repository!\n') % b)
4558 return 2
4558 return 2
4559 old = rb.get(b, '')
4559 old = rb.get(b, '')
4560 r = other.pushkey('bookmarks', b, old, new)
4560 r = other.pushkey('bookmarks', b, old, new)
4561 if not r:
4561 if not r:
4562 ui.warn(_('updating bookmark %s failed!\n') % b)
4562 ui.warn(_('updating bookmark %s failed!\n') % b)
4563 if not result:
4563 if not result:
4564 result = 2
4564 result = 2
4565
4565
4566 return result
4566 return result
4567
4567
4568 @command('recover', [])
4568 @command('recover', [])
4569 def recover(ui, repo):
4569 def recover(ui, repo):
4570 """roll back an interrupted transaction
4570 """roll back an interrupted transaction
4571
4571
4572 Recover from an interrupted commit or pull.
4572 Recover from an interrupted commit or pull.
4573
4573
4574 This command tries to fix the repository status after an
4574 This command tries to fix the repository status after an
4575 interrupted operation. It should only be necessary when Mercurial
4575 interrupted operation. It should only be necessary when Mercurial
4576 suggests it.
4576 suggests it.
4577
4577
4578 Returns 0 if successful, 1 if nothing to recover or verify fails.
4578 Returns 0 if successful, 1 if nothing to recover or verify fails.
4579 """
4579 """
4580 if repo.recover():
4580 if repo.recover():
4581 return hg.verify(repo)
4581 return hg.verify(repo)
4582 return 1
4582 return 1
4583
4583
4584 @command('^remove|rm',
4584 @command('^remove|rm',
4585 [('A', 'after', None, _('record delete for missing files')),
4585 [('A', 'after', None, _('record delete for missing files')),
4586 ('f', 'force', None,
4586 ('f', 'force', None,
4587 _('remove (and delete) file even if added or modified')),
4587 _('remove (and delete) file even if added or modified')),
4588 ] + walkopts,
4588 ] + walkopts,
4589 _('[OPTION]... FILE...'))
4589 _('[OPTION]... FILE...'))
4590 def remove(ui, repo, *pats, **opts):
4590 def remove(ui, repo, *pats, **opts):
4591 """remove the specified files on the next commit
4591 """remove the specified files on the next commit
4592
4592
4593 Schedule the indicated files for removal from the current branch.
4593 Schedule the indicated files for removal from the current branch.
4594
4594
4595 This command schedules the files to be removed at the next commit.
4595 This command schedules the files to be removed at the next commit.
4596 To undo a remove before that, see :hg:`revert`. To undo added
4596 To undo a remove before that, see :hg:`revert`. To undo added
4597 files, see :hg:`forget`.
4597 files, see :hg:`forget`.
4598
4598
4599 .. container:: verbose
4599 .. container:: verbose
4600
4600
4601 -A/--after can be used to remove only files that have already
4601 -A/--after can be used to remove only files that have already
4602 been deleted, -f/--force can be used to force deletion, and -Af
4602 been deleted, -f/--force can be used to force deletion, and -Af
4603 can be used to remove files from the next revision without
4603 can be used to remove files from the next revision without
4604 deleting them from the working directory.
4604 deleting them from the working directory.
4605
4605
4606 The following table details the behavior of remove for different
4606 The following table details the behavior of remove for different
4607 file states (columns) and option combinations (rows). The file
4607 file states (columns) and option combinations (rows). The file
4608 states are Added [A], Clean [C], Modified [M] and Missing [!]
4608 states are Added [A], Clean [C], Modified [M] and Missing [!]
4609 (as reported by :hg:`status`). The actions are Warn, Remove
4609 (as reported by :hg:`status`). The actions are Warn, Remove
4610 (from branch) and Delete (from disk):
4610 (from branch) and Delete (from disk):
4611
4611
4612 ======= == == == ==
4612 ======= == == == ==
4613 A C M !
4613 A C M !
4614 ======= == == == ==
4614 ======= == == == ==
4615 none W RD W R
4615 none W RD W R
4616 -f R RD RD R
4616 -f R RD RD R
4617 -A W W W R
4617 -A W W W R
4618 -Af R R R R
4618 -Af R R R R
4619 ======= == == == ==
4619 ======= == == == ==
4620
4620
4621 Note that remove never deletes files in Added [A] state from the
4621 Note that remove never deletes files in Added [A] state from the
4622 working directory, not even if option --force is specified.
4622 working directory, not even if option --force is specified.
4623
4623
4624 Returns 0 on success, 1 if any warnings encountered.
4624 Returns 0 on success, 1 if any warnings encountered.
4625 """
4625 """
4626
4626
4627 ret = 0
4627 ret = 0
4628 after, force = opts.get('after'), opts.get('force')
4628 after, force = opts.get('after'), opts.get('force')
4629 if not pats and not after:
4629 if not pats and not after:
4630 raise util.Abort(_('no files specified'))
4630 raise util.Abort(_('no files specified'))
4631
4631
4632 m = scmutil.match(repo[None], pats, opts)
4632 m = scmutil.match(repo[None], pats, opts)
4633 s = repo.status(match=m, clean=True)
4633 s = repo.status(match=m, clean=True)
4634 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4634 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4635
4635
4636 for f in m.files():
4636 for f in m.files():
4637 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4637 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4638 if os.path.exists(m.rel(f)):
4638 if os.path.exists(m.rel(f)):
4639 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4639 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4640 ret = 1
4640 ret = 1
4641
4641
4642 if force:
4642 if force:
4643 list = modified + deleted + clean + added
4643 list = modified + deleted + clean + added
4644 elif after:
4644 elif after:
4645 list = deleted
4645 list = deleted
4646 for f in modified + added + clean:
4646 for f in modified + added + clean:
4647 ui.warn(_('not removing %s: file still exists (use -f'
4647 ui.warn(_('not removing %s: file still exists (use -f'
4648 ' to force removal)\n') % m.rel(f))
4648 ' to force removal)\n') % m.rel(f))
4649 ret = 1
4649 ret = 1
4650 else:
4650 else:
4651 list = deleted + clean
4651 list = deleted + clean
4652 for f in modified:
4652 for f in modified:
4653 ui.warn(_('not removing %s: file is modified (use -f'
4653 ui.warn(_('not removing %s: file is modified (use -f'
4654 ' to force removal)\n') % m.rel(f))
4654 ' to force removal)\n') % m.rel(f))
4655 ret = 1
4655 ret = 1
4656 for f in added:
4656 for f in added:
4657 ui.warn(_('not removing %s: file has been marked for add'
4657 ui.warn(_('not removing %s: file has been marked for add'
4658 ' (use forget to undo)\n') % m.rel(f))
4658 ' (use forget to undo)\n') % m.rel(f))
4659 ret = 1
4659 ret = 1
4660
4660
4661 for f in sorted(list):
4661 for f in sorted(list):
4662 if ui.verbose or not m.exact(f):
4662 if ui.verbose or not m.exact(f):
4663 ui.status(_('removing %s\n') % m.rel(f))
4663 ui.status(_('removing %s\n') % m.rel(f))
4664
4664
4665 wlock = repo.wlock()
4665 wlock = repo.wlock()
4666 try:
4666 try:
4667 if not after:
4667 if not after:
4668 for f in list:
4668 for f in list:
4669 if f in added:
4669 if f in added:
4670 continue # we never unlink added files on remove
4670 continue # we never unlink added files on remove
4671 try:
4671 try:
4672 util.unlinkpath(repo.wjoin(f))
4672 util.unlinkpath(repo.wjoin(f))
4673 except OSError, inst:
4673 except OSError, inst:
4674 if inst.errno != errno.ENOENT:
4674 if inst.errno != errno.ENOENT:
4675 raise
4675 raise
4676 repo[None].forget(list)
4676 repo[None].forget(list)
4677 finally:
4677 finally:
4678 wlock.release()
4678 wlock.release()
4679
4679
4680 return ret
4680 return ret
4681
4681
4682 @command('rename|move|mv',
4682 @command('rename|move|mv',
4683 [('A', 'after', None, _('record a rename that has already occurred')),
4683 [('A', 'after', None, _('record a rename that has already occurred')),
4684 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4684 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4685 ] + walkopts + dryrunopts,
4685 ] + walkopts + dryrunopts,
4686 _('[OPTION]... SOURCE... DEST'))
4686 _('[OPTION]... SOURCE... DEST'))
4687 def rename(ui, repo, *pats, **opts):
4687 def rename(ui, repo, *pats, **opts):
4688 """rename files; equivalent of copy + remove
4688 """rename files; equivalent of copy + remove
4689
4689
4690 Mark dest as copies of sources; mark sources for deletion. If dest
4690 Mark dest as copies of sources; mark sources for deletion. If dest
4691 is a directory, copies are put in that directory. If dest is a
4691 is a directory, copies are put in that directory. If dest is a
4692 file, there can only be one source.
4692 file, there can only be one source.
4693
4693
4694 By default, this command copies the contents of files as they
4694 By default, this command copies the contents of files as they
4695 exist in the working directory. If invoked with -A/--after, the
4695 exist in the working directory. If invoked with -A/--after, the
4696 operation is recorded, but no copying is performed.
4696 operation is recorded, but no copying is performed.
4697
4697
4698 This command takes effect at the next commit. To undo a rename
4698 This command takes effect at the next commit. To undo a rename
4699 before that, see :hg:`revert`.
4699 before that, see :hg:`revert`.
4700
4700
4701 Returns 0 on success, 1 if errors are encountered.
4701 Returns 0 on success, 1 if errors are encountered.
4702 """
4702 """
4703 wlock = repo.wlock(False)
4703 wlock = repo.wlock(False)
4704 try:
4704 try:
4705 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4705 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4706 finally:
4706 finally:
4707 wlock.release()
4707 wlock.release()
4708
4708
4709 @command('resolve',
4709 @command('resolve',
4710 [('a', 'all', None, _('select all unresolved files')),
4710 [('a', 'all', None, _('select all unresolved files')),
4711 ('l', 'list', None, _('list state of files needing merge')),
4711 ('l', 'list', None, _('list state of files needing merge')),
4712 ('m', 'mark', None, _('mark files as resolved')),
4712 ('m', 'mark', None, _('mark files as resolved')),
4713 ('u', 'unmark', None, _('mark files as unresolved')),
4713 ('u', 'unmark', None, _('mark files as unresolved')),
4714 ('n', 'no-status', None, _('hide status prefix'))]
4714 ('n', 'no-status', None, _('hide status prefix'))]
4715 + mergetoolopts + walkopts,
4715 + mergetoolopts + walkopts,
4716 _('[OPTION]... [FILE]...'))
4716 _('[OPTION]... [FILE]...'))
4717 def resolve(ui, repo, *pats, **opts):
4717 def resolve(ui, repo, *pats, **opts):
4718 """redo merges or set/view the merge status of files
4718 """redo merges or set/view the merge status of files
4719
4719
4720 Merges with unresolved conflicts are often the result of
4720 Merges with unresolved conflicts are often the result of
4721 non-interactive merging using the ``internal:merge`` configuration
4721 non-interactive merging using the ``internal:merge`` configuration
4722 setting, or a command-line merge tool like ``diff3``. The resolve
4722 setting, or a command-line merge tool like ``diff3``. The resolve
4723 command is used to manage the files involved in a merge, after
4723 command is used to manage the files involved in a merge, after
4724 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4724 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4725 working directory must have two parents). See :hg:`help
4725 working directory must have two parents). See :hg:`help
4726 merge-tools` for information on configuring merge tools.
4726 merge-tools` for information on configuring merge tools.
4727
4727
4728 The resolve command can be used in the following ways:
4728 The resolve command can be used in the following ways:
4729
4729
4730 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4730 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4731 files, discarding any previous merge attempts. Re-merging is not
4731 files, discarding any previous merge attempts. Re-merging is not
4732 performed for files already marked as resolved. Use ``--all/-a``
4732 performed for files already marked as resolved. Use ``--all/-a``
4733 to select all unresolved files. ``--tool`` can be used to specify
4733 to select all unresolved files. ``--tool`` can be used to specify
4734 the merge tool used for the given files. It overrides the HGMERGE
4734 the merge tool used for the given files. It overrides the HGMERGE
4735 environment variable and your configuration files. Previous file
4735 environment variable and your configuration files. Previous file
4736 contents are saved with a ``.orig`` suffix.
4736 contents are saved with a ``.orig`` suffix.
4737
4737
4738 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4738 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4739 (e.g. after having manually fixed-up the files). The default is
4739 (e.g. after having manually fixed-up the files). The default is
4740 to mark all unresolved files.
4740 to mark all unresolved files.
4741
4741
4742 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4742 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4743 default is to mark all resolved files.
4743 default is to mark all resolved files.
4744
4744
4745 - :hg:`resolve -l`: list files which had or still have conflicts.
4745 - :hg:`resolve -l`: list files which had or still have conflicts.
4746 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4746 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4747
4747
4748 Note that Mercurial will not let you commit files with unresolved
4748 Note that Mercurial will not let you commit files with unresolved
4749 merge conflicts. You must use :hg:`resolve -m ...` before you can
4749 merge conflicts. You must use :hg:`resolve -m ...` before you can
4750 commit after a conflicting merge.
4750 commit after a conflicting merge.
4751
4751
4752 Returns 0 on success, 1 if any files fail a resolve attempt.
4752 Returns 0 on success, 1 if any files fail a resolve attempt.
4753 """
4753 """
4754
4754
4755 all, mark, unmark, show, nostatus = \
4755 all, mark, unmark, show, nostatus = \
4756 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4756 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4757
4757
4758 if (show and (mark or unmark)) or (mark and unmark):
4758 if (show and (mark or unmark)) or (mark and unmark):
4759 raise util.Abort(_("too many options specified"))
4759 raise util.Abort(_("too many options specified"))
4760 if pats and all:
4760 if pats and all:
4761 raise util.Abort(_("can't specify --all and patterns"))
4761 raise util.Abort(_("can't specify --all and patterns"))
4762 if not (all or pats or show or mark or unmark):
4762 if not (all or pats or show or mark or unmark):
4763 raise util.Abort(_('no files or directories specified; '
4763 raise util.Abort(_('no files or directories specified; '
4764 'use --all to remerge all files'))
4764 'use --all to remerge all files'))
4765
4765
4766 ms = mergemod.mergestate(repo)
4766 ms = mergemod.mergestate(repo)
4767 m = scmutil.match(repo[None], pats, opts)
4767 m = scmutil.match(repo[None], pats, opts)
4768 ret = 0
4768 ret = 0
4769
4769
4770 for f in ms:
4770 for f in ms:
4771 if m(f):
4771 if m(f):
4772 if show:
4772 if show:
4773 if nostatus:
4773 if nostatus:
4774 ui.write("%s\n" % f)
4774 ui.write("%s\n" % f)
4775 else:
4775 else:
4776 ui.write("%s %s\n" % (ms[f].upper(), f),
4776 ui.write("%s %s\n" % (ms[f].upper(), f),
4777 label='resolve.' +
4777 label='resolve.' +
4778 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4778 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4779 elif mark:
4779 elif mark:
4780 ms.mark(f, "r")
4780 ms.mark(f, "r")
4781 elif unmark:
4781 elif unmark:
4782 ms.mark(f, "u")
4782 ms.mark(f, "u")
4783 else:
4783 else:
4784 wctx = repo[None]
4784 wctx = repo[None]
4785 mctx = wctx.parents()[-1]
4785 mctx = wctx.parents()[-1]
4786
4786
4787 # backup pre-resolve (merge uses .orig for its own purposes)
4787 # backup pre-resolve (merge uses .orig for its own purposes)
4788 a = repo.wjoin(f)
4788 a = repo.wjoin(f)
4789 util.copyfile(a, a + ".resolve")
4789 util.copyfile(a, a + ".resolve")
4790
4790
4791 try:
4791 try:
4792 # resolve file
4792 # resolve file
4793 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4793 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4794 if ms.resolve(f, wctx, mctx):
4794 if ms.resolve(f, wctx, mctx):
4795 ret = 1
4795 ret = 1
4796 finally:
4796 finally:
4797 ui.setconfig('ui', 'forcemerge', '')
4797 ui.setconfig('ui', 'forcemerge', '')
4798
4798
4799 # replace filemerge's .orig file with our resolve file
4799 # replace filemerge's .orig file with our resolve file
4800 util.rename(a + ".resolve", a + ".orig")
4800 util.rename(a + ".resolve", a + ".orig")
4801
4801
4802 ms.commit()
4802 ms.commit()
4803 return ret
4803 return ret
4804
4804
4805 @command('revert',
4805 @command('revert',
4806 [('a', 'all', None, _('revert all changes when no arguments given')),
4806 [('a', 'all', None, _('revert all changes when no arguments given')),
4807 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4807 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4808 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4808 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4809 ('C', 'no-backup', None, _('do not save backup copies of files')),
4809 ('C', 'no-backup', None, _('do not save backup copies of files')),
4810 ] + walkopts + dryrunopts,
4810 ] + walkopts + dryrunopts,
4811 _('[OPTION]... [-r REV] [NAME]...'))
4811 _('[OPTION]... [-r REV] [NAME]...'))
4812 def revert(ui, repo, *pats, **opts):
4812 def revert(ui, repo, *pats, **opts):
4813 """restore files to their checkout state
4813 """restore files to their checkout state
4814
4814
4815 .. note::
4815 .. note::
4816 To check out earlier revisions, you should use :hg:`update REV`.
4816 To check out earlier revisions, you should use :hg:`update REV`.
4817 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4817 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4818
4818
4819 With no revision specified, revert the specified files or directories
4819 With no revision specified, revert the specified files or directories
4820 to the contents they had in the parent of the working directory.
4820 to the contents they had in the parent of the working directory.
4821 This restores the contents of files to an unmodified
4821 This restores the contents of files to an unmodified
4822 state and unschedules adds, removes, copies, and renames. If the
4822 state and unschedules adds, removes, copies, and renames. If the
4823 working directory has two parents, you must explicitly specify a
4823 working directory has two parents, you must explicitly specify a
4824 revision.
4824 revision.
4825
4825
4826 Using the -r/--rev or -d/--date options, revert the given files or
4826 Using the -r/--rev or -d/--date options, revert the given files or
4827 directories to their states as of a specific revision. Because
4827 directories to their states as of a specific revision. Because
4828 revert does not change the working directory parents, this will
4828 revert does not change the working directory parents, this will
4829 cause these files to appear modified. This can be helpful to "back
4829 cause these files to appear modified. This can be helpful to "back
4830 out" some or all of an earlier change. See :hg:`backout` for a
4830 out" some or all of an earlier change. See :hg:`backout` for a
4831 related method.
4831 related method.
4832
4832
4833 Modified files are saved with a .orig suffix before reverting.
4833 Modified files are saved with a .orig suffix before reverting.
4834 To disable these backups, use --no-backup.
4834 To disable these backups, use --no-backup.
4835
4835
4836 See :hg:`help dates` for a list of formats valid for -d/--date.
4836 See :hg:`help dates` for a list of formats valid for -d/--date.
4837
4837
4838 Returns 0 on success.
4838 Returns 0 on success.
4839 """
4839 """
4840
4840
4841 if opts.get("date"):
4841 if opts.get("date"):
4842 if opts.get("rev"):
4842 if opts.get("rev"):
4843 raise util.Abort(_("you can't specify a revision and a date"))
4843 raise util.Abort(_("you can't specify a revision and a date"))
4844 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4844 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4845
4845
4846 parent, p2 = repo.dirstate.parents()
4846 parent, p2 = repo.dirstate.parents()
4847 if not opts.get('rev') and p2 != nullid:
4847 if not opts.get('rev') and p2 != nullid:
4848 # revert after merge is a trap for new users (issue2915)
4848 # revert after merge is a trap for new users (issue2915)
4849 raise util.Abort(_('uncommitted merge with no revision specified'),
4849 raise util.Abort(_('uncommitted merge with no revision specified'),
4850 hint=_('use "hg update" or see "hg help revert"'))
4850 hint=_('use "hg update" or see "hg help revert"'))
4851
4851
4852 ctx = scmutil.revsingle(repo, opts.get('rev'))
4852 ctx = scmutil.revsingle(repo, opts.get('rev'))
4853
4853
4854 if not pats and not opts.get('all'):
4854 if not pats and not opts.get('all'):
4855 msg = _("no files or directories specified")
4855 msg = _("no files or directories specified")
4856 if p2 != nullid:
4856 if p2 != nullid:
4857 hint = _("uncommitted merge, use --all to discard all changes,"
4857 hint = _("uncommitted merge, use --all to discard all changes,"
4858 " or 'hg update -C .' to abort the merge")
4858 " or 'hg update -C .' to abort the merge")
4859 raise util.Abort(msg, hint=hint)
4859 raise util.Abort(msg, hint=hint)
4860 dirty = util.any(repo.status())
4860 dirty = util.any(repo.status())
4861 node = ctx.node()
4861 node = ctx.node()
4862 if node != parent:
4862 if node != parent:
4863 if dirty:
4863 if dirty:
4864 hint = _("uncommitted changes, use --all to discard all"
4864 hint = _("uncommitted changes, use --all to discard all"
4865 " changes, or 'hg update %s' to update") % ctx.rev()
4865 " changes, or 'hg update %s' to update") % ctx.rev()
4866 else:
4866 else:
4867 hint = _("use --all to revert all files,"
4867 hint = _("use --all to revert all files,"
4868 " or 'hg update %s' to update") % ctx.rev()
4868 " or 'hg update %s' to update") % ctx.rev()
4869 elif dirty:
4869 elif dirty:
4870 hint = _("uncommitted changes, use --all to discard all changes")
4870 hint = _("uncommitted changes, use --all to discard all changes")
4871 else:
4871 else:
4872 hint = _("use --all to revert all files")
4872 hint = _("use --all to revert all files")
4873 raise util.Abort(msg, hint=hint)
4873 raise util.Abort(msg, hint=hint)
4874
4874
4875 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4875 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4876
4876
4877 @command('rollback', dryrunopts +
4877 @command('rollback', dryrunopts +
4878 [('f', 'force', False, _('ignore safety measures'))])
4878 [('f', 'force', False, _('ignore safety measures'))])
4879 def rollback(ui, repo, **opts):
4879 def rollback(ui, repo, **opts):
4880 """roll back the last transaction (dangerous)
4880 """roll back the last transaction (dangerous)
4881
4881
4882 This command should be used with care. There is only one level of
4882 This command should be used with care. There is only one level of
4883 rollback, and there is no way to undo a rollback. It will also
4883 rollback, and there is no way to undo a rollback. It will also
4884 restore the dirstate at the time of the last transaction, losing
4884 restore the dirstate at the time of the last transaction, losing
4885 any dirstate changes since that time. This command does not alter
4885 any dirstate changes since that time. This command does not alter
4886 the working directory.
4886 the working directory.
4887
4887
4888 Transactions are used to encapsulate the effects of all commands
4888 Transactions are used to encapsulate the effects of all commands
4889 that create new changesets or propagate existing changesets into a
4889 that create new changesets or propagate existing changesets into a
4890 repository. For example, the following commands are transactional,
4890 repository. For example, the following commands are transactional,
4891 and their effects can be rolled back:
4891 and their effects can be rolled back:
4892
4892
4893 - commit
4893 - commit
4894 - import
4894 - import
4895 - pull
4895 - pull
4896 - push (with this repository as the destination)
4896 - push (with this repository as the destination)
4897 - unbundle
4897 - unbundle
4898
4898
4899 To avoid permanent data loss, rollback will refuse to rollback a
4899 To avoid permanent data loss, rollback will refuse to rollback a
4900 commit transaction if it isn't checked out. Use --force to
4900 commit transaction if it isn't checked out. Use --force to
4901 override this protection.
4901 override this protection.
4902
4902
4903 This command is not intended for use on public repositories. Once
4903 This command is not intended for use on public repositories. Once
4904 changes are visible for pull by other users, rolling a transaction
4904 changes are visible for pull by other users, rolling a transaction
4905 back locally is ineffective (someone else may already have pulled
4905 back locally is ineffective (someone else may already have pulled
4906 the changes). Furthermore, a race is possible with readers of the
4906 the changes). Furthermore, a race is possible with readers of the
4907 repository; for example an in-progress pull from the repository
4907 repository; for example an in-progress pull from the repository
4908 may fail if a rollback is performed.
4908 may fail if a rollback is performed.
4909
4909
4910 Returns 0 on success, 1 if no rollback data is available.
4910 Returns 0 on success, 1 if no rollback data is available.
4911 """
4911 """
4912 return repo.rollback(dryrun=opts.get('dry_run'),
4912 return repo.rollback(dryrun=opts.get('dry_run'),
4913 force=opts.get('force'))
4913 force=opts.get('force'))
4914
4914
4915 @command('root', [])
4915 @command('root', [])
4916 def root(ui, repo):
4916 def root(ui, repo):
4917 """print the root (top) of the current working directory
4917 """print the root (top) of the current working directory
4918
4918
4919 Print the root directory of the current repository.
4919 Print the root directory of the current repository.
4920
4920
4921 Returns 0 on success.
4921 Returns 0 on success.
4922 """
4922 """
4923 ui.write(repo.root + "\n")
4923 ui.write(repo.root + "\n")
4924
4924
4925 @command('^serve',
4925 @command('^serve',
4926 [('A', 'accesslog', '', _('name of access log file to write to'),
4926 [('A', 'accesslog', '', _('name of access log file to write to'),
4927 _('FILE')),
4927 _('FILE')),
4928 ('d', 'daemon', None, _('run server in background')),
4928 ('d', 'daemon', None, _('run server in background')),
4929 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4929 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4930 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4930 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4931 # use string type, then we can check if something was passed
4931 # use string type, then we can check if something was passed
4932 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4932 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4933 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4933 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4934 _('ADDR')),
4934 _('ADDR')),
4935 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4935 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4936 _('PREFIX')),
4936 _('PREFIX')),
4937 ('n', 'name', '',
4937 ('n', 'name', '',
4938 _('name to show in web pages (default: working directory)'), _('NAME')),
4938 _('name to show in web pages (default: working directory)'), _('NAME')),
4939 ('', 'web-conf', '',
4939 ('', 'web-conf', '',
4940 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4940 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4941 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4941 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4942 _('FILE')),
4942 _('FILE')),
4943 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4943 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4944 ('', 'stdio', None, _('for remote clients')),
4944 ('', 'stdio', None, _('for remote clients')),
4945 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4945 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4946 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4946 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4947 ('', 'style', '', _('template style to use'), _('STYLE')),
4947 ('', 'style', '', _('template style to use'), _('STYLE')),
4948 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4948 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4949 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4949 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4950 _('[OPTION]...'))
4950 _('[OPTION]...'))
4951 def serve(ui, repo, **opts):
4951 def serve(ui, repo, **opts):
4952 """start stand-alone webserver
4952 """start stand-alone webserver
4953
4953
4954 Start a local HTTP repository browser and pull server. You can use
4954 Start a local HTTP repository browser and pull server. You can use
4955 this for ad-hoc sharing and browsing of repositories. It is
4955 this for ad-hoc sharing and browsing of repositories. It is
4956 recommended to use a real web server to serve a repository for
4956 recommended to use a real web server to serve a repository for
4957 longer periods of time.
4957 longer periods of time.
4958
4958
4959 Please note that the server does not implement access control.
4959 Please note that the server does not implement access control.
4960 This means that, by default, anybody can read from the server and
4960 This means that, by default, anybody can read from the server and
4961 nobody can write to it by default. Set the ``web.allow_push``
4961 nobody can write to it by default. Set the ``web.allow_push``
4962 option to ``*`` to allow everybody to push to the server. You
4962 option to ``*`` to allow everybody to push to the server. You
4963 should use a real web server if you need to authenticate users.
4963 should use a real web server if you need to authenticate users.
4964
4964
4965 By default, the server logs accesses to stdout and errors to
4965 By default, the server logs accesses to stdout and errors to
4966 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4966 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4967 files.
4967 files.
4968
4968
4969 To have the server choose a free port number to listen on, specify
4969 To have the server choose a free port number to listen on, specify
4970 a port number of 0; in this case, the server will print the port
4970 a port number of 0; in this case, the server will print the port
4971 number it uses.
4971 number it uses.
4972
4972
4973 Returns 0 on success.
4973 Returns 0 on success.
4974 """
4974 """
4975
4975
4976 if opts["stdio"] and opts["cmdserver"]:
4976 if opts["stdio"] and opts["cmdserver"]:
4977 raise util.Abort(_("cannot use --stdio with --cmdserver"))
4977 raise util.Abort(_("cannot use --stdio with --cmdserver"))
4978
4978
4979 def checkrepo():
4979 def checkrepo():
4980 if repo is None:
4980 if repo is None:
4981 raise error.RepoError(_("There is no Mercurial repository here"
4981 raise error.RepoError(_("There is no Mercurial repository here"
4982 " (.hg not found)"))
4982 " (.hg not found)"))
4983
4983
4984 if opts["stdio"]:
4984 if opts["stdio"]:
4985 checkrepo()
4985 checkrepo()
4986 s = sshserver.sshserver(ui, repo)
4986 s = sshserver.sshserver(ui, repo)
4987 s.serve_forever()
4987 s.serve_forever()
4988
4988
4989 if opts["cmdserver"]:
4989 if opts["cmdserver"]:
4990 checkrepo()
4990 checkrepo()
4991 s = commandserver.server(ui, repo, opts["cmdserver"])
4991 s = commandserver.server(ui, repo, opts["cmdserver"])
4992 return s.serve()
4992 return s.serve()
4993
4993
4994 # this way we can check if something was given in the command-line
4994 # this way we can check if something was given in the command-line
4995 if opts.get('port'):
4995 if opts.get('port'):
4996 opts['port'] = util.getport(opts.get('port'))
4996 opts['port'] = util.getport(opts.get('port'))
4997
4997
4998 baseui = repo and repo.baseui or ui
4998 baseui = repo and repo.baseui or ui
4999 optlist = ("name templates style address port prefix ipv6"
4999 optlist = ("name templates style address port prefix ipv6"
5000 " accesslog errorlog certificate encoding")
5000 " accesslog errorlog certificate encoding")
5001 for o in optlist.split():
5001 for o in optlist.split():
5002 val = opts.get(o, '')
5002 val = opts.get(o, '')
5003 if val in (None, ''): # should check against default options instead
5003 if val in (None, ''): # should check against default options instead
5004 continue
5004 continue
5005 baseui.setconfig("web", o, val)
5005 baseui.setconfig("web", o, val)
5006 if repo and repo.ui != baseui:
5006 if repo and repo.ui != baseui:
5007 repo.ui.setconfig("web", o, val)
5007 repo.ui.setconfig("web", o, val)
5008
5008
5009 o = opts.get('web_conf') or opts.get('webdir_conf')
5009 o = opts.get('web_conf') or opts.get('webdir_conf')
5010 if not o:
5010 if not o:
5011 if not repo:
5011 if not repo:
5012 raise error.RepoError(_("There is no Mercurial repository"
5012 raise error.RepoError(_("There is no Mercurial repository"
5013 " here (.hg not found)"))
5013 " here (.hg not found)"))
5014 o = repo.root
5014 o = repo.root
5015
5015
5016 app = hgweb.hgweb(o, baseui=ui)
5016 app = hgweb.hgweb(o, baseui=ui)
5017
5017
5018 class service(object):
5018 class service(object):
5019 def init(self):
5019 def init(self):
5020 util.setsignalhandler()
5020 util.setsignalhandler()
5021 self.httpd = hgweb.server.create_server(ui, app)
5021 self.httpd = hgweb.server.create_server(ui, app)
5022
5022
5023 if opts['port'] and not ui.verbose:
5023 if opts['port'] and not ui.verbose:
5024 return
5024 return
5025
5025
5026 if self.httpd.prefix:
5026 if self.httpd.prefix:
5027 prefix = self.httpd.prefix.strip('/') + '/'
5027 prefix = self.httpd.prefix.strip('/') + '/'
5028 else:
5028 else:
5029 prefix = ''
5029 prefix = ''
5030
5030
5031 port = ':%d' % self.httpd.port
5031 port = ':%d' % self.httpd.port
5032 if port == ':80':
5032 if port == ':80':
5033 port = ''
5033 port = ''
5034
5034
5035 bindaddr = self.httpd.addr
5035 bindaddr = self.httpd.addr
5036 if bindaddr == '0.0.0.0':
5036 if bindaddr == '0.0.0.0':
5037 bindaddr = '*'
5037 bindaddr = '*'
5038 elif ':' in bindaddr: # IPv6
5038 elif ':' in bindaddr: # IPv6
5039 bindaddr = '[%s]' % bindaddr
5039 bindaddr = '[%s]' % bindaddr
5040
5040
5041 fqaddr = self.httpd.fqaddr
5041 fqaddr = self.httpd.fqaddr
5042 if ':' in fqaddr:
5042 if ':' in fqaddr:
5043 fqaddr = '[%s]' % fqaddr
5043 fqaddr = '[%s]' % fqaddr
5044 if opts['port']:
5044 if opts['port']:
5045 write = ui.status
5045 write = ui.status
5046 else:
5046 else:
5047 write = ui.write
5047 write = ui.write
5048 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5048 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5049 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5049 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5050
5050
5051 def run(self):
5051 def run(self):
5052 self.httpd.serve_forever()
5052 self.httpd.serve_forever()
5053
5053
5054 service = service()
5054 service = service()
5055
5055
5056 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5056 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5057
5057
5058 @command('showconfig|debugconfig',
5058 @command('showconfig|debugconfig',
5059 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5059 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5060 _('[-u] [NAME]...'))
5060 _('[-u] [NAME]...'))
5061 def showconfig(ui, repo, *values, **opts):
5061 def showconfig(ui, repo, *values, **opts):
5062 """show combined config settings from all hgrc files
5062 """show combined config settings from all hgrc files
5063
5063
5064 With no arguments, print names and values of all config items.
5064 With no arguments, print names and values of all config items.
5065
5065
5066 With one argument of the form section.name, print just the value
5066 With one argument of the form section.name, print just the value
5067 of that config item.
5067 of that config item.
5068
5068
5069 With multiple arguments, print names and values of all config
5069 With multiple arguments, print names and values of all config
5070 items with matching section names.
5070 items with matching section names.
5071
5071
5072 With --debug, the source (filename and line number) is printed
5072 With --debug, the source (filename and line number) is printed
5073 for each config item.
5073 for each config item.
5074
5074
5075 Returns 0 on success.
5075 Returns 0 on success.
5076 """
5076 """
5077
5077
5078 for f in scmutil.rcpath():
5078 for f in scmutil.rcpath():
5079 ui.debug('read config from: %s\n' % f)
5079 ui.debug('read config from: %s\n' % f)
5080 untrusted = bool(opts.get('untrusted'))
5080 untrusted = bool(opts.get('untrusted'))
5081 if values:
5081 if values:
5082 sections = [v for v in values if '.' not in v]
5082 sections = [v for v in values if '.' not in v]
5083 items = [v for v in values if '.' in v]
5083 items = [v for v in values if '.' in v]
5084 if len(items) > 1 or items and sections:
5084 if len(items) > 1 or items and sections:
5085 raise util.Abort(_('only one config item permitted'))
5085 raise util.Abort(_('only one config item permitted'))
5086 for section, name, value in ui.walkconfig(untrusted=untrusted):
5086 for section, name, value in ui.walkconfig(untrusted=untrusted):
5087 value = str(value).replace('\n', '\\n')
5087 value = str(value).replace('\n', '\\n')
5088 sectname = section + '.' + name
5088 sectname = section + '.' + name
5089 if values:
5089 if values:
5090 for v in values:
5090 for v in values:
5091 if v == section:
5091 if v == section:
5092 ui.debug('%s: ' %
5092 ui.debug('%s: ' %
5093 ui.configsource(section, name, untrusted))
5093 ui.configsource(section, name, untrusted))
5094 ui.write('%s=%s\n' % (sectname, value))
5094 ui.write('%s=%s\n' % (sectname, value))
5095 elif v == sectname:
5095 elif v == sectname:
5096 ui.debug('%s: ' %
5096 ui.debug('%s: ' %
5097 ui.configsource(section, name, untrusted))
5097 ui.configsource(section, name, untrusted))
5098 ui.write(value, '\n')
5098 ui.write(value, '\n')
5099 else:
5099 else:
5100 ui.debug('%s: ' %
5100 ui.debug('%s: ' %
5101 ui.configsource(section, name, untrusted))
5101 ui.configsource(section, name, untrusted))
5102 ui.write('%s=%s\n' % (sectname, value))
5102 ui.write('%s=%s\n' % (sectname, value))
5103
5103
5104 @command('^status|st',
5104 @command('^status|st',
5105 [('A', 'all', None, _('show status of all files')),
5105 [('A', 'all', None, _('show status of all files')),
5106 ('m', 'modified', None, _('show only modified files')),
5106 ('m', 'modified', None, _('show only modified files')),
5107 ('a', 'added', None, _('show only added files')),
5107 ('a', 'added', None, _('show only added files')),
5108 ('r', 'removed', None, _('show only removed files')),
5108 ('r', 'removed', None, _('show only removed files')),
5109 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5109 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5110 ('c', 'clean', None, _('show only files without changes')),
5110 ('c', 'clean', None, _('show only files without changes')),
5111 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5111 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5112 ('i', 'ignored', None, _('show only ignored files')),
5112 ('i', 'ignored', None, _('show only ignored files')),
5113 ('n', 'no-status', None, _('hide status prefix')),
5113 ('n', 'no-status', None, _('hide status prefix')),
5114 ('C', 'copies', None, _('show source of copied files')),
5114 ('C', 'copies', None, _('show source of copied files')),
5115 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5115 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5116 ('', 'rev', [], _('show difference from revision'), _('REV')),
5116 ('', 'rev', [], _('show difference from revision'), _('REV')),
5117 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5117 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5118 ] + walkopts + subrepoopts,
5118 ] + walkopts + subrepoopts,
5119 _('[OPTION]... [FILE]...'))
5119 _('[OPTION]... [FILE]...'))
5120 def status(ui, repo, *pats, **opts):
5120 def status(ui, repo, *pats, **opts):
5121 """show changed files in the working directory
5121 """show changed files in the working directory
5122
5122
5123 Show status of files in the repository. If names are given, only
5123 Show status of files in the repository. If names are given, only
5124 files that match are shown. Files that are clean or ignored or
5124 files that match are shown. Files that are clean or ignored or
5125 the source of a copy/move operation, are not listed unless
5125 the source of a copy/move operation, are not listed unless
5126 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5126 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5127 Unless options described with "show only ..." are given, the
5127 Unless options described with "show only ..." are given, the
5128 options -mardu are used.
5128 options -mardu are used.
5129
5129
5130 Option -q/--quiet hides untracked (unknown and ignored) files
5130 Option -q/--quiet hides untracked (unknown and ignored) files
5131 unless explicitly requested with -u/--unknown or -i/--ignored.
5131 unless explicitly requested with -u/--unknown or -i/--ignored.
5132
5132
5133 .. note::
5133 .. note::
5134 status may appear to disagree with diff if permissions have
5134 status may appear to disagree with diff if permissions have
5135 changed or a merge has occurred. The standard diff format does
5135 changed or a merge has occurred. The standard diff format does
5136 not report permission changes and diff only reports changes
5136 not report permission changes and diff only reports changes
5137 relative to one merge parent.
5137 relative to one merge parent.
5138
5138
5139 If one revision is given, it is used as the base revision.
5139 If one revision is given, it is used as the base revision.
5140 If two revisions are given, the differences between them are
5140 If two revisions are given, the differences between them are
5141 shown. The --change option can also be used as a shortcut to list
5141 shown. The --change option can also be used as a shortcut to list
5142 the changed files of a revision from its first parent.
5142 the changed files of a revision from its first parent.
5143
5143
5144 The codes used to show the status of files are::
5144 The codes used to show the status of files are::
5145
5145
5146 M = modified
5146 M = modified
5147 A = added
5147 A = added
5148 R = removed
5148 R = removed
5149 C = clean
5149 C = clean
5150 ! = missing (deleted by non-hg command, but still tracked)
5150 ! = missing (deleted by non-hg command, but still tracked)
5151 ? = not tracked
5151 ? = not tracked
5152 I = ignored
5152 I = ignored
5153 = origin of the previous file listed as A (added)
5153 = origin of the previous file listed as A (added)
5154
5154
5155 .. container:: verbose
5155 .. container:: verbose
5156
5156
5157 Examples:
5157 Examples:
5158
5158
5159 - show changes in the working directory relative to a
5159 - show changes in the working directory relative to a
5160 changeset::
5160 changeset::
5161
5161
5162 hg status --rev 9353
5162 hg status --rev 9353
5163
5163
5164 - show all changes including copies in an existing changeset::
5164 - show all changes including copies in an existing changeset::
5165
5165
5166 hg status --copies --change 9353
5166 hg status --copies --change 9353
5167
5167
5168 - get a NUL separated list of added files, suitable for xargs::
5168 - get a NUL separated list of added files, suitable for xargs::
5169
5169
5170 hg status -an0
5170 hg status -an0
5171
5171
5172 Returns 0 on success.
5172 Returns 0 on success.
5173 """
5173 """
5174
5174
5175 revs = opts.get('rev')
5175 revs = opts.get('rev')
5176 change = opts.get('change')
5176 change = opts.get('change')
5177
5177
5178 if revs and change:
5178 if revs and change:
5179 msg = _('cannot specify --rev and --change at the same time')
5179 msg = _('cannot specify --rev and --change at the same time')
5180 raise util.Abort(msg)
5180 raise util.Abort(msg)
5181 elif change:
5181 elif change:
5182 node2 = scmutil.revsingle(repo, change, None).node()
5182 node2 = scmutil.revsingle(repo, change, None).node()
5183 node1 = repo[node2].p1().node()
5183 node1 = repo[node2].p1().node()
5184 else:
5184 else:
5185 node1, node2 = scmutil.revpair(repo, revs)
5185 node1, node2 = scmutil.revpair(repo, revs)
5186
5186
5187 cwd = (pats and repo.getcwd()) or ''
5187 cwd = (pats and repo.getcwd()) or ''
5188 end = opts.get('print0') and '\0' or '\n'
5188 end = opts.get('print0') and '\0' or '\n'
5189 copy = {}
5189 copy = {}
5190 states = 'modified added removed deleted unknown ignored clean'.split()
5190 states = 'modified added removed deleted unknown ignored clean'.split()
5191 show = [k for k in states if opts.get(k)]
5191 show = [k for k in states if opts.get(k)]
5192 if opts.get('all'):
5192 if opts.get('all'):
5193 show += ui.quiet and (states[:4] + ['clean']) or states
5193 show += ui.quiet and (states[:4] + ['clean']) or states
5194 if not show:
5194 if not show:
5195 show = ui.quiet and states[:4] or states[:5]
5195 show = ui.quiet and states[:4] or states[:5]
5196
5196
5197 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5197 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5198 'ignored' in show, 'clean' in show, 'unknown' in show,
5198 'ignored' in show, 'clean' in show, 'unknown' in show,
5199 opts.get('subrepos'))
5199 opts.get('subrepos'))
5200 changestates = zip(states, 'MAR!?IC', stat)
5200 changestates = zip(states, 'MAR!?IC', stat)
5201
5201
5202 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5202 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5203 copy = copies.pathcopies(repo[node1], repo[node2])
5203 copy = copies.pathcopies(repo[node1], repo[node2])
5204
5204
5205 fm = ui.formatter('status', opts)
5205 fm = ui.formatter('status', opts)
5206 format = '%s %s' + end
5206 format = '%s %s' + end
5207 if opts.get('no_status'):
5207 if opts.get('no_status'):
5208 format = '%.0s%s' + end
5208 format = '%.0s%s' + end
5209
5209
5210 for state, char, files in changestates:
5210 for state, char, files in changestates:
5211 if state in show:
5211 if state in show:
5212 label = 'status.' + state
5212 label = 'status.' + state
5213 for f in files:
5213 for f in files:
5214 fm.startitem()
5214 fm.startitem()
5215 fm.write("status path", format, char,
5215 fm.write("status path", format, char,
5216 repo.pathto(f, cwd), label=label)
5216 repo.pathto(f, cwd), label=label)
5217 if f in copy:
5217 if f in copy:
5218 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5218 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5219 label='status.copied')
5219 label='status.copied')
5220 fm.end()
5220 fm.end()
5221
5221
5222 @command('^summary|sum',
5222 @command('^summary|sum',
5223 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5223 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5224 def summary(ui, repo, **opts):
5224 def summary(ui, repo, **opts):
5225 """summarize working directory state
5225 """summarize working directory state
5226
5226
5227 This generates a brief summary of the working directory state,
5227 This generates a brief summary of the working directory state,
5228 including parents, branch, commit status, and available updates.
5228 including parents, branch, commit status, and available updates.
5229
5229
5230 With the --remote option, this will check the default paths for
5230 With the --remote option, this will check the default paths for
5231 incoming and outgoing changes. This can be time-consuming.
5231 incoming and outgoing changes. This can be time-consuming.
5232
5232
5233 Returns 0 on success.
5233 Returns 0 on success.
5234 """
5234 """
5235
5235
5236 ctx = repo[None]
5236 ctx = repo[None]
5237 parents = ctx.parents()
5237 parents = ctx.parents()
5238 pnode = parents[0].node()
5238 pnode = parents[0].node()
5239 marks = []
5239 marks = []
5240
5240
5241 for p in parents:
5241 for p in parents:
5242 # label with log.changeset (instead of log.parent) since this
5242 # label with log.changeset (instead of log.parent) since this
5243 # shows a working directory parent *changeset*:
5243 # shows a working directory parent *changeset*:
5244 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5244 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5245 label='log.changeset')
5245 label='log.changeset')
5246 ui.write(' '.join(p.tags()), label='log.tag')
5246 ui.write(' '.join(p.tags()), label='log.tag')
5247 if p.bookmarks():
5247 if p.bookmarks():
5248 marks.extend(p.bookmarks())
5248 marks.extend(p.bookmarks())
5249 if p.rev() == -1:
5249 if p.rev() == -1:
5250 if not len(repo):
5250 if not len(repo):
5251 ui.write(_(' (empty repository)'))
5251 ui.write(_(' (empty repository)'))
5252 else:
5252 else:
5253 ui.write(_(' (no revision checked out)'))
5253 ui.write(_(' (no revision checked out)'))
5254 ui.write('\n')
5254 ui.write('\n')
5255 if p.description():
5255 if p.description():
5256 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5256 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5257 label='log.summary')
5257 label='log.summary')
5258
5258
5259 branch = ctx.branch()
5259 branch = ctx.branch()
5260 bheads = repo.branchheads(branch)
5260 bheads = repo.branchheads(branch)
5261 m = _('branch: %s\n') % branch
5261 m = _('branch: %s\n') % branch
5262 if branch != 'default':
5262 if branch != 'default':
5263 ui.write(m, label='log.branch')
5263 ui.write(m, label='log.branch')
5264 else:
5264 else:
5265 ui.status(m, label='log.branch')
5265 ui.status(m, label='log.branch')
5266
5266
5267 if marks:
5267 if marks:
5268 current = repo._bookmarkcurrent
5268 current = repo._bookmarkcurrent
5269 ui.write(_('bookmarks:'), label='log.bookmark')
5269 ui.write(_('bookmarks:'), label='log.bookmark')
5270 if current is not None:
5270 if current is not None:
5271 try:
5271 try:
5272 marks.remove(current)
5272 marks.remove(current)
5273 ui.write(' *' + current, label='bookmarks.current')
5273 ui.write(' *' + current, label='bookmarks.current')
5274 except ValueError:
5274 except ValueError:
5275 # current bookmark not in parent ctx marks
5275 # current bookmark not in parent ctx marks
5276 pass
5276 pass
5277 for m in marks:
5277 for m in marks:
5278 ui.write(' ' + m, label='log.bookmark')
5278 ui.write(' ' + m, label='log.bookmark')
5279 ui.write('\n', label='log.bookmark')
5279 ui.write('\n', label='log.bookmark')
5280
5280
5281 st = list(repo.status(unknown=True))[:6]
5281 st = list(repo.status(unknown=True))[:6]
5282
5282
5283 c = repo.dirstate.copies()
5283 c = repo.dirstate.copies()
5284 copied, renamed = [], []
5284 copied, renamed = [], []
5285 for d, s in c.iteritems():
5285 for d, s in c.iteritems():
5286 if s in st[2]:
5286 if s in st[2]:
5287 st[2].remove(s)
5287 st[2].remove(s)
5288 renamed.append(d)
5288 renamed.append(d)
5289 else:
5289 else:
5290 copied.append(d)
5290 copied.append(d)
5291 if d in st[1]:
5291 if d in st[1]:
5292 st[1].remove(d)
5292 st[1].remove(d)
5293 st.insert(3, renamed)
5293 st.insert(3, renamed)
5294 st.insert(4, copied)
5294 st.insert(4, copied)
5295
5295
5296 ms = mergemod.mergestate(repo)
5296 ms = mergemod.mergestate(repo)
5297 st.append([f for f in ms if ms[f] == 'u'])
5297 st.append([f for f in ms if ms[f] == 'u'])
5298
5298
5299 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5299 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5300 st.append(subs)
5300 st.append(subs)
5301
5301
5302 labels = [ui.label(_('%d modified'), 'status.modified'),
5302 labels = [ui.label(_('%d modified'), 'status.modified'),
5303 ui.label(_('%d added'), 'status.added'),
5303 ui.label(_('%d added'), 'status.added'),
5304 ui.label(_('%d removed'), 'status.removed'),
5304 ui.label(_('%d removed'), 'status.removed'),
5305 ui.label(_('%d renamed'), 'status.copied'),
5305 ui.label(_('%d renamed'), 'status.copied'),
5306 ui.label(_('%d copied'), 'status.copied'),
5306 ui.label(_('%d copied'), 'status.copied'),
5307 ui.label(_('%d deleted'), 'status.deleted'),
5307 ui.label(_('%d deleted'), 'status.deleted'),
5308 ui.label(_('%d unknown'), 'status.unknown'),
5308 ui.label(_('%d unknown'), 'status.unknown'),
5309 ui.label(_('%d ignored'), 'status.ignored'),
5309 ui.label(_('%d ignored'), 'status.ignored'),
5310 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5310 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5311 ui.label(_('%d subrepos'), 'status.modified')]
5311 ui.label(_('%d subrepos'), 'status.modified')]
5312 t = []
5312 t = []
5313 for s, l in zip(st, labels):
5313 for s, l in zip(st, labels):
5314 if s:
5314 if s:
5315 t.append(l % len(s))
5315 t.append(l % len(s))
5316
5316
5317 t = ', '.join(t)
5317 t = ', '.join(t)
5318 cleanworkdir = False
5318 cleanworkdir = False
5319
5319
5320 if len(parents) > 1:
5320 if len(parents) > 1:
5321 t += _(' (merge)')
5321 t += _(' (merge)')
5322 elif branch != parents[0].branch():
5322 elif branch != parents[0].branch():
5323 t += _(' (new branch)')
5323 t += _(' (new branch)')
5324 elif (parents[0].extra().get('close') and
5324 elif (parents[0].extra().get('close') and
5325 pnode in repo.branchheads(branch, closed=True)):
5325 pnode in repo.branchheads(branch, closed=True)):
5326 t += _(' (head closed)')
5326 t += _(' (head closed)')
5327 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5327 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5328 t += _(' (clean)')
5328 t += _(' (clean)')
5329 cleanworkdir = True
5329 cleanworkdir = True
5330 elif pnode not in bheads:
5330 elif pnode not in bheads:
5331 t += _(' (new branch head)')
5331 t += _(' (new branch head)')
5332
5332
5333 if cleanworkdir:
5333 if cleanworkdir:
5334 ui.status(_('commit: %s\n') % t.strip())
5334 ui.status(_('commit: %s\n') % t.strip())
5335 else:
5335 else:
5336 ui.write(_('commit: %s\n') % t.strip())
5336 ui.write(_('commit: %s\n') % t.strip())
5337
5337
5338 # all ancestors of branch heads - all ancestors of parent = new csets
5338 # all ancestors of branch heads - all ancestors of parent = new csets
5339 new = [0] * len(repo)
5339 new = [0] * len(repo)
5340 cl = repo.changelog
5340 cl = repo.changelog
5341 for a in [cl.rev(n) for n in bheads]:
5341 for a in [cl.rev(n) for n in bheads]:
5342 new[a] = 1
5342 new[a] = 1
5343 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
5343 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
5344 new[a] = 1
5344 new[a] = 1
5345 for a in [p.rev() for p in parents]:
5345 for a in [p.rev() for p in parents]:
5346 if a >= 0:
5346 if a >= 0:
5347 new[a] = 0
5347 new[a] = 0
5348 for a in cl.ancestors(*[p.rev() for p in parents]):
5348 for a in cl.ancestors(*[p.rev() for p in parents]):
5349 new[a] = 0
5349 new[a] = 0
5350 new = sum(new)
5350 new = sum(new)
5351
5351
5352 if new == 0:
5352 if new == 0:
5353 ui.status(_('update: (current)\n'))
5353 ui.status(_('update: (current)\n'))
5354 elif pnode not in bheads:
5354 elif pnode not in bheads:
5355 ui.write(_('update: %d new changesets (update)\n') % new)
5355 ui.write(_('update: %d new changesets (update)\n') % new)
5356 else:
5356 else:
5357 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5357 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5358 (new, len(bheads)))
5358 (new, len(bheads)))
5359
5359
5360 if opts.get('remote'):
5360 if opts.get('remote'):
5361 t = []
5361 t = []
5362 source, branches = hg.parseurl(ui.expandpath('default'))
5362 source, branches = hg.parseurl(ui.expandpath('default'))
5363 other = hg.peer(repo, {}, source)
5363 other = hg.peer(repo, {}, source)
5364 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
5364 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
5365 ui.debug('comparing with %s\n' % util.hidepassword(source))
5365 ui.debug('comparing with %s\n' % util.hidepassword(source))
5366 repo.ui.pushbuffer()
5366 repo.ui.pushbuffer()
5367 commoninc = discovery.findcommonincoming(repo, other)
5367 commoninc = discovery.findcommonincoming(repo, other)
5368 _common, incoming, _rheads = commoninc
5368 _common, incoming, _rheads = commoninc
5369 repo.ui.popbuffer()
5369 repo.ui.popbuffer()
5370 if incoming:
5370 if incoming:
5371 t.append(_('1 or more incoming'))
5371 t.append(_('1 or more incoming'))
5372
5372
5373 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5373 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5374 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5374 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5375 if source != dest:
5375 if source != dest:
5376 other = hg.peer(repo, {}, dest)
5376 other = hg.peer(repo, {}, dest)
5377 commoninc = None
5377 commoninc = None
5378 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5378 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5379 repo.ui.pushbuffer()
5379 repo.ui.pushbuffer()
5380 outgoing = discovery.findcommonoutgoing(repo, other,
5380 outgoing = discovery.findcommonoutgoing(repo, other,
5381 commoninc=commoninc)
5381 commoninc=commoninc)
5382 repo.ui.popbuffer()
5382 repo.ui.popbuffer()
5383 o = outgoing.missing
5383 o = outgoing.missing
5384 if o:
5384 if o:
5385 t.append(_('%d outgoing') % len(o))
5385 t.append(_('%d outgoing') % len(o))
5386 if 'bookmarks' in other.listkeys('namespaces'):
5386 if 'bookmarks' in other.listkeys('namespaces'):
5387 lmarks = repo.listkeys('bookmarks')
5387 lmarks = repo.listkeys('bookmarks')
5388 rmarks = other.listkeys('bookmarks')
5388 rmarks = other.listkeys('bookmarks')
5389 diff = set(rmarks) - set(lmarks)
5389 diff = set(rmarks) - set(lmarks)
5390 if len(diff) > 0:
5390 if len(diff) > 0:
5391 t.append(_('%d incoming bookmarks') % len(diff))
5391 t.append(_('%d incoming bookmarks') % len(diff))
5392 diff = set(lmarks) - set(rmarks)
5392 diff = set(lmarks) - set(rmarks)
5393 if len(diff) > 0:
5393 if len(diff) > 0:
5394 t.append(_('%d outgoing bookmarks') % len(diff))
5394 t.append(_('%d outgoing bookmarks') % len(diff))
5395
5395
5396 if t:
5396 if t:
5397 ui.write(_('remote: %s\n') % (', '.join(t)))
5397 ui.write(_('remote: %s\n') % (', '.join(t)))
5398 else:
5398 else:
5399 ui.status(_('remote: (synced)\n'))
5399 ui.status(_('remote: (synced)\n'))
5400
5400
5401 @command('tag',
5401 @command('tag',
5402 [('f', 'force', None, _('force tag')),
5402 [('f', 'force', None, _('force tag')),
5403 ('l', 'local', None, _('make the tag local')),
5403 ('l', 'local', None, _('make the tag local')),
5404 ('r', 'rev', '', _('revision to tag'), _('REV')),
5404 ('r', 'rev', '', _('revision to tag'), _('REV')),
5405 ('', 'remove', None, _('remove a tag')),
5405 ('', 'remove', None, _('remove a tag')),
5406 # -l/--local is already there, commitopts cannot be used
5406 # -l/--local is already there, commitopts cannot be used
5407 ('e', 'edit', None, _('edit commit message')),
5407 ('e', 'edit', None, _('edit commit message')),
5408 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5408 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5409 ] + commitopts2,
5409 ] + commitopts2,
5410 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5410 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5411 def tag(ui, repo, name1, *names, **opts):
5411 def tag(ui, repo, name1, *names, **opts):
5412 """add one or more tags for the current or given revision
5412 """add one or more tags for the current or given revision
5413
5413
5414 Name a particular revision using <name>.
5414 Name a particular revision using <name>.
5415
5415
5416 Tags are used to name particular revisions of the repository and are
5416 Tags are used to name particular revisions of the repository and are
5417 very useful to compare different revisions, to go back to significant
5417 very useful to compare different revisions, to go back to significant
5418 earlier versions or to mark branch points as releases, etc. Changing
5418 earlier versions or to mark branch points as releases, etc. Changing
5419 an existing tag is normally disallowed; use -f/--force to override.
5419 an existing tag is normally disallowed; use -f/--force to override.
5420
5420
5421 If no revision is given, the parent of the working directory is
5421 If no revision is given, the parent of the working directory is
5422 used, or tip if no revision is checked out.
5422 used, or tip if no revision is checked out.
5423
5423
5424 To facilitate version control, distribution, and merging of tags,
5424 To facilitate version control, distribution, and merging of tags,
5425 they are stored as a file named ".hgtags" which is managed similarly
5425 they are stored as a file named ".hgtags" which is managed similarly
5426 to other project files and can be hand-edited if necessary. This
5426 to other project files and can be hand-edited if necessary. This
5427 also means that tagging creates a new commit. The file
5427 also means that tagging creates a new commit. The file
5428 ".hg/localtags" is used for local tags (not shared among
5428 ".hg/localtags" is used for local tags (not shared among
5429 repositories).
5429 repositories).
5430
5430
5431 Tag commits are usually made at the head of a branch. If the parent
5431 Tag commits are usually made at the head of a branch. If the parent
5432 of the working directory is not a branch head, :hg:`tag` aborts; use
5432 of the working directory is not a branch head, :hg:`tag` aborts; use
5433 -f/--force to force the tag commit to be based on a non-head
5433 -f/--force to force the tag commit to be based on a non-head
5434 changeset.
5434 changeset.
5435
5435
5436 See :hg:`help dates` for a list of formats valid for -d/--date.
5436 See :hg:`help dates` for a list of formats valid for -d/--date.
5437
5437
5438 Since tag names have priority over branch names during revision
5438 Since tag names have priority over branch names during revision
5439 lookup, using an existing branch name as a tag name is discouraged.
5439 lookup, using an existing branch name as a tag name is discouraged.
5440
5440
5441 Returns 0 on success.
5441 Returns 0 on success.
5442 """
5442 """
5443 wlock = lock = None
5443 wlock = lock = None
5444 try:
5444 try:
5445 wlock = repo.wlock()
5445 wlock = repo.wlock()
5446 lock = repo.lock()
5446 lock = repo.lock()
5447 rev_ = "."
5447 rev_ = "."
5448 names = [t.strip() for t in (name1,) + names]
5448 names = [t.strip() for t in (name1,) + names]
5449 if len(names) != len(set(names)):
5449 if len(names) != len(set(names)):
5450 raise util.Abort(_('tag names must be unique'))
5450 raise util.Abort(_('tag names must be unique'))
5451 for n in names:
5451 for n in names:
5452 if n in ['tip', '.', 'null']:
5452 if n in ['tip', '.', 'null']:
5453 raise util.Abort(_("the name '%s' is reserved") % n)
5453 raise util.Abort(_("the name '%s' is reserved") % n)
5454 if not n:
5454 if not n:
5455 raise util.Abort(_('tag names cannot consist entirely of '
5455 raise util.Abort(_('tag names cannot consist entirely of '
5456 'whitespace'))
5456 'whitespace'))
5457 if opts.get('rev') and opts.get('remove'):
5457 if opts.get('rev') and opts.get('remove'):
5458 raise util.Abort(_("--rev and --remove are incompatible"))
5458 raise util.Abort(_("--rev and --remove are incompatible"))
5459 if opts.get('rev'):
5459 if opts.get('rev'):
5460 rev_ = opts['rev']
5460 rev_ = opts['rev']
5461 message = opts.get('message')
5461 message = opts.get('message')
5462 if opts.get('remove'):
5462 if opts.get('remove'):
5463 expectedtype = opts.get('local') and 'local' or 'global'
5463 expectedtype = opts.get('local') and 'local' or 'global'
5464 for n in names:
5464 for n in names:
5465 if not repo.tagtype(n):
5465 if not repo.tagtype(n):
5466 raise util.Abort(_("tag '%s' does not exist") % n)
5466 raise util.Abort(_("tag '%s' does not exist") % n)
5467 if repo.tagtype(n) != expectedtype:
5467 if repo.tagtype(n) != expectedtype:
5468 if expectedtype == 'global':
5468 if expectedtype == 'global':
5469 raise util.Abort(_("tag '%s' is not a global tag") % n)
5469 raise util.Abort(_("tag '%s' is not a global tag") % n)
5470 else:
5470 else:
5471 raise util.Abort(_("tag '%s' is not a local tag") % n)
5471 raise util.Abort(_("tag '%s' is not a local tag") % n)
5472 rev_ = nullid
5472 rev_ = nullid
5473 if not message:
5473 if not message:
5474 # we don't translate commit messages
5474 # we don't translate commit messages
5475 message = 'Removed tag %s' % ', '.join(names)
5475 message = 'Removed tag %s' % ', '.join(names)
5476 elif not opts.get('force'):
5476 elif not opts.get('force'):
5477 for n in names:
5477 for n in names:
5478 if n in repo.tags():
5478 if n in repo.tags():
5479 raise util.Abort(_("tag '%s' already exists "
5479 raise util.Abort(_("tag '%s' already exists "
5480 "(use -f to force)") % n)
5480 "(use -f to force)") % n)
5481 if not opts.get('local'):
5481 if not opts.get('local'):
5482 p1, p2 = repo.dirstate.parents()
5482 p1, p2 = repo.dirstate.parents()
5483 if p2 != nullid:
5483 if p2 != nullid:
5484 raise util.Abort(_('uncommitted merge'))
5484 raise util.Abort(_('uncommitted merge'))
5485 bheads = repo.branchheads()
5485 bheads = repo.branchheads()
5486 if not opts.get('force') and bheads and p1 not in bheads:
5486 if not opts.get('force') and bheads and p1 not in bheads:
5487 raise util.Abort(_('not at a branch head (use -f to force)'))
5487 raise util.Abort(_('not at a branch head (use -f to force)'))
5488 r = scmutil.revsingle(repo, rev_).node()
5488 r = scmutil.revsingle(repo, rev_).node()
5489
5489
5490 if not message:
5490 if not message:
5491 # we don't translate commit messages
5491 # we don't translate commit messages
5492 message = ('Added tag %s for changeset %s' %
5492 message = ('Added tag %s for changeset %s' %
5493 (', '.join(names), short(r)))
5493 (', '.join(names), short(r)))
5494
5494
5495 date = opts.get('date')
5495 date = opts.get('date')
5496 if date:
5496 if date:
5497 date = util.parsedate(date)
5497 date = util.parsedate(date)
5498
5498
5499 if opts.get('edit'):
5499 if opts.get('edit'):
5500 message = ui.edit(message, ui.username())
5500 message = ui.edit(message, ui.username())
5501
5501
5502 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5502 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5503 finally:
5503 finally:
5504 release(lock, wlock)
5504 release(lock, wlock)
5505
5505
5506 @command('tags', [], '')
5506 @command('tags', [], '')
5507 def tags(ui, repo):
5507 def tags(ui, repo):
5508 """list repository tags
5508 """list repository tags
5509
5509
5510 This lists both regular and local tags. When the -v/--verbose
5510 This lists both regular and local tags. When the -v/--verbose
5511 switch is used, a third column "local" is printed for local tags.
5511 switch is used, a third column "local" is printed for local tags.
5512
5512
5513 Returns 0 on success.
5513 Returns 0 on success.
5514 """
5514 """
5515
5515
5516 hexfunc = ui.debugflag and hex or short
5516 hexfunc = ui.debugflag and hex or short
5517 tagtype = ""
5517 tagtype = ""
5518
5518
5519 for t, n in reversed(repo.tagslist()):
5519 for t, n in reversed(repo.tagslist()):
5520 if ui.quiet:
5520 if ui.quiet:
5521 ui.write("%s\n" % t, label='tags.normal')
5521 ui.write("%s\n" % t, label='tags.normal')
5522 continue
5522 continue
5523
5523
5524 hn = hexfunc(n)
5524 hn = hexfunc(n)
5525 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5525 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5526 rev = ui.label(r, 'log.changeset')
5526 rev = ui.label(r, 'log.changeset')
5527 spaces = " " * (30 - encoding.colwidth(t))
5527 spaces = " " * (30 - encoding.colwidth(t))
5528
5528
5529 tag = ui.label(t, 'tags.normal')
5529 tag = ui.label(t, 'tags.normal')
5530 if ui.verbose:
5530 if ui.verbose:
5531 if repo.tagtype(t) == 'local':
5531 if repo.tagtype(t) == 'local':
5532 tagtype = " local"
5532 tagtype = " local"
5533 tag = ui.label(t, 'tags.local')
5533 tag = ui.label(t, 'tags.local')
5534 else:
5534 else:
5535 tagtype = ""
5535 tagtype = ""
5536 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5536 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5537
5537
5538 @command('tip',
5538 @command('tip',
5539 [('p', 'patch', None, _('show patch')),
5539 [('p', 'patch', None, _('show patch')),
5540 ('g', 'git', None, _('use git extended diff format')),
5540 ('g', 'git', None, _('use git extended diff format')),
5541 ] + templateopts,
5541 ] + templateopts,
5542 _('[-p] [-g]'))
5542 _('[-p] [-g]'))
5543 def tip(ui, repo, **opts):
5543 def tip(ui, repo, **opts):
5544 """show the tip revision
5544 """show the tip revision
5545
5545
5546 The tip revision (usually just called the tip) is the changeset
5546 The tip revision (usually just called the tip) is the changeset
5547 most recently added to the repository (and therefore the most
5547 most recently added to the repository (and therefore the most
5548 recently changed head).
5548 recently changed head).
5549
5549
5550 If you have just made a commit, that commit will be the tip. If
5550 If you have just made a commit, that commit will be the tip. If
5551 you have just pulled changes from another repository, the tip of
5551 you have just pulled changes from another repository, the tip of
5552 that repository becomes the current tip. The "tip" tag is special
5552 that repository becomes the current tip. The "tip" tag is special
5553 and cannot be renamed or assigned to a different changeset.
5553 and cannot be renamed or assigned to a different changeset.
5554
5554
5555 Returns 0 on success.
5555 Returns 0 on success.
5556 """
5556 """
5557 displayer = cmdutil.show_changeset(ui, repo, opts)
5557 displayer = cmdutil.show_changeset(ui, repo, opts)
5558 displayer.show(repo[len(repo) - 1])
5558 displayer.show(repo[len(repo) - 1])
5559 displayer.close()
5559 displayer.close()
5560
5560
5561 @command('unbundle',
5561 @command('unbundle',
5562 [('u', 'update', None,
5562 [('u', 'update', None,
5563 _('update to new branch head if changesets were unbundled'))],
5563 _('update to new branch head if changesets were unbundled'))],
5564 _('[-u] FILE...'))
5564 _('[-u] FILE...'))
5565 def unbundle(ui, repo, fname1, *fnames, **opts):
5565 def unbundle(ui, repo, fname1, *fnames, **opts):
5566 """apply one or more changegroup files
5566 """apply one or more changegroup files
5567
5567
5568 Apply one or more compressed changegroup files generated by the
5568 Apply one or more compressed changegroup files generated by the
5569 bundle command.
5569 bundle command.
5570
5570
5571 Returns 0 on success, 1 if an update has unresolved files.
5571 Returns 0 on success, 1 if an update has unresolved files.
5572 """
5572 """
5573 fnames = (fname1,) + fnames
5573 fnames = (fname1,) + fnames
5574
5574
5575 lock = repo.lock()
5575 lock = repo.lock()
5576 wc = repo['.']
5576 wc = repo['.']
5577 try:
5577 try:
5578 for fname in fnames:
5578 for fname in fnames:
5579 f = url.open(ui, fname)
5579 f = url.open(ui, fname)
5580 gen = changegroup.readbundle(f, fname)
5580 gen = changegroup.readbundle(f, fname)
5581 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5581 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5582 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5582 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5583 finally:
5583 finally:
5584 lock.release()
5584 lock.release()
5585 return postincoming(ui, repo, modheads, opts.get('update'), None)
5585 return postincoming(ui, repo, modheads, opts.get('update'), None)
5586
5586
5587 @command('^update|up|checkout|co',
5587 @command('^update|up|checkout|co',
5588 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5588 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5589 ('c', 'check', None,
5589 ('c', 'check', None,
5590 _('update across branches if no uncommitted changes')),
5590 _('update across branches if no uncommitted changes')),
5591 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5591 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5592 ('r', 'rev', '', _('revision'), _('REV'))],
5592 ('r', 'rev', '', _('revision'), _('REV'))],
5593 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5593 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5594 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5594 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5595 """update working directory (or switch revisions)
5595 """update working directory (or switch revisions)
5596
5596
5597 Update the repository's working directory to the specified
5597 Update the repository's working directory to the specified
5598 changeset. If no changeset is specified, update to the tip of the
5598 changeset. If no changeset is specified, update to the tip of the
5599 current named branch and move the current bookmark (see :hg:`help
5599 current named branch and move the current bookmark (see :hg:`help
5600 bookmarks`).
5600 bookmarks`).
5601
5601
5602 If the changeset is not a descendant of the working directory's
5602 If the changeset is not a descendant of the working directory's
5603 parent, the update is aborted. With the -c/--check option, the
5603 parent, the update is aborted. With the -c/--check option, the
5604 working directory is checked for uncommitted changes; if none are
5604 working directory is checked for uncommitted changes; if none are
5605 found, the working directory is updated to the specified
5605 found, the working directory is updated to the specified
5606 changeset.
5606 changeset.
5607
5607
5608 Update sets the working directory's parent revison to the specified
5608 Update sets the working directory's parent revison to the specified
5609 changeset (see :hg:`help parents`).
5609 changeset (see :hg:`help parents`).
5610
5610
5611 The following rules apply when the working directory contains
5611 The following rules apply when the working directory contains
5612 uncommitted changes:
5612 uncommitted changes:
5613
5613
5614 1. If neither -c/--check nor -C/--clean is specified, and if
5614 1. If neither -c/--check nor -C/--clean is specified, and if
5615 the requested changeset is an ancestor or descendant of
5615 the requested changeset is an ancestor or descendant of
5616 the working directory's parent, the uncommitted changes
5616 the working directory's parent, the uncommitted changes
5617 are merged into the requested changeset and the merged
5617 are merged into the requested changeset and the merged
5618 result is left uncommitted. If the requested changeset is
5618 result is left uncommitted. If the requested changeset is
5619 not an ancestor or descendant (that is, it is on another
5619 not an ancestor or descendant (that is, it is on another
5620 branch), the update is aborted and the uncommitted changes
5620 branch), the update is aborted and the uncommitted changes
5621 are preserved.
5621 are preserved.
5622
5622
5623 2. With the -c/--check option, the update is aborted and the
5623 2. With the -c/--check option, the update is aborted and the
5624 uncommitted changes are preserved.
5624 uncommitted changes are preserved.
5625
5625
5626 3. With the -C/--clean option, uncommitted changes are discarded and
5626 3. With the -C/--clean option, uncommitted changes are discarded and
5627 the working directory is updated to the requested changeset.
5627 the working directory is updated to the requested changeset.
5628
5628
5629 Use null as the changeset to remove the working directory (like
5629 Use null as the changeset to remove the working directory (like
5630 :hg:`clone -U`).
5630 :hg:`clone -U`).
5631
5631
5632 If you want to revert just one file to an older revision, use
5632 If you want to revert just one file to an older revision, use
5633 :hg:`revert [-r REV] NAME`.
5633 :hg:`revert [-r REV] NAME`.
5634
5634
5635 See :hg:`help dates` for a list of formats valid for -d/--date.
5635 See :hg:`help dates` for a list of formats valid for -d/--date.
5636
5636
5637 Returns 0 on success, 1 if there are unresolved files.
5637 Returns 0 on success, 1 if there are unresolved files.
5638 """
5638 """
5639 if rev and node:
5639 if rev and node:
5640 raise util.Abort(_("please specify just one revision"))
5640 raise util.Abort(_("please specify just one revision"))
5641
5641
5642 if rev is None or rev == '':
5642 if rev is None or rev == '':
5643 rev = node
5643 rev = node
5644
5644
5645 # with no argument, we also move the current bookmark, if any
5645 # with no argument, we also move the current bookmark, if any
5646 movemarkfrom = None
5646 movemarkfrom = None
5647 if rev is None or node == '':
5647 if rev is None or node == '':
5648 movemarkfrom = repo['.'].node()
5648 movemarkfrom = repo['.'].node()
5649
5649
5650 # if we defined a bookmark, we have to remember the original bookmark name
5650 # if we defined a bookmark, we have to remember the original bookmark name
5651 brev = rev
5651 brev = rev
5652 rev = scmutil.revsingle(repo, rev, rev).rev()
5652 rev = scmutil.revsingle(repo, rev, rev).rev()
5653
5653
5654 if check and clean:
5654 if check and clean:
5655 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5655 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5656
5656
5657 if date:
5657 if date:
5658 if rev is not None:
5658 if rev is not None:
5659 raise util.Abort(_("you can't specify a revision and a date"))
5659 raise util.Abort(_("you can't specify a revision and a date"))
5660 rev = cmdutil.finddate(ui, repo, date)
5660 rev = cmdutil.finddate(ui, repo, date)
5661
5661
5662 if check:
5662 if check:
5663 c = repo[None]
5663 c = repo[None]
5664 if c.dirty(merge=False, branch=False):
5664 if c.dirty(merge=False, branch=False):
5665 raise util.Abort(_("uncommitted local changes"))
5665 raise util.Abort(_("uncommitted local changes"))
5666 if rev is None:
5666 if rev is None:
5667 rev = repo[repo[None].branch()].rev()
5667 rev = repo[repo[None].branch()].rev()
5668 mergemod._checkunknown(repo, repo[None], repo[rev])
5668 mergemod._checkunknown(repo, repo[None], repo[rev])
5669
5669
5670 if clean:
5670 if clean:
5671 ret = hg.clean(repo, rev)
5671 ret = hg.clean(repo, rev)
5672 else:
5672 else:
5673 ret = hg.update(repo, rev)
5673 ret = hg.update(repo, rev)
5674
5674
5675 if not ret and movemarkfrom:
5675 if not ret and movemarkfrom:
5676 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5676 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5677 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5677 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5678 elif brev in repo._bookmarks:
5678 elif brev in repo._bookmarks:
5679 bookmarks.setcurrent(repo, brev)
5679 bookmarks.setcurrent(repo, brev)
5680 elif brev:
5680 elif brev:
5681 bookmarks.unsetcurrent(repo)
5681 bookmarks.unsetcurrent(repo)
5682
5682
5683 return ret
5683 return ret
5684
5684
5685 @command('verify', [])
5685 @command('verify', [])
5686 def verify(ui, repo):
5686 def verify(ui, repo):
5687 """verify the integrity of the repository
5687 """verify the integrity of the repository
5688
5688
5689 Verify the integrity of the current repository.
5689 Verify the integrity of the current repository.
5690
5690
5691 This will perform an extensive check of the repository's
5691 This will perform an extensive check of the repository's
5692 integrity, validating the hashes and checksums of each entry in
5692 integrity, validating the hashes and checksums of each entry in
5693 the changelog, manifest, and tracked files, as well as the
5693 the changelog, manifest, and tracked files, as well as the
5694 integrity of their crosslinks and indices.
5694 integrity of their crosslinks and indices.
5695
5695
5696 Returns 0 on success, 1 if errors are encountered.
5696 Returns 0 on success, 1 if errors are encountered.
5697 """
5697 """
5698 return hg.verify(repo)
5698 return hg.verify(repo)
5699
5699
5700 @command('version', [])
5700 @command('version', [])
5701 def version_(ui):
5701 def version_(ui):
5702 """output version and copyright information"""
5702 """output version and copyright information"""
5703 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5703 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5704 % util.version())
5704 % util.version())
5705 ui.status(_(
5705 ui.status(_(
5706 "(see http://mercurial.selenic.com for more information)\n"
5706 "(see http://mercurial.selenic.com for more information)\n"
5707 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5707 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5708 "This is free software; see the source for copying conditions. "
5708 "This is free software; see the source for copying conditions. "
5709 "There is NO\nwarranty; "
5709 "There is NO\nwarranty; "
5710 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5710 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5711 ))
5711 ))
5712
5712
5713 norepo = ("clone init version help debugcommands debugcomplete"
5713 norepo = ("clone init version help debugcommands debugcomplete"
5714 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5714 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5715 " debugknown debuggetbundle debugbundle")
5715 " debugknown debuggetbundle debugbundle")
5716 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5716 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5717 " debugdata debugindex debugindexdot debugrevlog")
5717 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,783 +1,795 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 import errno
7 import errno
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import scmutil, util, ignore, osutil, parsers, encoding
11 import scmutil, util, ignore, osutil, parsers, encoding
12 import struct, os, stat, errno
12 import struct, os, stat, errno
13 import cStringIO
13 import cStringIO
14
14
15 _format = ">cllll"
15 _format = ">cllll"
16 propertycache = util.propertycache
16 propertycache = util.propertycache
17 filecache = scmutil.filecache
17 filecache = scmutil.filecache
18
18
19 class repocache(filecache):
19 class repocache(filecache):
20 """filecache for files in .hg/"""
20 """filecache for files in .hg/"""
21 def join(self, obj, fname):
21 def join(self, obj, fname):
22 return obj._opener.join(fname)
22 return obj._opener.join(fname)
23
23
24 class rootcache(filecache):
24 class rootcache(filecache):
25 """filecache for files in the repository root"""
25 """filecache for files in the repository root"""
26 def join(self, obj, fname):
26 def join(self, obj, fname):
27 return obj._join(fname)
27 return obj._join(fname)
28
28
29 def _finddirs(path):
29 def _finddirs(path):
30 pos = path.rfind('/')
30 pos = path.rfind('/')
31 while pos != -1:
31 while pos != -1:
32 yield path[:pos]
32 yield path[:pos]
33 pos = path.rfind('/', 0, pos)
33 pos = path.rfind('/', 0, pos)
34
34
35 def _incdirs(dirs, path):
35 def _incdirs(dirs, path):
36 for base in _finddirs(path):
36 for base in _finddirs(path):
37 if base in dirs:
37 if base in dirs:
38 dirs[base] += 1
38 dirs[base] += 1
39 return
39 return
40 dirs[base] = 1
40 dirs[base] = 1
41
41
42 def _decdirs(dirs, path):
42 def _decdirs(dirs, path):
43 for base in _finddirs(path):
43 for base in _finddirs(path):
44 if dirs[base] > 1:
44 if dirs[base] > 1:
45 dirs[base] -= 1
45 dirs[base] -= 1
46 return
46 return
47 del dirs[base]
47 del dirs[base]
48
48
49 class dirstate(object):
49 class dirstate(object):
50
50
51 def __init__(self, opener, ui, root, validate):
51 def __init__(self, opener, ui, root, validate):
52 '''Create a new dirstate object.
52 '''Create a new dirstate object.
53
53
54 opener is an open()-like callable that can be used to open the
54 opener is an open()-like callable that can be used to open the
55 dirstate file; root is the root of the directory tracked by
55 dirstate file; root is the root of the directory tracked by
56 the dirstate.
56 the dirstate.
57 '''
57 '''
58 self._opener = opener
58 self._opener = opener
59 self._validate = validate
59 self._validate = validate
60 self._root = root
60 self._root = root
61 self._rootdir = os.path.join(root, '')
61 self._rootdir = os.path.join(root, '')
62 self._dirty = False
62 self._dirty = False
63 self._dirtypl = False
63 self._dirtypl = False
64 self._lastnormaltime = 0
64 self._lastnormaltime = 0
65 self._ui = ui
65 self._ui = ui
66 self._filecache = {}
66 self._filecache = {}
67
67
68 @propertycache
68 @propertycache
69 def _map(self):
69 def _map(self):
70 '''Return the dirstate contents as a map from filename to
70 '''Return the dirstate contents as a map from filename to
71 (state, mode, size, time).'''
71 (state, mode, size, time).'''
72 self._read()
72 self._read()
73 return self._map
73 return self._map
74
74
75 @propertycache
75 @propertycache
76 def _copymap(self):
76 def _copymap(self):
77 self._read()
77 self._read()
78 return self._copymap
78 return self._copymap
79
79
80 @propertycache
80 @propertycache
81 def _foldmap(self):
81 def _foldmap(self):
82 f = {}
82 f = {}
83 for name in self._map:
83 for name in self._map:
84 f[util.normcase(name)] = name
84 f[util.normcase(name)] = name
85 for name in self._dirs:
85 for name in self._dirs:
86 f[util.normcase(name)] = name
86 f[util.normcase(name)] = name
87 f['.'] = '.' # prevents useless util.fspath() invocation
87 f['.'] = '.' # prevents useless util.fspath() invocation
88 return f
88 return f
89
89
90 @repocache('branch')
90 @repocache('branch')
91 def _branch(self):
91 def _branch(self):
92 try:
92 try:
93 return self._opener.read("branch").strip() or "default"
93 return self._opener.read("branch").strip() or "default"
94 except IOError, inst:
94 except IOError, inst:
95 if inst.errno != errno.ENOENT:
95 if inst.errno != errno.ENOENT:
96 raise
96 raise
97 return "default"
97 return "default"
98
98
99 @propertycache
99 @propertycache
100 def _pl(self):
100 def _pl(self):
101 try:
101 try:
102 fp = self._opener("dirstate")
102 fp = self._opener("dirstate")
103 st = fp.read(40)
103 st = fp.read(40)
104 fp.close()
104 fp.close()
105 l = len(st)
105 l = len(st)
106 if l == 40:
106 if l == 40:
107 return st[:20], st[20:40]
107 return st[:20], st[20:40]
108 elif l > 0 and l < 40:
108 elif l > 0 and l < 40:
109 raise util.Abort(_('working directory state appears damaged!'))
109 raise util.Abort(_('working directory state appears damaged!'))
110 except IOError, err:
110 except IOError, err:
111 if err.errno != errno.ENOENT:
111 if err.errno != errno.ENOENT:
112 raise
112 raise
113 return [nullid, nullid]
113 return [nullid, nullid]
114
114
115 @propertycache
115 @propertycache
116 def _dirs(self):
116 def _dirs(self):
117 dirs = {}
117 dirs = {}
118 for f, s in self._map.iteritems():
118 for f, s in self._map.iteritems():
119 if s[0] != 'r':
119 if s[0] != 'r':
120 _incdirs(dirs, f)
120 _incdirs(dirs, f)
121 return dirs
121 return dirs
122
122
123 def dirs(self):
123 def dirs(self):
124 return self._dirs
124 return self._dirs
125
125
126 @rootcache('.hgignore')
126 @rootcache('.hgignore')
127 def _ignore(self):
127 def _ignore(self):
128 files = [self._join('.hgignore')]
128 files = [self._join('.hgignore')]
129 for name, path in self._ui.configitems("ui"):
129 for name, path in self._ui.configitems("ui"):
130 if name == 'ignore' or name.startswith('ignore.'):
130 if name == 'ignore' or name.startswith('ignore.'):
131 files.append(util.expandpath(path))
131 files.append(util.expandpath(path))
132 return ignore.ignore(self._root, files, self._ui.warn)
132 return ignore.ignore(self._root, files, self._ui.warn)
133
133
134 @propertycache
134 @propertycache
135 def _slash(self):
135 def _slash(self):
136 return self._ui.configbool('ui', 'slash') and os.sep != '/'
136 return self._ui.configbool('ui', 'slash') and os.sep != '/'
137
137
138 @propertycache
138 @propertycache
139 def _checklink(self):
139 def _checklink(self):
140 return util.checklink(self._root)
140 return util.checklink(self._root)
141
141
142 @propertycache
142 @propertycache
143 def _checkexec(self):
143 def _checkexec(self):
144 return util.checkexec(self._root)
144 return util.checkexec(self._root)
145
145
146 @propertycache
146 @propertycache
147 def _checkcase(self):
147 def _checkcase(self):
148 return not util.checkcase(self._join('.hg'))
148 return not util.checkcase(self._join('.hg'))
149
149
150 def _join(self, f):
150 def _join(self, f):
151 # much faster than os.path.join()
151 # much faster than os.path.join()
152 # it's safe because f is always a relative path
152 # it's safe because f is always a relative path
153 return self._rootdir + f
153 return self._rootdir + f
154
154
155 def flagfunc(self, buildfallback):
155 def flagfunc(self, buildfallback):
156 if self._checklink and self._checkexec:
156 if self._checklink and self._checkexec:
157 def f(x):
157 def f(x):
158 p = self._join(x)
158 p = self._join(x)
159 if os.path.islink(p):
159 if os.path.islink(p):
160 return 'l'
160 return 'l'
161 if util.isexec(p):
161 if util.isexec(p):
162 return 'x'
162 return 'x'
163 return ''
163 return ''
164 return f
164 return f
165
165
166 fallback = buildfallback()
166 fallback = buildfallback()
167 if self._checklink:
167 if self._checklink:
168 def f(x):
168 def f(x):
169 if os.path.islink(self._join(x)):
169 if os.path.islink(self._join(x)):
170 return 'l'
170 return 'l'
171 if 'x' in fallback(x):
171 if 'x' in fallback(x):
172 return 'x'
172 return 'x'
173 return ''
173 return ''
174 return f
174 return f
175 if self._checkexec:
175 if self._checkexec:
176 def f(x):
176 def f(x):
177 if 'l' in fallback(x):
177 if 'l' in fallback(x):
178 return 'l'
178 return 'l'
179 if util.isexec(self._join(x)):
179 if util.isexec(self._join(x)):
180 return 'x'
180 return 'x'
181 return ''
181 return ''
182 return f
182 return f
183 else:
183 else:
184 return fallback
184 return fallback
185
185
186 def getcwd(self):
186 def getcwd(self):
187 cwd = os.getcwd()
187 cwd = os.getcwd()
188 if cwd == self._root:
188 if cwd == self._root:
189 return ''
189 return ''
190 # self._root ends with a path separator if self._root is '/' or 'C:\'
190 # self._root ends with a path separator if self._root is '/' or 'C:\'
191 rootsep = self._root
191 rootsep = self._root
192 if not util.endswithsep(rootsep):
192 if not util.endswithsep(rootsep):
193 rootsep += os.sep
193 rootsep += os.sep
194 if cwd.startswith(rootsep):
194 if cwd.startswith(rootsep):
195 return cwd[len(rootsep):]
195 return cwd[len(rootsep):]
196 else:
196 else:
197 # we're outside the repo. return an absolute path.
197 # we're outside the repo. return an absolute path.
198 return cwd
198 return cwd
199
199
200 def pathto(self, f, cwd=None):
200 def pathto(self, f, cwd=None):
201 if cwd is None:
201 if cwd is None:
202 cwd = self.getcwd()
202 cwd = self.getcwd()
203 path = util.pathto(self._root, cwd, f)
203 path = util.pathto(self._root, cwd, f)
204 if self._slash:
204 if self._slash:
205 return util.normpath(path)
205 return util.normpath(path)
206 return path
206 return path
207
207
208 def __getitem__(self, key):
208 def __getitem__(self, key):
209 '''Return the current state of key (a filename) in the dirstate.
209 '''Return the current state of key (a filename) in the dirstate.
210
210
211 States are:
211 States are:
212 n normal
212 n normal
213 m needs merging
213 m needs merging
214 r marked for removal
214 r marked for removal
215 a marked for addition
215 a marked for addition
216 ? not tracked
216 ? not tracked
217 '''
217 '''
218 return self._map.get(key, ("?",))[0]
218 return self._map.get(key, ("?",))[0]
219
219
220 def __contains__(self, key):
220 def __contains__(self, key):
221 return key in self._map
221 return key in self._map
222
222
223 def __iter__(self):
223 def __iter__(self):
224 for x in sorted(self._map):
224 for x in sorted(self._map):
225 yield x
225 yield x
226
226
227 def parents(self):
227 def parents(self):
228 return [self._validate(p) for p in self._pl]
228 return [self._validate(p) for p in self._pl]
229
229
230 def p1(self):
230 def p1(self):
231 return self._validate(self._pl[0])
231 return self._validate(self._pl[0])
232
232
233 def p2(self):
233 def p2(self):
234 return self._validate(self._pl[1])
234 return self._validate(self._pl[1])
235
235
236 def branch(self):
236 def branch(self):
237 return encoding.tolocal(self._branch)
237 return encoding.tolocal(self._branch)
238
238
239 def setparents(self, p1, p2=nullid):
239 def setparents(self, p1, p2=nullid):
240 """Set dirstate parents to p1 and p2.
241
242 When moving from two parents to one, 'm' merged entries a
243 adjusted to normal and previous copy records discarded and
244 returned by the call.
245
246 See localrepo.setparents()
247 """
240 self._dirty = self._dirtypl = True
248 self._dirty = self._dirtypl = True
241 oldp2 = self._pl[1]
249 oldp2 = self._pl[1]
242 self._pl = p1, p2
250 self._pl = p1, p2
251 copies = {}
243 if oldp2 != nullid and p2 == nullid:
252 if oldp2 != nullid and p2 == nullid:
244 # Discard 'm' markers when moving away from a merge state
253 # Discard 'm' markers when moving away from a merge state
245 for f, s in self._map.iteritems():
254 for f, s in self._map.iteritems():
246 if s[0] == 'm':
255 if s[0] == 'm':
256 if f in self._copymap:
257 copies[f] = self._copymap[f]
247 self.normallookup(f)
258 self.normallookup(f)
259 return copies
248
260
249 def setbranch(self, branch):
261 def setbranch(self, branch):
250 if branch in ['tip', '.', 'null']:
262 if branch in ['tip', '.', 'null']:
251 raise util.Abort(_('the name \'%s\' is reserved') % branch)
263 raise util.Abort(_('the name \'%s\' is reserved') % branch)
252 self._branch = encoding.fromlocal(branch)
264 self._branch = encoding.fromlocal(branch)
253 f = self._opener('branch', 'w', atomictemp=True)
265 f = self._opener('branch', 'w', atomictemp=True)
254 try:
266 try:
255 f.write(self._branch + '\n')
267 f.write(self._branch + '\n')
256 finally:
268 finally:
257 f.close()
269 f.close()
258
270
259 def _read(self):
271 def _read(self):
260 self._map = {}
272 self._map = {}
261 self._copymap = {}
273 self._copymap = {}
262 try:
274 try:
263 st = self._opener.read("dirstate")
275 st = self._opener.read("dirstate")
264 except IOError, err:
276 except IOError, err:
265 if err.errno != errno.ENOENT:
277 if err.errno != errno.ENOENT:
266 raise
278 raise
267 return
279 return
268 if not st:
280 if not st:
269 return
281 return
270
282
271 p = parsers.parse_dirstate(self._map, self._copymap, st)
283 p = parsers.parse_dirstate(self._map, self._copymap, st)
272 if not self._dirtypl:
284 if not self._dirtypl:
273 self._pl = p
285 self._pl = p
274
286
275 def invalidate(self):
287 def invalidate(self):
276 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
288 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
277 "_ignore"):
289 "_ignore"):
278 if a in self.__dict__:
290 if a in self.__dict__:
279 delattr(self, a)
291 delattr(self, a)
280 self._lastnormaltime = 0
292 self._lastnormaltime = 0
281 self._dirty = False
293 self._dirty = False
282
294
283 def copy(self, source, dest):
295 def copy(self, source, dest):
284 """Mark dest as a copy of source. Unmark dest if source is None."""
296 """Mark dest as a copy of source. Unmark dest if source is None."""
285 if source == dest:
297 if source == dest:
286 return
298 return
287 self._dirty = True
299 self._dirty = True
288 if source is not None:
300 if source is not None:
289 self._copymap[dest] = source
301 self._copymap[dest] = source
290 elif dest in self._copymap:
302 elif dest in self._copymap:
291 del self._copymap[dest]
303 del self._copymap[dest]
292
304
293 def copied(self, file):
305 def copied(self, file):
294 return self._copymap.get(file, None)
306 return self._copymap.get(file, None)
295
307
296 def copies(self):
308 def copies(self):
297 return self._copymap
309 return self._copymap
298
310
299 def _droppath(self, f):
311 def _droppath(self, f):
300 if self[f] not in "?r" and "_dirs" in self.__dict__:
312 if self[f] not in "?r" and "_dirs" in self.__dict__:
301 _decdirs(self._dirs, f)
313 _decdirs(self._dirs, f)
302
314
303 def _addpath(self, f, check=False):
315 def _addpath(self, f, check=False):
304 oldstate = self[f]
316 oldstate = self[f]
305 if check or oldstate == "r":
317 if check or oldstate == "r":
306 scmutil.checkfilename(f)
318 scmutil.checkfilename(f)
307 if f in self._dirs:
319 if f in self._dirs:
308 raise util.Abort(_('directory %r already in dirstate') % f)
320 raise util.Abort(_('directory %r already in dirstate') % f)
309 # shadows
321 # shadows
310 for d in _finddirs(f):
322 for d in _finddirs(f):
311 if d in self._dirs:
323 if d in self._dirs:
312 break
324 break
313 if d in self._map and self[d] != 'r':
325 if d in self._map and self[d] != 'r':
314 raise util.Abort(
326 raise util.Abort(
315 _('file %r in dirstate clashes with %r') % (d, f))
327 _('file %r in dirstate clashes with %r') % (d, f))
316 if oldstate in "?r" and "_dirs" in self.__dict__:
328 if oldstate in "?r" and "_dirs" in self.__dict__:
317 _incdirs(self._dirs, f)
329 _incdirs(self._dirs, f)
318
330
319 def normal(self, f):
331 def normal(self, f):
320 '''Mark a file normal and clean.'''
332 '''Mark a file normal and clean.'''
321 self._dirty = True
333 self._dirty = True
322 self._addpath(f)
334 self._addpath(f)
323 s = os.lstat(self._join(f))
335 s = os.lstat(self._join(f))
324 mtime = int(s.st_mtime)
336 mtime = int(s.st_mtime)
325 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
337 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
326 if f in self._copymap:
338 if f in self._copymap:
327 del self._copymap[f]
339 del self._copymap[f]
328 if mtime > self._lastnormaltime:
340 if mtime > self._lastnormaltime:
329 # Remember the most recent modification timeslot for status(),
341 # Remember the most recent modification timeslot for status(),
330 # to make sure we won't miss future size-preserving file content
342 # to make sure we won't miss future size-preserving file content
331 # modifications that happen within the same timeslot.
343 # modifications that happen within the same timeslot.
332 self._lastnormaltime = mtime
344 self._lastnormaltime = mtime
333
345
334 def normallookup(self, f):
346 def normallookup(self, f):
335 '''Mark a file normal, but possibly dirty.'''
347 '''Mark a file normal, but possibly dirty.'''
336 if self._pl[1] != nullid and f in self._map:
348 if self._pl[1] != nullid and f in self._map:
337 # if there is a merge going on and the file was either
349 # if there is a merge going on and the file was either
338 # in state 'm' (-1) or coming from other parent (-2) before
350 # in state 'm' (-1) or coming from other parent (-2) before
339 # being removed, restore that state.
351 # being removed, restore that state.
340 entry = self._map[f]
352 entry = self._map[f]
341 if entry[0] == 'r' and entry[2] in (-1, -2):
353 if entry[0] == 'r' and entry[2] in (-1, -2):
342 source = self._copymap.get(f)
354 source = self._copymap.get(f)
343 if entry[2] == -1:
355 if entry[2] == -1:
344 self.merge(f)
356 self.merge(f)
345 elif entry[2] == -2:
357 elif entry[2] == -2:
346 self.otherparent(f)
358 self.otherparent(f)
347 if source:
359 if source:
348 self.copy(source, f)
360 self.copy(source, f)
349 return
361 return
350 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
362 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
351 return
363 return
352 self._dirty = True
364 self._dirty = True
353 self._addpath(f)
365 self._addpath(f)
354 self._map[f] = ('n', 0, -1, -1)
366 self._map[f] = ('n', 0, -1, -1)
355 if f in self._copymap:
367 if f in self._copymap:
356 del self._copymap[f]
368 del self._copymap[f]
357
369
358 def otherparent(self, f):
370 def otherparent(self, f):
359 '''Mark as coming from the other parent, always dirty.'''
371 '''Mark as coming from the other parent, always dirty.'''
360 if self._pl[1] == nullid:
372 if self._pl[1] == nullid:
361 raise util.Abort(_("setting %r to other parent "
373 raise util.Abort(_("setting %r to other parent "
362 "only allowed in merges") % f)
374 "only allowed in merges") % f)
363 self._dirty = True
375 self._dirty = True
364 self._addpath(f)
376 self._addpath(f)
365 self._map[f] = ('n', 0, -2, -1)
377 self._map[f] = ('n', 0, -2, -1)
366 if f in self._copymap:
378 if f in self._copymap:
367 del self._copymap[f]
379 del self._copymap[f]
368
380
369 def add(self, f):
381 def add(self, f):
370 '''Mark a file added.'''
382 '''Mark a file added.'''
371 self._dirty = True
383 self._dirty = True
372 self._addpath(f, True)
384 self._addpath(f, True)
373 self._map[f] = ('a', 0, -1, -1)
385 self._map[f] = ('a', 0, -1, -1)
374 if f in self._copymap:
386 if f in self._copymap:
375 del self._copymap[f]
387 del self._copymap[f]
376
388
377 def remove(self, f):
389 def remove(self, f):
378 '''Mark a file removed.'''
390 '''Mark a file removed.'''
379 self._dirty = True
391 self._dirty = True
380 self._droppath(f)
392 self._droppath(f)
381 size = 0
393 size = 0
382 if self._pl[1] != nullid and f in self._map:
394 if self._pl[1] != nullid and f in self._map:
383 # backup the previous state
395 # backup the previous state
384 entry = self._map[f]
396 entry = self._map[f]
385 if entry[0] == 'm': # merge
397 if entry[0] == 'm': # merge
386 size = -1
398 size = -1
387 elif entry[0] == 'n' and entry[2] == -2: # other parent
399 elif entry[0] == 'n' and entry[2] == -2: # other parent
388 size = -2
400 size = -2
389 self._map[f] = ('r', 0, size, 0)
401 self._map[f] = ('r', 0, size, 0)
390 if size == 0 and f in self._copymap:
402 if size == 0 and f in self._copymap:
391 del self._copymap[f]
403 del self._copymap[f]
392
404
393 def merge(self, f):
405 def merge(self, f):
394 '''Mark a file merged.'''
406 '''Mark a file merged.'''
395 if self._pl[1] == nullid:
407 if self._pl[1] == nullid:
396 return self.normallookup(f)
408 return self.normallookup(f)
397 self._dirty = True
409 self._dirty = True
398 s = os.lstat(self._join(f))
410 s = os.lstat(self._join(f))
399 self._addpath(f)
411 self._addpath(f)
400 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
412 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
401 if f in self._copymap:
413 if f in self._copymap:
402 del self._copymap[f]
414 del self._copymap[f]
403
415
404 def drop(self, f):
416 def drop(self, f):
405 '''Drop a file from the dirstate'''
417 '''Drop a file from the dirstate'''
406 if f in self._map:
418 if f in self._map:
407 self._dirty = True
419 self._dirty = True
408 self._droppath(f)
420 self._droppath(f)
409 del self._map[f]
421 del self._map[f]
410
422
411 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
423 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
412 normed = util.normcase(path)
424 normed = util.normcase(path)
413 folded = self._foldmap.get(normed, None)
425 folded = self._foldmap.get(normed, None)
414 if folded is None:
426 if folded is None:
415 if isknown:
427 if isknown:
416 folded = path
428 folded = path
417 else:
429 else:
418 if exists is None:
430 if exists is None:
419 exists = os.path.lexists(os.path.join(self._root, path))
431 exists = os.path.lexists(os.path.join(self._root, path))
420 if not exists:
432 if not exists:
421 # Maybe a path component exists
433 # Maybe a path component exists
422 if not ignoremissing and '/' in path:
434 if not ignoremissing and '/' in path:
423 d, f = path.rsplit('/', 1)
435 d, f = path.rsplit('/', 1)
424 d = self._normalize(d, isknown, ignoremissing, None)
436 d = self._normalize(d, isknown, ignoremissing, None)
425 folded = d + "/" + f
437 folded = d + "/" + f
426 else:
438 else:
427 # No path components, preserve original case
439 # No path components, preserve original case
428 folded = path
440 folded = path
429 else:
441 else:
430 # recursively normalize leading directory components
442 # recursively normalize leading directory components
431 # against dirstate
443 # against dirstate
432 if '/' in normed:
444 if '/' in normed:
433 d, f = normed.rsplit('/', 1)
445 d, f = normed.rsplit('/', 1)
434 d = self._normalize(d, isknown, ignoremissing, True)
446 d = self._normalize(d, isknown, ignoremissing, True)
435 r = self._root + "/" + d
447 r = self._root + "/" + d
436 folded = d + "/" + util.fspath(f, r)
448 folded = d + "/" + util.fspath(f, r)
437 else:
449 else:
438 folded = util.fspath(normed, self._root)
450 folded = util.fspath(normed, self._root)
439 self._foldmap[normed] = folded
451 self._foldmap[normed] = folded
440
452
441 return folded
453 return folded
442
454
443 def normalize(self, path, isknown=False, ignoremissing=False):
455 def normalize(self, path, isknown=False, ignoremissing=False):
444 '''
456 '''
445 normalize the case of a pathname when on a casefolding filesystem
457 normalize the case of a pathname when on a casefolding filesystem
446
458
447 isknown specifies whether the filename came from walking the
459 isknown specifies whether the filename came from walking the
448 disk, to avoid extra filesystem access.
460 disk, to avoid extra filesystem access.
449
461
450 If ignoremissing is True, missing path are returned
462 If ignoremissing is True, missing path are returned
451 unchanged. Otherwise, we try harder to normalize possibly
463 unchanged. Otherwise, we try harder to normalize possibly
452 existing path components.
464 existing path components.
453
465
454 The normalized case is determined based on the following precedence:
466 The normalized case is determined based on the following precedence:
455
467
456 - version of name already stored in the dirstate
468 - version of name already stored in the dirstate
457 - version of name stored on disk
469 - version of name stored on disk
458 - version provided via command arguments
470 - version provided via command arguments
459 '''
471 '''
460
472
461 if self._checkcase:
473 if self._checkcase:
462 return self._normalize(path, isknown, ignoremissing)
474 return self._normalize(path, isknown, ignoremissing)
463 return path
475 return path
464
476
465 def clear(self):
477 def clear(self):
466 self._map = {}
478 self._map = {}
467 if "_dirs" in self.__dict__:
479 if "_dirs" in self.__dict__:
468 delattr(self, "_dirs")
480 delattr(self, "_dirs")
469 self._copymap = {}
481 self._copymap = {}
470 self._pl = [nullid, nullid]
482 self._pl = [nullid, nullid]
471 self._lastnormaltime = 0
483 self._lastnormaltime = 0
472 self._dirty = True
484 self._dirty = True
473
485
474 def rebuild(self, parent, files):
486 def rebuild(self, parent, files):
475 self.clear()
487 self.clear()
476 for f in files:
488 for f in files:
477 if 'x' in files.flags(f):
489 if 'x' in files.flags(f):
478 self._map[f] = ('n', 0777, -1, 0)
490 self._map[f] = ('n', 0777, -1, 0)
479 else:
491 else:
480 self._map[f] = ('n', 0666, -1, 0)
492 self._map[f] = ('n', 0666, -1, 0)
481 self._pl = (parent, nullid)
493 self._pl = (parent, nullid)
482 self._dirty = True
494 self._dirty = True
483
495
484 def write(self):
496 def write(self):
485 if not self._dirty:
497 if not self._dirty:
486 return
498 return
487 st = self._opener("dirstate", "w", atomictemp=True)
499 st = self._opener("dirstate", "w", atomictemp=True)
488
500
489 # use the modification time of the newly created temporary file as the
501 # use the modification time of the newly created temporary file as the
490 # filesystem's notion of 'now'
502 # filesystem's notion of 'now'
491 now = int(util.fstat(st).st_mtime)
503 now = int(util.fstat(st).st_mtime)
492
504
493 cs = cStringIO.StringIO()
505 cs = cStringIO.StringIO()
494 copymap = self._copymap
506 copymap = self._copymap
495 pack = struct.pack
507 pack = struct.pack
496 write = cs.write
508 write = cs.write
497 write("".join(self._pl))
509 write("".join(self._pl))
498 for f, e in self._map.iteritems():
510 for f, e in self._map.iteritems():
499 if e[0] == 'n' and e[3] == now:
511 if e[0] == 'n' and e[3] == now:
500 # The file was last modified "simultaneously" with the current
512 # The file was last modified "simultaneously" with the current
501 # write to dirstate (i.e. within the same second for file-
513 # write to dirstate (i.e. within the same second for file-
502 # systems with a granularity of 1 sec). This commonly happens
514 # systems with a granularity of 1 sec). This commonly happens
503 # for at least a couple of files on 'update'.
515 # for at least a couple of files on 'update'.
504 # The user could change the file without changing its size
516 # The user could change the file without changing its size
505 # within the same second. Invalidate the file's stat data in
517 # within the same second. Invalidate the file's stat data in
506 # dirstate, forcing future 'status' calls to compare the
518 # dirstate, forcing future 'status' calls to compare the
507 # contents of the file. This prevents mistakenly treating such
519 # contents of the file. This prevents mistakenly treating such
508 # files as clean.
520 # files as clean.
509 e = (e[0], 0, -1, -1) # mark entry as 'unset'
521 e = (e[0], 0, -1, -1) # mark entry as 'unset'
510 self._map[f] = e
522 self._map[f] = e
511
523
512 if f in copymap:
524 if f in copymap:
513 f = "%s\0%s" % (f, copymap[f])
525 f = "%s\0%s" % (f, copymap[f])
514 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
526 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
515 write(e)
527 write(e)
516 write(f)
528 write(f)
517 st.write(cs.getvalue())
529 st.write(cs.getvalue())
518 st.close()
530 st.close()
519 self._lastnormaltime = 0
531 self._lastnormaltime = 0
520 self._dirty = self._dirtypl = False
532 self._dirty = self._dirtypl = False
521
533
522 def _dirignore(self, f):
534 def _dirignore(self, f):
523 if f == '.':
535 if f == '.':
524 return False
536 return False
525 if self._ignore(f):
537 if self._ignore(f):
526 return True
538 return True
527 for p in _finddirs(f):
539 for p in _finddirs(f):
528 if self._ignore(p):
540 if self._ignore(p):
529 return True
541 return True
530 return False
542 return False
531
543
532 def walk(self, match, subrepos, unknown, ignored):
544 def walk(self, match, subrepos, unknown, ignored):
533 '''
545 '''
534 Walk recursively through the directory tree, finding all files
546 Walk recursively through the directory tree, finding all files
535 matched by match.
547 matched by match.
536
548
537 Return a dict mapping filename to stat-like object (either
549 Return a dict mapping filename to stat-like object (either
538 mercurial.osutil.stat instance or return value of os.stat()).
550 mercurial.osutil.stat instance or return value of os.stat()).
539 '''
551 '''
540
552
541 def fwarn(f, msg):
553 def fwarn(f, msg):
542 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
554 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
543 return False
555 return False
544
556
545 def badtype(mode):
557 def badtype(mode):
546 kind = _('unknown')
558 kind = _('unknown')
547 if stat.S_ISCHR(mode):
559 if stat.S_ISCHR(mode):
548 kind = _('character device')
560 kind = _('character device')
549 elif stat.S_ISBLK(mode):
561 elif stat.S_ISBLK(mode):
550 kind = _('block device')
562 kind = _('block device')
551 elif stat.S_ISFIFO(mode):
563 elif stat.S_ISFIFO(mode):
552 kind = _('fifo')
564 kind = _('fifo')
553 elif stat.S_ISSOCK(mode):
565 elif stat.S_ISSOCK(mode):
554 kind = _('socket')
566 kind = _('socket')
555 elif stat.S_ISDIR(mode):
567 elif stat.S_ISDIR(mode):
556 kind = _('directory')
568 kind = _('directory')
557 return _('unsupported file type (type is %s)') % kind
569 return _('unsupported file type (type is %s)') % kind
558
570
559 ignore = self._ignore
571 ignore = self._ignore
560 dirignore = self._dirignore
572 dirignore = self._dirignore
561 if ignored:
573 if ignored:
562 ignore = util.never
574 ignore = util.never
563 dirignore = util.never
575 dirignore = util.never
564 elif not unknown:
576 elif not unknown:
565 # if unknown and ignored are False, skip step 2
577 # if unknown and ignored are False, skip step 2
566 ignore = util.always
578 ignore = util.always
567 dirignore = util.always
579 dirignore = util.always
568
580
569 matchfn = match.matchfn
581 matchfn = match.matchfn
570 badfn = match.bad
582 badfn = match.bad
571 dmap = self._map
583 dmap = self._map
572 normpath = util.normpath
584 normpath = util.normpath
573 listdir = osutil.listdir
585 listdir = osutil.listdir
574 lstat = os.lstat
586 lstat = os.lstat
575 getkind = stat.S_IFMT
587 getkind = stat.S_IFMT
576 dirkind = stat.S_IFDIR
588 dirkind = stat.S_IFDIR
577 regkind = stat.S_IFREG
589 regkind = stat.S_IFREG
578 lnkkind = stat.S_IFLNK
590 lnkkind = stat.S_IFLNK
579 join = self._join
591 join = self._join
580 work = []
592 work = []
581 wadd = work.append
593 wadd = work.append
582
594
583 exact = skipstep3 = False
595 exact = skipstep3 = False
584 if matchfn == match.exact: # match.exact
596 if matchfn == match.exact: # match.exact
585 exact = True
597 exact = True
586 dirignore = util.always # skip step 2
598 dirignore = util.always # skip step 2
587 elif match.files() and not match.anypats(): # match.match, no patterns
599 elif match.files() and not match.anypats(): # match.match, no patterns
588 skipstep3 = True
600 skipstep3 = True
589
601
590 if not exact and self._checkcase:
602 if not exact and self._checkcase:
591 normalize = self._normalize
603 normalize = self._normalize
592 skipstep3 = False
604 skipstep3 = False
593 else:
605 else:
594 normalize = lambda x, y, z: x
606 normalize = lambda x, y, z: x
595
607
596 files = sorted(match.files())
608 files = sorted(match.files())
597 subrepos.sort()
609 subrepos.sort()
598 i, j = 0, 0
610 i, j = 0, 0
599 while i < len(files) and j < len(subrepos):
611 while i < len(files) and j < len(subrepos):
600 subpath = subrepos[j] + "/"
612 subpath = subrepos[j] + "/"
601 if files[i] < subpath:
613 if files[i] < subpath:
602 i += 1
614 i += 1
603 continue
615 continue
604 while i < len(files) and files[i].startswith(subpath):
616 while i < len(files) and files[i].startswith(subpath):
605 del files[i]
617 del files[i]
606 j += 1
618 j += 1
607
619
608 if not files or '.' in files:
620 if not files or '.' in files:
609 files = ['']
621 files = ['']
610 results = dict.fromkeys(subrepos)
622 results = dict.fromkeys(subrepos)
611 results['.hg'] = None
623 results['.hg'] = None
612
624
613 # step 1: find all explicit files
625 # step 1: find all explicit files
614 for ff in files:
626 for ff in files:
615 nf = normalize(normpath(ff), False, True)
627 nf = normalize(normpath(ff), False, True)
616 if nf in results:
628 if nf in results:
617 continue
629 continue
618
630
619 try:
631 try:
620 st = lstat(join(nf))
632 st = lstat(join(nf))
621 kind = getkind(st.st_mode)
633 kind = getkind(st.st_mode)
622 if kind == dirkind:
634 if kind == dirkind:
623 skipstep3 = False
635 skipstep3 = False
624 if nf in dmap:
636 if nf in dmap:
625 #file deleted on disk but still in dirstate
637 #file deleted on disk but still in dirstate
626 results[nf] = None
638 results[nf] = None
627 match.dir(nf)
639 match.dir(nf)
628 if not dirignore(nf):
640 if not dirignore(nf):
629 wadd(nf)
641 wadd(nf)
630 elif kind == regkind or kind == lnkkind:
642 elif kind == regkind or kind == lnkkind:
631 results[nf] = st
643 results[nf] = st
632 else:
644 else:
633 badfn(ff, badtype(kind))
645 badfn(ff, badtype(kind))
634 if nf in dmap:
646 if nf in dmap:
635 results[nf] = None
647 results[nf] = None
636 except OSError, inst:
648 except OSError, inst:
637 if nf in dmap: # does it exactly match a file?
649 if nf in dmap: # does it exactly match a file?
638 results[nf] = None
650 results[nf] = None
639 else: # does it match a directory?
651 else: # does it match a directory?
640 prefix = nf + "/"
652 prefix = nf + "/"
641 for fn in dmap:
653 for fn in dmap:
642 if fn.startswith(prefix):
654 if fn.startswith(prefix):
643 match.dir(nf)
655 match.dir(nf)
644 skipstep3 = False
656 skipstep3 = False
645 break
657 break
646 else:
658 else:
647 badfn(ff, inst.strerror)
659 badfn(ff, inst.strerror)
648
660
649 # step 2: visit subdirectories
661 # step 2: visit subdirectories
650 while work:
662 while work:
651 nd = work.pop()
663 nd = work.pop()
652 skip = None
664 skip = None
653 if nd == '.':
665 if nd == '.':
654 nd = ''
666 nd = ''
655 else:
667 else:
656 skip = '.hg'
668 skip = '.hg'
657 try:
669 try:
658 entries = listdir(join(nd), stat=True, skip=skip)
670 entries = listdir(join(nd), stat=True, skip=skip)
659 except OSError, inst:
671 except OSError, inst:
660 if inst.errno == errno.EACCES:
672 if inst.errno == errno.EACCES:
661 fwarn(nd, inst.strerror)
673 fwarn(nd, inst.strerror)
662 continue
674 continue
663 raise
675 raise
664 for f, kind, st in entries:
676 for f, kind, st in entries:
665 nf = normalize(nd and (nd + "/" + f) or f, True, True)
677 nf = normalize(nd and (nd + "/" + f) or f, True, True)
666 if nf not in results:
678 if nf not in results:
667 if kind == dirkind:
679 if kind == dirkind:
668 if not ignore(nf):
680 if not ignore(nf):
669 match.dir(nf)
681 match.dir(nf)
670 wadd(nf)
682 wadd(nf)
671 if nf in dmap and matchfn(nf):
683 if nf in dmap and matchfn(nf):
672 results[nf] = None
684 results[nf] = None
673 elif kind == regkind or kind == lnkkind:
685 elif kind == regkind or kind == lnkkind:
674 if nf in dmap:
686 if nf in dmap:
675 if matchfn(nf):
687 if matchfn(nf):
676 results[nf] = st
688 results[nf] = st
677 elif matchfn(nf) and not ignore(nf):
689 elif matchfn(nf) and not ignore(nf):
678 results[nf] = st
690 results[nf] = st
679 elif nf in dmap and matchfn(nf):
691 elif nf in dmap and matchfn(nf):
680 results[nf] = None
692 results[nf] = None
681
693
682 # step 3: report unseen items in the dmap hash
694 # step 3: report unseen items in the dmap hash
683 if not skipstep3 and not exact:
695 if not skipstep3 and not exact:
684 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
696 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
685 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
697 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
686 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
698 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
687 st = None
699 st = None
688 results[nf] = st
700 results[nf] = st
689 for s in subrepos:
701 for s in subrepos:
690 del results[s]
702 del results[s]
691 del results['.hg']
703 del results['.hg']
692 return results
704 return results
693
705
694 def status(self, match, subrepos, ignored, clean, unknown):
706 def status(self, match, subrepos, ignored, clean, unknown):
695 '''Determine the status of the working copy relative to the
707 '''Determine the status of the working copy relative to the
696 dirstate and return a tuple of lists (unsure, modified, added,
708 dirstate and return a tuple of lists (unsure, modified, added,
697 removed, deleted, unknown, ignored, clean), where:
709 removed, deleted, unknown, ignored, clean), where:
698
710
699 unsure:
711 unsure:
700 files that might have been modified since the dirstate was
712 files that might have been modified since the dirstate was
701 written, but need to be read to be sure (size is the same
713 written, but need to be read to be sure (size is the same
702 but mtime differs)
714 but mtime differs)
703 modified:
715 modified:
704 files that have definitely been modified since the dirstate
716 files that have definitely been modified since the dirstate
705 was written (different size or mode)
717 was written (different size or mode)
706 added:
718 added:
707 files that have been explicitly added with hg add
719 files that have been explicitly added with hg add
708 removed:
720 removed:
709 files that have been explicitly removed with hg remove
721 files that have been explicitly removed with hg remove
710 deleted:
722 deleted:
711 files that have been deleted through other means ("missing")
723 files that have been deleted through other means ("missing")
712 unknown:
724 unknown:
713 files not in the dirstate that are not ignored
725 files not in the dirstate that are not ignored
714 ignored:
726 ignored:
715 files not in the dirstate that are ignored
727 files not in the dirstate that are ignored
716 (by _dirignore())
728 (by _dirignore())
717 clean:
729 clean:
718 files that have definitely not been modified since the
730 files that have definitely not been modified since the
719 dirstate was written
731 dirstate was written
720 '''
732 '''
721 listignored, listclean, listunknown = ignored, clean, unknown
733 listignored, listclean, listunknown = ignored, clean, unknown
722 lookup, modified, added, unknown, ignored = [], [], [], [], []
734 lookup, modified, added, unknown, ignored = [], [], [], [], []
723 removed, deleted, clean = [], [], []
735 removed, deleted, clean = [], [], []
724
736
725 dmap = self._map
737 dmap = self._map
726 ladd = lookup.append # aka "unsure"
738 ladd = lookup.append # aka "unsure"
727 madd = modified.append
739 madd = modified.append
728 aadd = added.append
740 aadd = added.append
729 uadd = unknown.append
741 uadd = unknown.append
730 iadd = ignored.append
742 iadd = ignored.append
731 radd = removed.append
743 radd = removed.append
732 dadd = deleted.append
744 dadd = deleted.append
733 cadd = clean.append
745 cadd = clean.append
734
746
735 lnkkind = stat.S_IFLNK
747 lnkkind = stat.S_IFLNK
736
748
737 for fn, st in self.walk(match, subrepos, listunknown,
749 for fn, st in self.walk(match, subrepos, listunknown,
738 listignored).iteritems():
750 listignored).iteritems():
739 if fn not in dmap:
751 if fn not in dmap:
740 if (listignored or match.exact(fn)) and self._dirignore(fn):
752 if (listignored or match.exact(fn)) and self._dirignore(fn):
741 if listignored:
753 if listignored:
742 iadd(fn)
754 iadd(fn)
743 elif listunknown:
755 elif listunknown:
744 uadd(fn)
756 uadd(fn)
745 continue
757 continue
746
758
747 state, mode, size, time = dmap[fn]
759 state, mode, size, time = dmap[fn]
748
760
749 if not st and state in "nma":
761 if not st and state in "nma":
750 dadd(fn)
762 dadd(fn)
751 elif state == 'n':
763 elif state == 'n':
752 # The "mode & lnkkind != lnkkind or self._checklink"
764 # The "mode & lnkkind != lnkkind or self._checklink"
753 # lines are an expansion of "islink => checklink"
765 # lines are an expansion of "islink => checklink"
754 # where islink means "is this a link?" and checklink
766 # where islink means "is this a link?" and checklink
755 # means "can we check links?".
767 # means "can we check links?".
756 mtime = int(st.st_mtime)
768 mtime = int(st.st_mtime)
757 if (size >= 0 and
769 if (size >= 0 and
758 (size != st.st_size
770 (size != st.st_size
759 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
771 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
760 and (mode & lnkkind != lnkkind or self._checklink)
772 and (mode & lnkkind != lnkkind or self._checklink)
761 or size == -2 # other parent
773 or size == -2 # other parent
762 or fn in self._copymap):
774 or fn in self._copymap):
763 madd(fn)
775 madd(fn)
764 elif (mtime != time
776 elif (mtime != time
765 and (mode & lnkkind != lnkkind or self._checklink)):
777 and (mode & lnkkind != lnkkind or self._checklink)):
766 ladd(fn)
778 ladd(fn)
767 elif mtime == self._lastnormaltime:
779 elif mtime == self._lastnormaltime:
768 # fn may have been changed in the same timeslot without
780 # fn may have been changed in the same timeslot without
769 # changing its size. This can happen if we quickly do
781 # changing its size. This can happen if we quickly do
770 # multiple commits in a single transaction.
782 # multiple commits in a single transaction.
771 # Force lookup, so we don't miss such a racy file change.
783 # Force lookup, so we don't miss such a racy file change.
772 ladd(fn)
784 ladd(fn)
773 elif listclean:
785 elif listclean:
774 cadd(fn)
786 cadd(fn)
775 elif state == 'm':
787 elif state == 'm':
776 madd(fn)
788 madd(fn)
777 elif state == 'a':
789 elif state == 'a':
778 aadd(fn)
790 aadd(fn)
779 elif state == 'r':
791 elif state == 'r':
780 radd(fn)
792 radd(fn)
781
793
782 return (lookup, modified, added, removed, deleted, unknown, ignored,
794 return (lookup, modified, added, removed, deleted, unknown, ignored,
783 clean)
795 clean)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now