##// END OF EJS Templates
cmdutil: put recordfunc invocation into wlock scope for consistency...
FUJIWARA Katsunori -
r25758:c5dfa47a default
parent child Browse files
Show More
@@ -1,3322 +1,3329 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, cStringIO, shutil
10 import os, sys, errno, re, tempfile, cStringIO, shutil
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 context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import encoding
16 import encoding
17 import formatter
17 import formatter
18 import crecord as crecordmod
18 import crecord as crecordmod
19 import lock as lockmod
19 import lock as lockmod
20
20
21 def ishunk(x):
21 def ishunk(x):
22 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
22 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
23 return isinstance(x, hunkclasses)
23 return isinstance(x, hunkclasses)
24
24
25 def newandmodified(chunks, originalchunks):
25 def newandmodified(chunks, originalchunks):
26 newlyaddedandmodifiedfiles = set()
26 newlyaddedandmodifiedfiles = set()
27 for chunk in chunks:
27 for chunk in chunks:
28 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
28 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
29 originalchunks:
29 originalchunks:
30 newlyaddedandmodifiedfiles.add(chunk.header.filename())
30 newlyaddedandmodifiedfiles.add(chunk.header.filename())
31 return newlyaddedandmodifiedfiles
31 return newlyaddedandmodifiedfiles
32
32
33 def parsealiases(cmd):
33 def parsealiases(cmd):
34 return cmd.lstrip("^").split("|")
34 return cmd.lstrip("^").split("|")
35
35
36 def setupwrapcolorwrite(ui):
36 def setupwrapcolorwrite(ui):
37 # wrap ui.write so diff output can be labeled/colorized
37 # wrap ui.write so diff output can be labeled/colorized
38 def wrapwrite(orig, *args, **kw):
38 def wrapwrite(orig, *args, **kw):
39 label = kw.pop('label', '')
39 label = kw.pop('label', '')
40 for chunk, l in patch.difflabel(lambda: args):
40 for chunk, l in patch.difflabel(lambda: args):
41 orig(chunk, label=label + l)
41 orig(chunk, label=label + l)
42
42
43 oldwrite = ui.write
43 oldwrite = ui.write
44 def wrap(*args, **kwargs):
44 def wrap(*args, **kwargs):
45 return wrapwrite(oldwrite, *args, **kwargs)
45 return wrapwrite(oldwrite, *args, **kwargs)
46 setattr(ui, 'write', wrap)
46 setattr(ui, 'write', wrap)
47 return oldwrite
47 return oldwrite
48
48
49 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
49 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
50 if usecurses:
50 if usecurses:
51 if testfile:
51 if testfile:
52 recordfn = crecordmod.testdecorator(testfile,
52 recordfn = crecordmod.testdecorator(testfile,
53 crecordmod.testchunkselector)
53 crecordmod.testchunkselector)
54 else:
54 else:
55 recordfn = crecordmod.chunkselector
55 recordfn = crecordmod.chunkselector
56
56
57 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
57 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
58
58
59 else:
59 else:
60 return patch.filterpatch(ui, originalhunks, operation)
60 return patch.filterpatch(ui, originalhunks, operation)
61
61
62 def recordfilter(ui, originalhunks, operation=None):
62 def recordfilter(ui, originalhunks, operation=None):
63 """ Prompts the user to filter the originalhunks and return a list of
63 """ Prompts the user to filter the originalhunks and return a list of
64 selected hunks.
64 selected hunks.
65 *operation* is used for ui purposes to indicate the user
65 *operation* is used for ui purposes to indicate the user
66 what kind of filtering they are doing: reverting, commiting, shelving, etc.
66 what kind of filtering they are doing: reverting, commiting, shelving, etc.
67 *operation* has to be a translated string.
67 *operation* has to be a translated string.
68 """
68 """
69 usecurses = ui.configbool('experimental', 'crecord', False)
69 usecurses = ui.configbool('experimental', 'crecord', False)
70 testfile = ui.config('experimental', 'crecordtest', None)
70 testfile = ui.config('experimental', 'crecordtest', None)
71 oldwrite = setupwrapcolorwrite(ui)
71 oldwrite = setupwrapcolorwrite(ui)
72 try:
72 try:
73 newchunks = filterchunks(ui, originalhunks, usecurses, testfile,
73 newchunks = filterchunks(ui, originalhunks, usecurses, testfile,
74 operation)
74 operation)
75 finally:
75 finally:
76 ui.write = oldwrite
76 ui.write = oldwrite
77 return newchunks
77 return newchunks
78
78
79 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
79 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
80 filterfn, *pats, **opts):
80 filterfn, *pats, **opts):
81 import merge as mergemod
81 import merge as mergemod
82
82
83 if not ui.interactive():
83 if not ui.interactive():
84 raise util.Abort(_('running non-interactively, use %s instead') %
84 raise util.Abort(_('running non-interactively, use %s instead') %
85 cmdsuggest)
85 cmdsuggest)
86
86
87 # make sure username is set before going interactive
87 # make sure username is set before going interactive
88 if not opts.get('user'):
88 if not opts.get('user'):
89 ui.username() # raise exception, username not provided
89 ui.username() # raise exception, username not provided
90
90
91 def recordfunc(ui, repo, message, match, opts):
91 def recordfunc(ui, repo, message, match, opts):
92 """This is generic record driver.
92 """This is generic record driver.
93
93
94 Its job is to interactively filter local changes, and
94 Its job is to interactively filter local changes, and
95 accordingly prepare working directory into a state in which the
95 accordingly prepare working directory into a state in which the
96 job can be delegated to a non-interactive commit command such as
96 job can be delegated to a non-interactive commit command such as
97 'commit' or 'qrefresh'.
97 'commit' or 'qrefresh'.
98
98
99 After the actual job is done by non-interactive command, the
99 After the actual job is done by non-interactive command, the
100 working directory is restored to its original state.
100 working directory is restored to its original state.
101
101
102 In the end we'll record interesting changes, and everything else
102 In the end we'll record interesting changes, and everything else
103 will be left in place, so the user can continue working.
103 will be left in place, so the user can continue working.
104 """
104 """
105
105
106 checkunfinished(repo, commit=True)
106 checkunfinished(repo, commit=True)
107 merge = len(repo[None].parents()) > 1
107 merge = len(repo[None].parents()) > 1
108 if merge:
108 if merge:
109 raise util.Abort(_('cannot partially commit a merge '
109 raise util.Abort(_('cannot partially commit a merge '
110 '(use "hg commit" instead)'))
110 '(use "hg commit" instead)'))
111
111
112 status = repo.status(match=match)
112 status = repo.status(match=match)
113 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
113 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
114 diffopts.nodates = True
114 diffopts.nodates = True
115 diffopts.git = True
115 diffopts.git = True
116 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
116 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
117 originalchunks = patch.parsepatch(originaldiff)
117 originalchunks = patch.parsepatch(originaldiff)
118
118
119 # 1. filter patch, so we have intending-to apply subset of it
119 # 1. filter patch, so we have intending-to apply subset of it
120 try:
120 try:
121 chunks = filterfn(ui, originalchunks)
121 chunks = filterfn(ui, originalchunks)
122 except patch.PatchError as err:
122 except patch.PatchError as err:
123 raise util.Abort(_('error parsing patch: %s') % err)
123 raise util.Abort(_('error parsing patch: %s') % err)
124
124
125 # We need to keep a backup of files that have been newly added and
125 # We need to keep a backup of files that have been newly added and
126 # modified during the recording process because there is a previous
126 # modified during the recording process because there is a previous
127 # version without the edit in the workdir
127 # version without the edit in the workdir
128 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
128 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
129 contenders = set()
129 contenders = set()
130 for h in chunks:
130 for h in chunks:
131 try:
131 try:
132 contenders.update(set(h.files()))
132 contenders.update(set(h.files()))
133 except AttributeError:
133 except AttributeError:
134 pass
134 pass
135
135
136 changed = status.modified + status.added + status.removed
136 changed = status.modified + status.added + status.removed
137 newfiles = [f for f in changed if f in contenders]
137 newfiles = [f for f in changed if f in contenders]
138 if not newfiles:
138 if not newfiles:
139 ui.status(_('no changes to record\n'))
139 ui.status(_('no changes to record\n'))
140 return 0
140 return 0
141
141
142 modified = set(status.modified)
142 modified = set(status.modified)
143
143
144 # 2. backup changed files, so we can restore them in the end
144 # 2. backup changed files, so we can restore them in the end
145
145
146 if backupall:
146 if backupall:
147 tobackup = changed
147 tobackup = changed
148 else:
148 else:
149 tobackup = [f for f in newfiles if f in modified or f in \
149 tobackup = [f for f in newfiles if f in modified or f in \
150 newlyaddedandmodifiedfiles]
150 newlyaddedandmodifiedfiles]
151 backups = {}
151 backups = {}
152 if tobackup:
152 if tobackup:
153 backupdir = repo.join('record-backups')
153 backupdir = repo.join('record-backups')
154 try:
154 try:
155 os.mkdir(backupdir)
155 os.mkdir(backupdir)
156 except OSError as err:
156 except OSError as err:
157 if err.errno != errno.EEXIST:
157 if err.errno != errno.EEXIST:
158 raise
158 raise
159 try:
159 try:
160 # backup continues
160 # backup continues
161 for f in tobackup:
161 for f in tobackup:
162 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
162 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
163 dir=backupdir)
163 dir=backupdir)
164 os.close(fd)
164 os.close(fd)
165 ui.debug('backup %r as %r\n' % (f, tmpname))
165 ui.debug('backup %r as %r\n' % (f, tmpname))
166 util.copyfile(repo.wjoin(f), tmpname)
166 util.copyfile(repo.wjoin(f), tmpname)
167 shutil.copystat(repo.wjoin(f), tmpname)
167 shutil.copystat(repo.wjoin(f), tmpname)
168 backups[f] = tmpname
168 backups[f] = tmpname
169
169
170 fp = cStringIO.StringIO()
170 fp = cStringIO.StringIO()
171 for c in chunks:
171 for c in chunks:
172 fname = c.filename()
172 fname = c.filename()
173 if fname in backups:
173 if fname in backups:
174 c.write(fp)
174 c.write(fp)
175 dopatch = fp.tell()
175 dopatch = fp.tell()
176 fp.seek(0)
176 fp.seek(0)
177
177
178 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
178 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
179 # 3a. apply filtered patch to clean repo (clean)
179 # 3a. apply filtered patch to clean repo (clean)
180 if backups:
180 if backups:
181 # Equivalent to hg.revert
181 # Equivalent to hg.revert
182 choices = lambda key: key in backups
182 choices = lambda key: key in backups
183 mergemod.update(repo, repo.dirstate.p1(),
183 mergemod.update(repo, repo.dirstate.p1(),
184 False, True, choices)
184 False, True, choices)
185
185
186 # 3b. (apply)
186 # 3b. (apply)
187 if dopatch:
187 if dopatch:
188 try:
188 try:
189 ui.debug('applying patch\n')
189 ui.debug('applying patch\n')
190 ui.debug(fp.getvalue())
190 ui.debug(fp.getvalue())
191 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
191 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
192 except patch.PatchError as err:
192 except patch.PatchError as err:
193 raise util.Abort(str(err))
193 raise util.Abort(str(err))
194 del fp
194 del fp
195
195
196 # 4. We prepared working directory according to filtered
196 # 4. We prepared working directory according to filtered
197 # patch. Now is the time to delegate the job to
197 # patch. Now is the time to delegate the job to
198 # commit/qrefresh or the like!
198 # commit/qrefresh or the like!
199
199
200 # Make all of the pathnames absolute.
200 # Make all of the pathnames absolute.
201 newfiles = [repo.wjoin(nf) for nf in newfiles]
201 newfiles = [repo.wjoin(nf) for nf in newfiles]
202 return commitfunc(ui, repo, *newfiles, **opts)
202 return commitfunc(ui, repo, *newfiles, **opts)
203 finally:
203 finally:
204 # 5. finally restore backed-up files
204 # 5. finally restore backed-up files
205 try:
205 try:
206 for realname, tmpname in backups.iteritems():
206 for realname, tmpname in backups.iteritems():
207 ui.debug('restoring %r to %r\n' % (tmpname, realname))
207 ui.debug('restoring %r to %r\n' % (tmpname, realname))
208 util.copyfile(tmpname, repo.wjoin(realname))
208 util.copyfile(tmpname, repo.wjoin(realname))
209 # Our calls to copystat() here and above are a
209 # Our calls to copystat() here and above are a
210 # hack to trick any editors that have f open that
210 # hack to trick any editors that have f open that
211 # we haven't modified them.
211 # we haven't modified them.
212 #
212 #
213 # Also note that this racy as an editor could
213 # Also note that this racy as an editor could
214 # notice the file's mtime before we've finished
214 # notice the file's mtime before we've finished
215 # writing it.
215 # writing it.
216 shutil.copystat(tmpname, repo.wjoin(realname))
216 shutil.copystat(tmpname, repo.wjoin(realname))
217 os.unlink(tmpname)
217 os.unlink(tmpname)
218 if tobackup:
218 if tobackup:
219 os.rmdir(backupdir)
219 os.rmdir(backupdir)
220 except OSError:
220 except OSError:
221 pass
221 pass
222
222
223 return commit(ui, repo, recordfunc, pats, opts)
223 def recordinwlock(ui, repo, message, match, opts):
224 wlock = repo.wlock()
225 try:
226 return recordfunc(ui, repo, message, match, opts)
227 finally:
228 wlock.release()
229
230 return commit(ui, repo, recordinwlock, pats, opts)
224
231
225 def findpossible(cmd, table, strict=False):
232 def findpossible(cmd, table, strict=False):
226 """
233 """
227 Return cmd -> (aliases, command table entry)
234 Return cmd -> (aliases, command table entry)
228 for each matching command.
235 for each matching command.
229 Return debug commands (or their aliases) only if no normal command matches.
236 Return debug commands (or their aliases) only if no normal command matches.
230 """
237 """
231 choice = {}
238 choice = {}
232 debugchoice = {}
239 debugchoice = {}
233
240
234 if cmd in table:
241 if cmd in table:
235 # short-circuit exact matches, "log" alias beats "^log|history"
242 # short-circuit exact matches, "log" alias beats "^log|history"
236 keys = [cmd]
243 keys = [cmd]
237 else:
244 else:
238 keys = table.keys()
245 keys = table.keys()
239
246
240 allcmds = []
247 allcmds = []
241 for e in keys:
248 for e in keys:
242 aliases = parsealiases(e)
249 aliases = parsealiases(e)
243 allcmds.extend(aliases)
250 allcmds.extend(aliases)
244 found = None
251 found = None
245 if cmd in aliases:
252 if cmd in aliases:
246 found = cmd
253 found = cmd
247 elif not strict:
254 elif not strict:
248 for a in aliases:
255 for a in aliases:
249 if a.startswith(cmd):
256 if a.startswith(cmd):
250 found = a
257 found = a
251 break
258 break
252 if found is not None:
259 if found is not None:
253 if aliases[0].startswith("debug") or found.startswith("debug"):
260 if aliases[0].startswith("debug") or found.startswith("debug"):
254 debugchoice[found] = (aliases, table[e])
261 debugchoice[found] = (aliases, table[e])
255 else:
262 else:
256 choice[found] = (aliases, table[e])
263 choice[found] = (aliases, table[e])
257
264
258 if not choice and debugchoice:
265 if not choice and debugchoice:
259 choice = debugchoice
266 choice = debugchoice
260
267
261 return choice, allcmds
268 return choice, allcmds
262
269
263 def findcmd(cmd, table, strict=True):
270 def findcmd(cmd, table, strict=True):
264 """Return (aliases, command table entry) for command string."""
271 """Return (aliases, command table entry) for command string."""
265 choice, allcmds = findpossible(cmd, table, strict)
272 choice, allcmds = findpossible(cmd, table, strict)
266
273
267 if cmd in choice:
274 if cmd in choice:
268 return choice[cmd]
275 return choice[cmd]
269
276
270 if len(choice) > 1:
277 if len(choice) > 1:
271 clist = choice.keys()
278 clist = choice.keys()
272 clist.sort()
279 clist.sort()
273 raise error.AmbiguousCommand(cmd, clist)
280 raise error.AmbiguousCommand(cmd, clist)
274
281
275 if choice:
282 if choice:
276 return choice.values()[0]
283 return choice.values()[0]
277
284
278 raise error.UnknownCommand(cmd, allcmds)
285 raise error.UnknownCommand(cmd, allcmds)
279
286
280 def findrepo(p):
287 def findrepo(p):
281 while not os.path.isdir(os.path.join(p, ".hg")):
288 while not os.path.isdir(os.path.join(p, ".hg")):
282 oldp, p = p, os.path.dirname(p)
289 oldp, p = p, os.path.dirname(p)
283 if p == oldp:
290 if p == oldp:
284 return None
291 return None
285
292
286 return p
293 return p
287
294
288 def bailifchanged(repo, merge=True):
295 def bailifchanged(repo, merge=True):
289 if merge and repo.dirstate.p2() != nullid:
296 if merge and repo.dirstate.p2() != nullid:
290 raise util.Abort(_('outstanding uncommitted merge'))
297 raise util.Abort(_('outstanding uncommitted merge'))
291 modified, added, removed, deleted = repo.status()[:4]
298 modified, added, removed, deleted = repo.status()[:4]
292 if modified or added or removed or deleted:
299 if modified or added or removed or deleted:
293 raise util.Abort(_('uncommitted changes'))
300 raise util.Abort(_('uncommitted changes'))
294 ctx = repo[None]
301 ctx = repo[None]
295 for s in sorted(ctx.substate):
302 for s in sorted(ctx.substate):
296 ctx.sub(s).bailifchanged()
303 ctx.sub(s).bailifchanged()
297
304
298 def logmessage(ui, opts):
305 def logmessage(ui, opts):
299 """ get the log message according to -m and -l option """
306 """ get the log message according to -m and -l option """
300 message = opts.get('message')
307 message = opts.get('message')
301 logfile = opts.get('logfile')
308 logfile = opts.get('logfile')
302
309
303 if message and logfile:
310 if message and logfile:
304 raise util.Abort(_('options --message and --logfile are mutually '
311 raise util.Abort(_('options --message and --logfile are mutually '
305 'exclusive'))
312 'exclusive'))
306 if not message and logfile:
313 if not message and logfile:
307 try:
314 try:
308 if logfile == '-':
315 if logfile == '-':
309 message = ui.fin.read()
316 message = ui.fin.read()
310 else:
317 else:
311 message = '\n'.join(util.readfile(logfile).splitlines())
318 message = '\n'.join(util.readfile(logfile).splitlines())
312 except IOError as inst:
319 except IOError as inst:
313 raise util.Abort(_("can't read commit message '%s': %s") %
320 raise util.Abort(_("can't read commit message '%s': %s") %
314 (logfile, inst.strerror))
321 (logfile, inst.strerror))
315 return message
322 return message
316
323
317 def mergeeditform(ctxorbool, baseformname):
324 def mergeeditform(ctxorbool, baseformname):
318 """return appropriate editform name (referencing a committemplate)
325 """return appropriate editform name (referencing a committemplate)
319
326
320 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
327 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
321 merging is committed.
328 merging is committed.
322
329
323 This returns baseformname with '.merge' appended if it is a merge,
330 This returns baseformname with '.merge' appended if it is a merge,
324 otherwise '.normal' is appended.
331 otherwise '.normal' is appended.
325 """
332 """
326 if isinstance(ctxorbool, bool):
333 if isinstance(ctxorbool, bool):
327 if ctxorbool:
334 if ctxorbool:
328 return baseformname + ".merge"
335 return baseformname + ".merge"
329 elif 1 < len(ctxorbool.parents()):
336 elif 1 < len(ctxorbool.parents()):
330 return baseformname + ".merge"
337 return baseformname + ".merge"
331
338
332 return baseformname + ".normal"
339 return baseformname + ".normal"
333
340
334 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
341 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
335 editform='', **opts):
342 editform='', **opts):
336 """get appropriate commit message editor according to '--edit' option
343 """get appropriate commit message editor according to '--edit' option
337
344
338 'finishdesc' is a function to be called with edited commit message
345 'finishdesc' is a function to be called with edited commit message
339 (= 'description' of the new changeset) just after editing, but
346 (= 'description' of the new changeset) just after editing, but
340 before checking empty-ness. It should return actual text to be
347 before checking empty-ness. It should return actual text to be
341 stored into history. This allows to change description before
348 stored into history. This allows to change description before
342 storing.
349 storing.
343
350
344 'extramsg' is a extra message to be shown in the editor instead of
351 'extramsg' is a extra message to be shown in the editor instead of
345 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
352 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
346 is automatically added.
353 is automatically added.
347
354
348 'editform' is a dot-separated list of names, to distinguish
355 'editform' is a dot-separated list of names, to distinguish
349 the purpose of commit text editing.
356 the purpose of commit text editing.
350
357
351 'getcommiteditor' returns 'commitforceeditor' regardless of
358 'getcommiteditor' returns 'commitforceeditor' regardless of
352 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
359 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
353 they are specific for usage in MQ.
360 they are specific for usage in MQ.
354 """
361 """
355 if edit or finishdesc or extramsg:
362 if edit or finishdesc or extramsg:
356 return lambda r, c, s: commitforceeditor(r, c, s,
363 return lambda r, c, s: commitforceeditor(r, c, s,
357 finishdesc=finishdesc,
364 finishdesc=finishdesc,
358 extramsg=extramsg,
365 extramsg=extramsg,
359 editform=editform)
366 editform=editform)
360 elif editform:
367 elif editform:
361 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
368 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
362 else:
369 else:
363 return commiteditor
370 return commiteditor
364
371
365 def loglimit(opts):
372 def loglimit(opts):
366 """get the log limit according to option -l/--limit"""
373 """get the log limit according to option -l/--limit"""
367 limit = opts.get('limit')
374 limit = opts.get('limit')
368 if limit:
375 if limit:
369 try:
376 try:
370 limit = int(limit)
377 limit = int(limit)
371 except ValueError:
378 except ValueError:
372 raise util.Abort(_('limit must be a positive integer'))
379 raise util.Abort(_('limit must be a positive integer'))
373 if limit <= 0:
380 if limit <= 0:
374 raise util.Abort(_('limit must be positive'))
381 raise util.Abort(_('limit must be positive'))
375 else:
382 else:
376 limit = None
383 limit = None
377 return limit
384 return limit
378
385
379 def makefilename(repo, pat, node, desc=None,
386 def makefilename(repo, pat, node, desc=None,
380 total=None, seqno=None, revwidth=None, pathname=None):
387 total=None, seqno=None, revwidth=None, pathname=None):
381 node_expander = {
388 node_expander = {
382 'H': lambda: hex(node),
389 'H': lambda: hex(node),
383 'R': lambda: str(repo.changelog.rev(node)),
390 'R': lambda: str(repo.changelog.rev(node)),
384 'h': lambda: short(node),
391 'h': lambda: short(node),
385 'm': lambda: re.sub('[^\w]', '_', str(desc))
392 'm': lambda: re.sub('[^\w]', '_', str(desc))
386 }
393 }
387 expander = {
394 expander = {
388 '%': lambda: '%',
395 '%': lambda: '%',
389 'b': lambda: os.path.basename(repo.root),
396 'b': lambda: os.path.basename(repo.root),
390 }
397 }
391
398
392 try:
399 try:
393 if node:
400 if node:
394 expander.update(node_expander)
401 expander.update(node_expander)
395 if node:
402 if node:
396 expander['r'] = (lambda:
403 expander['r'] = (lambda:
397 str(repo.changelog.rev(node)).zfill(revwidth or 0))
404 str(repo.changelog.rev(node)).zfill(revwidth or 0))
398 if total is not None:
405 if total is not None:
399 expander['N'] = lambda: str(total)
406 expander['N'] = lambda: str(total)
400 if seqno is not None:
407 if seqno is not None:
401 expander['n'] = lambda: str(seqno)
408 expander['n'] = lambda: str(seqno)
402 if total is not None and seqno is not None:
409 if total is not None and seqno is not None:
403 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
410 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
404 if pathname is not None:
411 if pathname is not None:
405 expander['s'] = lambda: os.path.basename(pathname)
412 expander['s'] = lambda: os.path.basename(pathname)
406 expander['d'] = lambda: os.path.dirname(pathname) or '.'
413 expander['d'] = lambda: os.path.dirname(pathname) or '.'
407 expander['p'] = lambda: pathname
414 expander['p'] = lambda: pathname
408
415
409 newname = []
416 newname = []
410 patlen = len(pat)
417 patlen = len(pat)
411 i = 0
418 i = 0
412 while i < patlen:
419 while i < patlen:
413 c = pat[i]
420 c = pat[i]
414 if c == '%':
421 if c == '%':
415 i += 1
422 i += 1
416 c = pat[i]
423 c = pat[i]
417 c = expander[c]()
424 c = expander[c]()
418 newname.append(c)
425 newname.append(c)
419 i += 1
426 i += 1
420 return ''.join(newname)
427 return ''.join(newname)
421 except KeyError as inst:
428 except KeyError as inst:
422 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
429 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
423 inst.args[0])
430 inst.args[0])
424
431
425 def makefileobj(repo, pat, node=None, desc=None, total=None,
432 def makefileobj(repo, pat, node=None, desc=None, total=None,
426 seqno=None, revwidth=None, mode='wb', modemap=None,
433 seqno=None, revwidth=None, mode='wb', modemap=None,
427 pathname=None):
434 pathname=None):
428
435
429 writable = mode not in ('r', 'rb')
436 writable = mode not in ('r', 'rb')
430
437
431 if not pat or pat == '-':
438 if not pat or pat == '-':
432 if writable:
439 if writable:
433 fp = repo.ui.fout
440 fp = repo.ui.fout
434 else:
441 else:
435 fp = repo.ui.fin
442 fp = repo.ui.fin
436 if util.safehasattr(fp, 'fileno'):
443 if util.safehasattr(fp, 'fileno'):
437 return os.fdopen(os.dup(fp.fileno()), mode)
444 return os.fdopen(os.dup(fp.fileno()), mode)
438 else:
445 else:
439 # if this fp can't be duped properly, return
446 # if this fp can't be duped properly, return
440 # a dummy object that can be closed
447 # a dummy object that can be closed
441 class wrappedfileobj(object):
448 class wrappedfileobj(object):
442 noop = lambda x: None
449 noop = lambda x: None
443 def __init__(self, f):
450 def __init__(self, f):
444 self.f = f
451 self.f = f
445 def __getattr__(self, attr):
452 def __getattr__(self, attr):
446 if attr == 'close':
453 if attr == 'close':
447 return self.noop
454 return self.noop
448 else:
455 else:
449 return getattr(self.f, attr)
456 return getattr(self.f, attr)
450
457
451 return wrappedfileobj(fp)
458 return wrappedfileobj(fp)
452 if util.safehasattr(pat, 'write') and writable:
459 if util.safehasattr(pat, 'write') and writable:
453 return pat
460 return pat
454 if util.safehasattr(pat, 'read') and 'r' in mode:
461 if util.safehasattr(pat, 'read') and 'r' in mode:
455 return pat
462 return pat
456 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
463 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
457 if modemap is not None:
464 if modemap is not None:
458 mode = modemap.get(fn, mode)
465 mode = modemap.get(fn, mode)
459 if mode == 'wb':
466 if mode == 'wb':
460 modemap[fn] = 'ab'
467 modemap[fn] = 'ab'
461 return open(fn, mode)
468 return open(fn, mode)
462
469
463 def openrevlog(repo, cmd, file_, opts):
470 def openrevlog(repo, cmd, file_, opts):
464 """opens the changelog, manifest, a filelog or a given revlog"""
471 """opens the changelog, manifest, a filelog or a given revlog"""
465 cl = opts['changelog']
472 cl = opts['changelog']
466 mf = opts['manifest']
473 mf = opts['manifest']
467 dir = opts['dir']
474 dir = opts['dir']
468 msg = None
475 msg = None
469 if cl and mf:
476 if cl and mf:
470 msg = _('cannot specify --changelog and --manifest at the same time')
477 msg = _('cannot specify --changelog and --manifest at the same time')
471 elif cl and dir:
478 elif cl and dir:
472 msg = _('cannot specify --changelog and --dir at the same time')
479 msg = _('cannot specify --changelog and --dir at the same time')
473 elif cl or mf:
480 elif cl or mf:
474 if file_:
481 if file_:
475 msg = _('cannot specify filename with --changelog or --manifest')
482 msg = _('cannot specify filename with --changelog or --manifest')
476 elif not repo:
483 elif not repo:
477 msg = _('cannot specify --changelog or --manifest or --dir '
484 msg = _('cannot specify --changelog or --manifest or --dir '
478 'without a repository')
485 'without a repository')
479 if msg:
486 if msg:
480 raise util.Abort(msg)
487 raise util.Abort(msg)
481
488
482 r = None
489 r = None
483 if repo:
490 if repo:
484 if cl:
491 if cl:
485 r = repo.unfiltered().changelog
492 r = repo.unfiltered().changelog
486 elif dir:
493 elif dir:
487 if 'treemanifest' not in repo.requirements:
494 if 'treemanifest' not in repo.requirements:
488 raise util.Abort(_("--dir can only be used on repos with "
495 raise util.Abort(_("--dir can only be used on repos with "
489 "treemanifest enabled"))
496 "treemanifest enabled"))
490 dirlog = repo.dirlog(file_)
497 dirlog = repo.dirlog(file_)
491 if len(dirlog):
498 if len(dirlog):
492 r = dirlog
499 r = dirlog
493 elif mf:
500 elif mf:
494 r = repo.manifest
501 r = repo.manifest
495 elif file_:
502 elif file_:
496 filelog = repo.file(file_)
503 filelog = repo.file(file_)
497 if len(filelog):
504 if len(filelog):
498 r = filelog
505 r = filelog
499 if not r:
506 if not r:
500 if not file_:
507 if not file_:
501 raise error.CommandError(cmd, _('invalid arguments'))
508 raise error.CommandError(cmd, _('invalid arguments'))
502 if not os.path.isfile(file_):
509 if not os.path.isfile(file_):
503 raise util.Abort(_("revlog '%s' not found") % file_)
510 raise util.Abort(_("revlog '%s' not found") % file_)
504 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
511 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
505 file_[:-2] + ".i")
512 file_[:-2] + ".i")
506 return r
513 return r
507
514
508 def copy(ui, repo, pats, opts, rename=False):
515 def copy(ui, repo, pats, opts, rename=False):
509 # called with the repo lock held
516 # called with the repo lock held
510 #
517 #
511 # hgsep => pathname that uses "/" to separate directories
518 # hgsep => pathname that uses "/" to separate directories
512 # ossep => pathname that uses os.sep to separate directories
519 # ossep => pathname that uses os.sep to separate directories
513 cwd = repo.getcwd()
520 cwd = repo.getcwd()
514 targets = {}
521 targets = {}
515 after = opts.get("after")
522 after = opts.get("after")
516 dryrun = opts.get("dry_run")
523 dryrun = opts.get("dry_run")
517 wctx = repo[None]
524 wctx = repo[None]
518
525
519 def walkpat(pat):
526 def walkpat(pat):
520 srcs = []
527 srcs = []
521 if after:
528 if after:
522 badstates = '?'
529 badstates = '?'
523 else:
530 else:
524 badstates = '?r'
531 badstates = '?r'
525 m = scmutil.match(repo[None], [pat], opts, globbed=True)
532 m = scmutil.match(repo[None], [pat], opts, globbed=True)
526 for abs in repo.walk(m):
533 for abs in repo.walk(m):
527 state = repo.dirstate[abs]
534 state = repo.dirstate[abs]
528 rel = m.rel(abs)
535 rel = m.rel(abs)
529 exact = m.exact(abs)
536 exact = m.exact(abs)
530 if state in badstates:
537 if state in badstates:
531 if exact and state == '?':
538 if exact and state == '?':
532 ui.warn(_('%s: not copying - file is not managed\n') % rel)
539 ui.warn(_('%s: not copying - file is not managed\n') % rel)
533 if exact and state == 'r':
540 if exact and state == 'r':
534 ui.warn(_('%s: not copying - file has been marked for'
541 ui.warn(_('%s: not copying - file has been marked for'
535 ' remove\n') % rel)
542 ' remove\n') % rel)
536 continue
543 continue
537 # abs: hgsep
544 # abs: hgsep
538 # rel: ossep
545 # rel: ossep
539 srcs.append((abs, rel, exact))
546 srcs.append((abs, rel, exact))
540 return srcs
547 return srcs
541
548
542 # abssrc: hgsep
549 # abssrc: hgsep
543 # relsrc: ossep
550 # relsrc: ossep
544 # otarget: ossep
551 # otarget: ossep
545 def copyfile(abssrc, relsrc, otarget, exact):
552 def copyfile(abssrc, relsrc, otarget, exact):
546 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
553 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
547 if '/' in abstarget:
554 if '/' in abstarget:
548 # We cannot normalize abstarget itself, this would prevent
555 # We cannot normalize abstarget itself, this would prevent
549 # case only renames, like a => A.
556 # case only renames, like a => A.
550 abspath, absname = abstarget.rsplit('/', 1)
557 abspath, absname = abstarget.rsplit('/', 1)
551 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
558 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
552 reltarget = repo.pathto(abstarget, cwd)
559 reltarget = repo.pathto(abstarget, cwd)
553 target = repo.wjoin(abstarget)
560 target = repo.wjoin(abstarget)
554 src = repo.wjoin(abssrc)
561 src = repo.wjoin(abssrc)
555 state = repo.dirstate[abstarget]
562 state = repo.dirstate[abstarget]
556
563
557 scmutil.checkportable(ui, abstarget)
564 scmutil.checkportable(ui, abstarget)
558
565
559 # check for collisions
566 # check for collisions
560 prevsrc = targets.get(abstarget)
567 prevsrc = targets.get(abstarget)
561 if prevsrc is not None:
568 if prevsrc is not None:
562 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
569 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
563 (reltarget, repo.pathto(abssrc, cwd),
570 (reltarget, repo.pathto(abssrc, cwd),
564 repo.pathto(prevsrc, cwd)))
571 repo.pathto(prevsrc, cwd)))
565 return
572 return
566
573
567 # check for overwrites
574 # check for overwrites
568 exists = os.path.lexists(target)
575 exists = os.path.lexists(target)
569 samefile = False
576 samefile = False
570 if exists and abssrc != abstarget:
577 if exists and abssrc != abstarget:
571 if (repo.dirstate.normalize(abssrc) ==
578 if (repo.dirstate.normalize(abssrc) ==
572 repo.dirstate.normalize(abstarget)):
579 repo.dirstate.normalize(abstarget)):
573 if not rename:
580 if not rename:
574 ui.warn(_("%s: can't copy - same file\n") % reltarget)
581 ui.warn(_("%s: can't copy - same file\n") % reltarget)
575 return
582 return
576 exists = False
583 exists = False
577 samefile = True
584 samefile = True
578
585
579 if not after and exists or after and state in 'mn':
586 if not after and exists or after and state in 'mn':
580 if not opts['force']:
587 if not opts['force']:
581 ui.warn(_('%s: not overwriting - file exists\n') %
588 ui.warn(_('%s: not overwriting - file exists\n') %
582 reltarget)
589 reltarget)
583 return
590 return
584
591
585 if after:
592 if after:
586 if not exists:
593 if not exists:
587 if rename:
594 if rename:
588 ui.warn(_('%s: not recording move - %s does not exist\n') %
595 ui.warn(_('%s: not recording move - %s does not exist\n') %
589 (relsrc, reltarget))
596 (relsrc, reltarget))
590 else:
597 else:
591 ui.warn(_('%s: not recording copy - %s does not exist\n') %
598 ui.warn(_('%s: not recording copy - %s does not exist\n') %
592 (relsrc, reltarget))
599 (relsrc, reltarget))
593 return
600 return
594 elif not dryrun:
601 elif not dryrun:
595 try:
602 try:
596 if exists:
603 if exists:
597 os.unlink(target)
604 os.unlink(target)
598 targetdir = os.path.dirname(target) or '.'
605 targetdir = os.path.dirname(target) or '.'
599 if not os.path.isdir(targetdir):
606 if not os.path.isdir(targetdir):
600 os.makedirs(targetdir)
607 os.makedirs(targetdir)
601 if samefile:
608 if samefile:
602 tmp = target + "~hgrename"
609 tmp = target + "~hgrename"
603 os.rename(src, tmp)
610 os.rename(src, tmp)
604 os.rename(tmp, target)
611 os.rename(tmp, target)
605 else:
612 else:
606 util.copyfile(src, target)
613 util.copyfile(src, target)
607 srcexists = True
614 srcexists = True
608 except IOError as inst:
615 except IOError as inst:
609 if inst.errno == errno.ENOENT:
616 if inst.errno == errno.ENOENT:
610 ui.warn(_('%s: deleted in working directory\n') % relsrc)
617 ui.warn(_('%s: deleted in working directory\n') % relsrc)
611 srcexists = False
618 srcexists = False
612 else:
619 else:
613 ui.warn(_('%s: cannot copy - %s\n') %
620 ui.warn(_('%s: cannot copy - %s\n') %
614 (relsrc, inst.strerror))
621 (relsrc, inst.strerror))
615 return True # report a failure
622 return True # report a failure
616
623
617 if ui.verbose or not exact:
624 if ui.verbose or not exact:
618 if rename:
625 if rename:
619 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
626 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
620 else:
627 else:
621 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
628 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
622
629
623 targets[abstarget] = abssrc
630 targets[abstarget] = abssrc
624
631
625 # fix up dirstate
632 # fix up dirstate
626 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
633 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
627 dryrun=dryrun, cwd=cwd)
634 dryrun=dryrun, cwd=cwd)
628 if rename and not dryrun:
635 if rename and not dryrun:
629 if not after and srcexists and not samefile:
636 if not after and srcexists and not samefile:
630 util.unlinkpath(repo.wjoin(abssrc))
637 util.unlinkpath(repo.wjoin(abssrc))
631 wctx.forget([abssrc])
638 wctx.forget([abssrc])
632
639
633 # pat: ossep
640 # pat: ossep
634 # dest ossep
641 # dest ossep
635 # srcs: list of (hgsep, hgsep, ossep, bool)
642 # srcs: list of (hgsep, hgsep, ossep, bool)
636 # return: function that takes hgsep and returns ossep
643 # return: function that takes hgsep and returns ossep
637 def targetpathfn(pat, dest, srcs):
644 def targetpathfn(pat, dest, srcs):
638 if os.path.isdir(pat):
645 if os.path.isdir(pat):
639 abspfx = pathutil.canonpath(repo.root, cwd, pat)
646 abspfx = pathutil.canonpath(repo.root, cwd, pat)
640 abspfx = util.localpath(abspfx)
647 abspfx = util.localpath(abspfx)
641 if destdirexists:
648 if destdirexists:
642 striplen = len(os.path.split(abspfx)[0])
649 striplen = len(os.path.split(abspfx)[0])
643 else:
650 else:
644 striplen = len(abspfx)
651 striplen = len(abspfx)
645 if striplen:
652 if striplen:
646 striplen += len(os.sep)
653 striplen += len(os.sep)
647 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
654 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
648 elif destdirexists:
655 elif destdirexists:
649 res = lambda p: os.path.join(dest,
656 res = lambda p: os.path.join(dest,
650 os.path.basename(util.localpath(p)))
657 os.path.basename(util.localpath(p)))
651 else:
658 else:
652 res = lambda p: dest
659 res = lambda p: dest
653 return res
660 return res
654
661
655 # pat: ossep
662 # pat: ossep
656 # dest ossep
663 # dest ossep
657 # srcs: list of (hgsep, hgsep, ossep, bool)
664 # srcs: list of (hgsep, hgsep, ossep, bool)
658 # return: function that takes hgsep and returns ossep
665 # return: function that takes hgsep and returns ossep
659 def targetpathafterfn(pat, dest, srcs):
666 def targetpathafterfn(pat, dest, srcs):
660 if matchmod.patkind(pat):
667 if matchmod.patkind(pat):
661 # a mercurial pattern
668 # a mercurial pattern
662 res = lambda p: os.path.join(dest,
669 res = lambda p: os.path.join(dest,
663 os.path.basename(util.localpath(p)))
670 os.path.basename(util.localpath(p)))
664 else:
671 else:
665 abspfx = pathutil.canonpath(repo.root, cwd, pat)
672 abspfx = pathutil.canonpath(repo.root, cwd, pat)
666 if len(abspfx) < len(srcs[0][0]):
673 if len(abspfx) < len(srcs[0][0]):
667 # A directory. Either the target path contains the last
674 # A directory. Either the target path contains the last
668 # component of the source path or it does not.
675 # component of the source path or it does not.
669 def evalpath(striplen):
676 def evalpath(striplen):
670 score = 0
677 score = 0
671 for s in srcs:
678 for s in srcs:
672 t = os.path.join(dest, util.localpath(s[0])[striplen:])
679 t = os.path.join(dest, util.localpath(s[0])[striplen:])
673 if os.path.lexists(t):
680 if os.path.lexists(t):
674 score += 1
681 score += 1
675 return score
682 return score
676
683
677 abspfx = util.localpath(abspfx)
684 abspfx = util.localpath(abspfx)
678 striplen = len(abspfx)
685 striplen = len(abspfx)
679 if striplen:
686 if striplen:
680 striplen += len(os.sep)
687 striplen += len(os.sep)
681 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
688 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
682 score = evalpath(striplen)
689 score = evalpath(striplen)
683 striplen1 = len(os.path.split(abspfx)[0])
690 striplen1 = len(os.path.split(abspfx)[0])
684 if striplen1:
691 if striplen1:
685 striplen1 += len(os.sep)
692 striplen1 += len(os.sep)
686 if evalpath(striplen1) > score:
693 if evalpath(striplen1) > score:
687 striplen = striplen1
694 striplen = striplen1
688 res = lambda p: os.path.join(dest,
695 res = lambda p: os.path.join(dest,
689 util.localpath(p)[striplen:])
696 util.localpath(p)[striplen:])
690 else:
697 else:
691 # a file
698 # a file
692 if destdirexists:
699 if destdirexists:
693 res = lambda p: os.path.join(dest,
700 res = lambda p: os.path.join(dest,
694 os.path.basename(util.localpath(p)))
701 os.path.basename(util.localpath(p)))
695 else:
702 else:
696 res = lambda p: dest
703 res = lambda p: dest
697 return res
704 return res
698
705
699 pats = scmutil.expandpats(pats)
706 pats = scmutil.expandpats(pats)
700 if not pats:
707 if not pats:
701 raise util.Abort(_('no source or destination specified'))
708 raise util.Abort(_('no source or destination specified'))
702 if len(pats) == 1:
709 if len(pats) == 1:
703 raise util.Abort(_('no destination specified'))
710 raise util.Abort(_('no destination specified'))
704 dest = pats.pop()
711 dest = pats.pop()
705 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
712 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
706 if not destdirexists:
713 if not destdirexists:
707 if len(pats) > 1 or matchmod.patkind(pats[0]):
714 if len(pats) > 1 or matchmod.patkind(pats[0]):
708 raise util.Abort(_('with multiple sources, destination must be an '
715 raise util.Abort(_('with multiple sources, destination must be an '
709 'existing directory'))
716 'existing directory'))
710 if util.endswithsep(dest):
717 if util.endswithsep(dest):
711 raise util.Abort(_('destination %s is not a directory') % dest)
718 raise util.Abort(_('destination %s is not a directory') % dest)
712
719
713 tfn = targetpathfn
720 tfn = targetpathfn
714 if after:
721 if after:
715 tfn = targetpathafterfn
722 tfn = targetpathafterfn
716 copylist = []
723 copylist = []
717 for pat in pats:
724 for pat in pats:
718 srcs = walkpat(pat)
725 srcs = walkpat(pat)
719 if not srcs:
726 if not srcs:
720 continue
727 continue
721 copylist.append((tfn(pat, dest, srcs), srcs))
728 copylist.append((tfn(pat, dest, srcs), srcs))
722 if not copylist:
729 if not copylist:
723 raise util.Abort(_('no files to copy'))
730 raise util.Abort(_('no files to copy'))
724
731
725 errors = 0
732 errors = 0
726 for targetpath, srcs in copylist:
733 for targetpath, srcs in copylist:
727 for abssrc, relsrc, exact in srcs:
734 for abssrc, relsrc, exact in srcs:
728 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
735 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
729 errors += 1
736 errors += 1
730
737
731 if errors:
738 if errors:
732 ui.warn(_('(consider using --after)\n'))
739 ui.warn(_('(consider using --after)\n'))
733
740
734 return errors != 0
741 return errors != 0
735
742
736 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
743 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
737 runargs=None, appendpid=False):
744 runargs=None, appendpid=False):
738 '''Run a command as a service.'''
745 '''Run a command as a service.'''
739
746
740 def writepid(pid):
747 def writepid(pid):
741 if opts['pid_file']:
748 if opts['pid_file']:
742 if appendpid:
749 if appendpid:
743 mode = 'a'
750 mode = 'a'
744 else:
751 else:
745 mode = 'w'
752 mode = 'w'
746 fp = open(opts['pid_file'], mode)
753 fp = open(opts['pid_file'], mode)
747 fp.write(str(pid) + '\n')
754 fp.write(str(pid) + '\n')
748 fp.close()
755 fp.close()
749
756
750 if opts['daemon'] and not opts['daemon_pipefds']:
757 if opts['daemon'] and not opts['daemon_pipefds']:
751 # Signal child process startup with file removal
758 # Signal child process startup with file removal
752 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
759 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
753 os.close(lockfd)
760 os.close(lockfd)
754 try:
761 try:
755 if not runargs:
762 if not runargs:
756 runargs = util.hgcmd() + sys.argv[1:]
763 runargs = util.hgcmd() + sys.argv[1:]
757 runargs.append('--daemon-pipefds=%s' % lockpath)
764 runargs.append('--daemon-pipefds=%s' % lockpath)
758 # Don't pass --cwd to the child process, because we've already
765 # Don't pass --cwd to the child process, because we've already
759 # changed directory.
766 # changed directory.
760 for i in xrange(1, len(runargs)):
767 for i in xrange(1, len(runargs)):
761 if runargs[i].startswith('--cwd='):
768 if runargs[i].startswith('--cwd='):
762 del runargs[i]
769 del runargs[i]
763 break
770 break
764 elif runargs[i].startswith('--cwd'):
771 elif runargs[i].startswith('--cwd'):
765 del runargs[i:i + 2]
772 del runargs[i:i + 2]
766 break
773 break
767 def condfn():
774 def condfn():
768 return not os.path.exists(lockpath)
775 return not os.path.exists(lockpath)
769 pid = util.rundetached(runargs, condfn)
776 pid = util.rundetached(runargs, condfn)
770 if pid < 0:
777 if pid < 0:
771 raise util.Abort(_('child process failed to start'))
778 raise util.Abort(_('child process failed to start'))
772 writepid(pid)
779 writepid(pid)
773 finally:
780 finally:
774 try:
781 try:
775 os.unlink(lockpath)
782 os.unlink(lockpath)
776 except OSError as e:
783 except OSError as e:
777 if e.errno != errno.ENOENT:
784 if e.errno != errno.ENOENT:
778 raise
785 raise
779 if parentfn:
786 if parentfn:
780 return parentfn(pid)
787 return parentfn(pid)
781 else:
788 else:
782 return
789 return
783
790
784 if initfn:
791 if initfn:
785 initfn()
792 initfn()
786
793
787 if not opts['daemon']:
794 if not opts['daemon']:
788 writepid(os.getpid())
795 writepid(os.getpid())
789
796
790 if opts['daemon_pipefds']:
797 if opts['daemon_pipefds']:
791 lockpath = opts['daemon_pipefds']
798 lockpath = opts['daemon_pipefds']
792 try:
799 try:
793 os.setsid()
800 os.setsid()
794 except AttributeError:
801 except AttributeError:
795 pass
802 pass
796 os.unlink(lockpath)
803 os.unlink(lockpath)
797 util.hidewindow()
804 util.hidewindow()
798 sys.stdout.flush()
805 sys.stdout.flush()
799 sys.stderr.flush()
806 sys.stderr.flush()
800
807
801 nullfd = os.open(os.devnull, os.O_RDWR)
808 nullfd = os.open(os.devnull, os.O_RDWR)
802 logfilefd = nullfd
809 logfilefd = nullfd
803 if logfile:
810 if logfile:
804 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
811 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
805 os.dup2(nullfd, 0)
812 os.dup2(nullfd, 0)
806 os.dup2(logfilefd, 1)
813 os.dup2(logfilefd, 1)
807 os.dup2(logfilefd, 2)
814 os.dup2(logfilefd, 2)
808 if nullfd not in (0, 1, 2):
815 if nullfd not in (0, 1, 2):
809 os.close(nullfd)
816 os.close(nullfd)
810 if logfile and logfilefd not in (0, 1, 2):
817 if logfile and logfilefd not in (0, 1, 2):
811 os.close(logfilefd)
818 os.close(logfilefd)
812
819
813 if runfn:
820 if runfn:
814 return runfn()
821 return runfn()
815
822
816 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
823 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
817 """Utility function used by commands.import to import a single patch
824 """Utility function used by commands.import to import a single patch
818
825
819 This function is explicitly defined here to help the evolve extension to
826 This function is explicitly defined here to help the evolve extension to
820 wrap this part of the import logic.
827 wrap this part of the import logic.
821
828
822 The API is currently a bit ugly because it a simple code translation from
829 The API is currently a bit ugly because it a simple code translation from
823 the import command. Feel free to make it better.
830 the import command. Feel free to make it better.
824
831
825 :hunk: a patch (as a binary string)
832 :hunk: a patch (as a binary string)
826 :parents: nodes that will be parent of the created commit
833 :parents: nodes that will be parent of the created commit
827 :opts: the full dict of option passed to the import command
834 :opts: the full dict of option passed to the import command
828 :msgs: list to save commit message to.
835 :msgs: list to save commit message to.
829 (used in case we need to save it when failing)
836 (used in case we need to save it when failing)
830 :updatefunc: a function that update a repo to a given node
837 :updatefunc: a function that update a repo to a given node
831 updatefunc(<repo>, <node>)
838 updatefunc(<repo>, <node>)
832 """
839 """
833 tmpname, message, user, date, branch, nodeid, p1, p2 = \
840 tmpname, message, user, date, branch, nodeid, p1, p2 = \
834 patch.extract(ui, hunk)
841 patch.extract(ui, hunk)
835
842
836 update = not opts.get('bypass')
843 update = not opts.get('bypass')
837 strip = opts["strip"]
844 strip = opts["strip"]
838 prefix = opts["prefix"]
845 prefix = opts["prefix"]
839 sim = float(opts.get('similarity') or 0)
846 sim = float(opts.get('similarity') or 0)
840 if not tmpname:
847 if not tmpname:
841 return (None, None, False)
848 return (None, None, False)
842 msg = _('applied to working directory')
849 msg = _('applied to working directory')
843
850
844 rejects = False
851 rejects = False
845 dsguard = None
852 dsguard = None
846
853
847 try:
854 try:
848 cmdline_message = logmessage(ui, opts)
855 cmdline_message = logmessage(ui, opts)
849 if cmdline_message:
856 if cmdline_message:
850 # pickup the cmdline msg
857 # pickup the cmdline msg
851 message = cmdline_message
858 message = cmdline_message
852 elif message:
859 elif message:
853 # pickup the patch msg
860 # pickup the patch msg
854 message = message.strip()
861 message = message.strip()
855 else:
862 else:
856 # launch the editor
863 # launch the editor
857 message = None
864 message = None
858 ui.debug('message:\n%s\n' % message)
865 ui.debug('message:\n%s\n' % message)
859
866
860 if len(parents) == 1:
867 if len(parents) == 1:
861 parents.append(repo[nullid])
868 parents.append(repo[nullid])
862 if opts.get('exact'):
869 if opts.get('exact'):
863 if not nodeid or not p1:
870 if not nodeid or not p1:
864 raise util.Abort(_('not a Mercurial patch'))
871 raise util.Abort(_('not a Mercurial patch'))
865 p1 = repo[p1]
872 p1 = repo[p1]
866 p2 = repo[p2 or nullid]
873 p2 = repo[p2 or nullid]
867 elif p2:
874 elif p2:
868 try:
875 try:
869 p1 = repo[p1]
876 p1 = repo[p1]
870 p2 = repo[p2]
877 p2 = repo[p2]
871 # Without any options, consider p2 only if the
878 # Without any options, consider p2 only if the
872 # patch is being applied on top of the recorded
879 # patch is being applied on top of the recorded
873 # first parent.
880 # first parent.
874 if p1 != parents[0]:
881 if p1 != parents[0]:
875 p1 = parents[0]
882 p1 = parents[0]
876 p2 = repo[nullid]
883 p2 = repo[nullid]
877 except error.RepoError:
884 except error.RepoError:
878 p1, p2 = parents
885 p1, p2 = parents
879 if p2.node() == nullid:
886 if p2.node() == nullid:
880 ui.warn(_("warning: import the patch as a normal revision\n"
887 ui.warn(_("warning: import the patch as a normal revision\n"
881 "(use --exact to import the patch as a merge)\n"))
888 "(use --exact to import the patch as a merge)\n"))
882 else:
889 else:
883 p1, p2 = parents
890 p1, p2 = parents
884
891
885 n = None
892 n = None
886 if update:
893 if update:
887 dsguard = dirstateguard(repo, 'tryimportone')
894 dsguard = dirstateguard(repo, 'tryimportone')
888 if p1 != parents[0]:
895 if p1 != parents[0]:
889 updatefunc(repo, p1.node())
896 updatefunc(repo, p1.node())
890 if p2 != parents[1]:
897 if p2 != parents[1]:
891 repo.setparents(p1.node(), p2.node())
898 repo.setparents(p1.node(), p2.node())
892
899
893 if opts.get('exact') or opts.get('import_branch'):
900 if opts.get('exact') or opts.get('import_branch'):
894 repo.dirstate.setbranch(branch or 'default')
901 repo.dirstate.setbranch(branch or 'default')
895
902
896 partial = opts.get('partial', False)
903 partial = opts.get('partial', False)
897 files = set()
904 files = set()
898 try:
905 try:
899 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
906 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
900 files=files, eolmode=None, similarity=sim / 100.0)
907 files=files, eolmode=None, similarity=sim / 100.0)
901 except patch.PatchError as e:
908 except patch.PatchError as e:
902 if not partial:
909 if not partial:
903 raise util.Abort(str(e))
910 raise util.Abort(str(e))
904 if partial:
911 if partial:
905 rejects = True
912 rejects = True
906
913
907 files = list(files)
914 files = list(files)
908 if opts.get('no_commit'):
915 if opts.get('no_commit'):
909 if message:
916 if message:
910 msgs.append(message)
917 msgs.append(message)
911 else:
918 else:
912 if opts.get('exact') or p2:
919 if opts.get('exact') or p2:
913 # If you got here, you either use --force and know what
920 # If you got here, you either use --force and know what
914 # you are doing or used --exact or a merge patch while
921 # you are doing or used --exact or a merge patch while
915 # being updated to its first parent.
922 # being updated to its first parent.
916 m = None
923 m = None
917 else:
924 else:
918 m = scmutil.matchfiles(repo, files or [])
925 m = scmutil.matchfiles(repo, files or [])
919 editform = mergeeditform(repo[None], 'import.normal')
926 editform = mergeeditform(repo[None], 'import.normal')
920 if opts.get('exact'):
927 if opts.get('exact'):
921 editor = None
928 editor = None
922 else:
929 else:
923 editor = getcommiteditor(editform=editform, **opts)
930 editor = getcommiteditor(editform=editform, **opts)
924 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
931 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
925 try:
932 try:
926 if partial:
933 if partial:
927 repo.ui.setconfig('ui', 'allowemptycommit', True)
934 repo.ui.setconfig('ui', 'allowemptycommit', True)
928 n = repo.commit(message, opts.get('user') or user,
935 n = repo.commit(message, opts.get('user') or user,
929 opts.get('date') or date, match=m,
936 opts.get('date') or date, match=m,
930 editor=editor)
937 editor=editor)
931 finally:
938 finally:
932 repo.ui.restoreconfig(allowemptyback)
939 repo.ui.restoreconfig(allowemptyback)
933 dsguard.close()
940 dsguard.close()
934 else:
941 else:
935 if opts.get('exact') or opts.get('import_branch'):
942 if opts.get('exact') or opts.get('import_branch'):
936 branch = branch or 'default'
943 branch = branch or 'default'
937 else:
944 else:
938 branch = p1.branch()
945 branch = p1.branch()
939 store = patch.filestore()
946 store = patch.filestore()
940 try:
947 try:
941 files = set()
948 files = set()
942 try:
949 try:
943 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
950 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
944 files, eolmode=None)
951 files, eolmode=None)
945 except patch.PatchError as e:
952 except patch.PatchError as e:
946 raise util.Abort(str(e))
953 raise util.Abort(str(e))
947 if opts.get('exact'):
954 if opts.get('exact'):
948 editor = None
955 editor = None
949 else:
956 else:
950 editor = getcommiteditor(editform='import.bypass')
957 editor = getcommiteditor(editform='import.bypass')
951 memctx = context.makememctx(repo, (p1.node(), p2.node()),
958 memctx = context.makememctx(repo, (p1.node(), p2.node()),
952 message,
959 message,
953 opts.get('user') or user,
960 opts.get('user') or user,
954 opts.get('date') or date,
961 opts.get('date') or date,
955 branch, files, store,
962 branch, files, store,
956 editor=editor)
963 editor=editor)
957 n = memctx.commit()
964 n = memctx.commit()
958 finally:
965 finally:
959 store.close()
966 store.close()
960 if opts.get('exact') and opts.get('no_commit'):
967 if opts.get('exact') and opts.get('no_commit'):
961 # --exact with --no-commit is still useful in that it does merge
968 # --exact with --no-commit is still useful in that it does merge
962 # and branch bits
969 # and branch bits
963 ui.warn(_("warning: can't check exact import with --no-commit\n"))
970 ui.warn(_("warning: can't check exact import with --no-commit\n"))
964 elif opts.get('exact') and hex(n) != nodeid:
971 elif opts.get('exact') and hex(n) != nodeid:
965 raise util.Abort(_('patch is damaged or loses information'))
972 raise util.Abort(_('patch is damaged or loses information'))
966 if n:
973 if n:
967 # i18n: refers to a short changeset id
974 # i18n: refers to a short changeset id
968 msg = _('created %s') % short(n)
975 msg = _('created %s') % short(n)
969 return (msg, n, rejects)
976 return (msg, n, rejects)
970 finally:
977 finally:
971 lockmod.release(dsguard)
978 lockmod.release(dsguard)
972 os.unlink(tmpname)
979 os.unlink(tmpname)
973
980
974 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
981 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
975 opts=None):
982 opts=None):
976 '''export changesets as hg patches.'''
983 '''export changesets as hg patches.'''
977
984
978 total = len(revs)
985 total = len(revs)
979 revwidth = max([len(str(rev)) for rev in revs])
986 revwidth = max([len(str(rev)) for rev in revs])
980 filemode = {}
987 filemode = {}
981
988
982 def single(rev, seqno, fp):
989 def single(rev, seqno, fp):
983 ctx = repo[rev]
990 ctx = repo[rev]
984 node = ctx.node()
991 node = ctx.node()
985 parents = [p.node() for p in ctx.parents() if p]
992 parents = [p.node() for p in ctx.parents() if p]
986 branch = ctx.branch()
993 branch = ctx.branch()
987 if switch_parent:
994 if switch_parent:
988 parents.reverse()
995 parents.reverse()
989
996
990 if parents:
997 if parents:
991 prev = parents[0]
998 prev = parents[0]
992 else:
999 else:
993 prev = nullid
1000 prev = nullid
994
1001
995 shouldclose = False
1002 shouldclose = False
996 if not fp and len(template) > 0:
1003 if not fp and len(template) > 0:
997 desc_lines = ctx.description().rstrip().split('\n')
1004 desc_lines = ctx.description().rstrip().split('\n')
998 desc = desc_lines[0] #Commit always has a first line.
1005 desc = desc_lines[0] #Commit always has a first line.
999 fp = makefileobj(repo, template, node, desc=desc, total=total,
1006 fp = makefileobj(repo, template, node, desc=desc, total=total,
1000 seqno=seqno, revwidth=revwidth, mode='wb',
1007 seqno=seqno, revwidth=revwidth, mode='wb',
1001 modemap=filemode)
1008 modemap=filemode)
1002 if fp != template:
1009 if fp != template:
1003 shouldclose = True
1010 shouldclose = True
1004 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1011 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1005 repo.ui.note("%s\n" % fp.name)
1012 repo.ui.note("%s\n" % fp.name)
1006
1013
1007 if not fp:
1014 if not fp:
1008 write = repo.ui.write
1015 write = repo.ui.write
1009 else:
1016 else:
1010 def write(s, **kw):
1017 def write(s, **kw):
1011 fp.write(s)
1018 fp.write(s)
1012
1019
1013 write("# HG changeset patch\n")
1020 write("# HG changeset patch\n")
1014 write("# User %s\n" % ctx.user())
1021 write("# User %s\n" % ctx.user())
1015 write("# Date %d %d\n" % ctx.date())
1022 write("# Date %d %d\n" % ctx.date())
1016 write("# %s\n" % util.datestr(ctx.date()))
1023 write("# %s\n" % util.datestr(ctx.date()))
1017 if branch and branch != 'default':
1024 if branch and branch != 'default':
1018 write("# Branch %s\n" % branch)
1025 write("# Branch %s\n" % branch)
1019 write("# Node ID %s\n" % hex(node))
1026 write("# Node ID %s\n" % hex(node))
1020 write("# Parent %s\n" % hex(prev))
1027 write("# Parent %s\n" % hex(prev))
1021 if len(parents) > 1:
1028 if len(parents) > 1:
1022 write("# Parent %s\n" % hex(parents[1]))
1029 write("# Parent %s\n" % hex(parents[1]))
1023 write(ctx.description().rstrip())
1030 write(ctx.description().rstrip())
1024 write("\n\n")
1031 write("\n\n")
1025
1032
1026 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
1033 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
1027 write(chunk, label=label)
1034 write(chunk, label=label)
1028
1035
1029 if shouldclose:
1036 if shouldclose:
1030 fp.close()
1037 fp.close()
1031
1038
1032 for seqno, rev in enumerate(revs):
1039 for seqno, rev in enumerate(revs):
1033 single(rev, seqno + 1, fp)
1040 single(rev, seqno + 1, fp)
1034
1041
1035 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1042 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1036 changes=None, stat=False, fp=None, prefix='',
1043 changes=None, stat=False, fp=None, prefix='',
1037 root='', listsubrepos=False):
1044 root='', listsubrepos=False):
1038 '''show diff or diffstat.'''
1045 '''show diff or diffstat.'''
1039 if fp is None:
1046 if fp is None:
1040 write = ui.write
1047 write = ui.write
1041 else:
1048 else:
1042 def write(s, **kw):
1049 def write(s, **kw):
1043 fp.write(s)
1050 fp.write(s)
1044
1051
1045 if root:
1052 if root:
1046 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1053 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1047 else:
1054 else:
1048 relroot = ''
1055 relroot = ''
1049 if relroot != '':
1056 if relroot != '':
1050 # XXX relative roots currently don't work if the root is within a
1057 # XXX relative roots currently don't work if the root is within a
1051 # subrepo
1058 # subrepo
1052 uirelroot = match.uipath(relroot)
1059 uirelroot = match.uipath(relroot)
1053 relroot += '/'
1060 relroot += '/'
1054 for matchroot in match.files():
1061 for matchroot in match.files():
1055 if not matchroot.startswith(relroot):
1062 if not matchroot.startswith(relroot):
1056 ui.warn(_('warning: %s not inside relative root %s\n') % (
1063 ui.warn(_('warning: %s not inside relative root %s\n') % (
1057 match.uipath(matchroot), uirelroot))
1064 match.uipath(matchroot), uirelroot))
1058
1065
1059 if stat:
1066 if stat:
1060 diffopts = diffopts.copy(context=0)
1067 diffopts = diffopts.copy(context=0)
1061 width = 80
1068 width = 80
1062 if not ui.plain():
1069 if not ui.plain():
1063 width = ui.termwidth()
1070 width = ui.termwidth()
1064 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1071 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1065 prefix=prefix, relroot=relroot)
1072 prefix=prefix, relroot=relroot)
1066 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1073 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1067 width=width,
1074 width=width,
1068 git=diffopts.git):
1075 git=diffopts.git):
1069 write(chunk, label=label)
1076 write(chunk, label=label)
1070 else:
1077 else:
1071 for chunk, label in patch.diffui(repo, node1, node2, match,
1078 for chunk, label in patch.diffui(repo, node1, node2, match,
1072 changes, diffopts, prefix=prefix,
1079 changes, diffopts, prefix=prefix,
1073 relroot=relroot):
1080 relroot=relroot):
1074 write(chunk, label=label)
1081 write(chunk, label=label)
1075
1082
1076 if listsubrepos:
1083 if listsubrepos:
1077 ctx1 = repo[node1]
1084 ctx1 = repo[node1]
1078 ctx2 = repo[node2]
1085 ctx2 = repo[node2]
1079 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1086 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1080 tempnode2 = node2
1087 tempnode2 = node2
1081 try:
1088 try:
1082 if node2 is not None:
1089 if node2 is not None:
1083 tempnode2 = ctx2.substate[subpath][1]
1090 tempnode2 = ctx2.substate[subpath][1]
1084 except KeyError:
1091 except KeyError:
1085 # A subrepo that existed in node1 was deleted between node1 and
1092 # A subrepo that existed in node1 was deleted between node1 and
1086 # node2 (inclusive). Thus, ctx2's substate won't contain that
1093 # node2 (inclusive). Thus, ctx2's substate won't contain that
1087 # subpath. The best we can do is to ignore it.
1094 # subpath. The best we can do is to ignore it.
1088 tempnode2 = None
1095 tempnode2 = None
1089 submatch = matchmod.narrowmatcher(subpath, match)
1096 submatch = matchmod.narrowmatcher(subpath, match)
1090 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1097 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1091 stat=stat, fp=fp, prefix=prefix)
1098 stat=stat, fp=fp, prefix=prefix)
1092
1099
1093 class changeset_printer(object):
1100 class changeset_printer(object):
1094 '''show changeset information when templating not requested.'''
1101 '''show changeset information when templating not requested.'''
1095
1102
1096 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1103 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1097 self.ui = ui
1104 self.ui = ui
1098 self.repo = repo
1105 self.repo = repo
1099 self.buffered = buffered
1106 self.buffered = buffered
1100 self.matchfn = matchfn
1107 self.matchfn = matchfn
1101 self.diffopts = diffopts
1108 self.diffopts = diffopts
1102 self.header = {}
1109 self.header = {}
1103 self.hunk = {}
1110 self.hunk = {}
1104 self.lastheader = None
1111 self.lastheader = None
1105 self.footer = None
1112 self.footer = None
1106
1113
1107 def flush(self, rev):
1114 def flush(self, rev):
1108 if rev in self.header:
1115 if rev in self.header:
1109 h = self.header[rev]
1116 h = self.header[rev]
1110 if h != self.lastheader:
1117 if h != self.lastheader:
1111 self.lastheader = h
1118 self.lastheader = h
1112 self.ui.write(h)
1119 self.ui.write(h)
1113 del self.header[rev]
1120 del self.header[rev]
1114 if rev in self.hunk:
1121 if rev in self.hunk:
1115 self.ui.write(self.hunk[rev])
1122 self.ui.write(self.hunk[rev])
1116 del self.hunk[rev]
1123 del self.hunk[rev]
1117 return 1
1124 return 1
1118 return 0
1125 return 0
1119
1126
1120 def close(self):
1127 def close(self):
1121 if self.footer:
1128 if self.footer:
1122 self.ui.write(self.footer)
1129 self.ui.write(self.footer)
1123
1130
1124 def show(self, ctx, copies=None, matchfn=None, **props):
1131 def show(self, ctx, copies=None, matchfn=None, **props):
1125 if self.buffered:
1132 if self.buffered:
1126 self.ui.pushbuffer()
1133 self.ui.pushbuffer()
1127 self._show(ctx, copies, matchfn, props)
1134 self._show(ctx, copies, matchfn, props)
1128 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1135 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1129 else:
1136 else:
1130 self._show(ctx, copies, matchfn, props)
1137 self._show(ctx, copies, matchfn, props)
1131
1138
1132 def _show(self, ctx, copies, matchfn, props):
1139 def _show(self, ctx, copies, matchfn, props):
1133 '''show a single changeset or file revision'''
1140 '''show a single changeset or file revision'''
1134 changenode = ctx.node()
1141 changenode = ctx.node()
1135 rev = ctx.rev()
1142 rev = ctx.rev()
1136 if self.ui.debugflag:
1143 if self.ui.debugflag:
1137 hexfunc = hex
1144 hexfunc = hex
1138 else:
1145 else:
1139 hexfunc = short
1146 hexfunc = short
1140 if rev is None:
1147 if rev is None:
1141 pctx = ctx.p1()
1148 pctx = ctx.p1()
1142 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1149 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1143 else:
1150 else:
1144 revnode = (rev, hexfunc(changenode))
1151 revnode = (rev, hexfunc(changenode))
1145
1152
1146 if self.ui.quiet:
1153 if self.ui.quiet:
1147 self.ui.write("%d:%s\n" % revnode, label='log.node')
1154 self.ui.write("%d:%s\n" % revnode, label='log.node')
1148 return
1155 return
1149
1156
1150 date = util.datestr(ctx.date())
1157 date = util.datestr(ctx.date())
1151
1158
1152 # i18n: column positioning for "hg log"
1159 # i18n: column positioning for "hg log"
1153 self.ui.write(_("changeset: %d:%s\n") % revnode,
1160 self.ui.write(_("changeset: %d:%s\n") % revnode,
1154 label='log.changeset changeset.%s' % ctx.phasestr())
1161 label='log.changeset changeset.%s' % ctx.phasestr())
1155
1162
1156 # branches are shown first before any other names due to backwards
1163 # branches are shown first before any other names due to backwards
1157 # compatibility
1164 # compatibility
1158 branch = ctx.branch()
1165 branch = ctx.branch()
1159 # don't show the default branch name
1166 # don't show the default branch name
1160 if branch != 'default':
1167 if branch != 'default':
1161 # i18n: column positioning for "hg log"
1168 # i18n: column positioning for "hg log"
1162 self.ui.write(_("branch: %s\n") % branch,
1169 self.ui.write(_("branch: %s\n") % branch,
1163 label='log.branch')
1170 label='log.branch')
1164
1171
1165 for name, ns in self.repo.names.iteritems():
1172 for name, ns in self.repo.names.iteritems():
1166 # branches has special logic already handled above, so here we just
1173 # branches has special logic already handled above, so here we just
1167 # skip it
1174 # skip it
1168 if name == 'branches':
1175 if name == 'branches':
1169 continue
1176 continue
1170 # we will use the templatename as the color name since those two
1177 # we will use the templatename as the color name since those two
1171 # should be the same
1178 # should be the same
1172 for name in ns.names(self.repo, changenode):
1179 for name in ns.names(self.repo, changenode):
1173 self.ui.write(ns.logfmt % name,
1180 self.ui.write(ns.logfmt % name,
1174 label='log.%s' % ns.colorname)
1181 label='log.%s' % ns.colorname)
1175 if self.ui.debugflag:
1182 if self.ui.debugflag:
1176 # i18n: column positioning for "hg log"
1183 # i18n: column positioning for "hg log"
1177 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1184 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1178 label='log.phase')
1185 label='log.phase')
1179 for pctx in self._meaningful_parentrevs(ctx):
1186 for pctx in self._meaningful_parentrevs(ctx):
1180 label = 'log.parent changeset.%s' % pctx.phasestr()
1187 label = 'log.parent changeset.%s' % pctx.phasestr()
1181 # i18n: column positioning for "hg log"
1188 # i18n: column positioning for "hg log"
1182 self.ui.write(_("parent: %d:%s\n")
1189 self.ui.write(_("parent: %d:%s\n")
1183 % (pctx.rev(), hexfunc(pctx.node())),
1190 % (pctx.rev(), hexfunc(pctx.node())),
1184 label=label)
1191 label=label)
1185
1192
1186 if self.ui.debugflag and rev is not None:
1193 if self.ui.debugflag and rev is not None:
1187 mnode = ctx.manifestnode()
1194 mnode = ctx.manifestnode()
1188 # i18n: column positioning for "hg log"
1195 # i18n: column positioning for "hg log"
1189 self.ui.write(_("manifest: %d:%s\n") %
1196 self.ui.write(_("manifest: %d:%s\n") %
1190 (self.repo.manifest.rev(mnode), hex(mnode)),
1197 (self.repo.manifest.rev(mnode), hex(mnode)),
1191 label='ui.debug log.manifest')
1198 label='ui.debug log.manifest')
1192 # i18n: column positioning for "hg log"
1199 # i18n: column positioning for "hg log"
1193 self.ui.write(_("user: %s\n") % ctx.user(),
1200 self.ui.write(_("user: %s\n") % ctx.user(),
1194 label='log.user')
1201 label='log.user')
1195 # i18n: column positioning for "hg log"
1202 # i18n: column positioning for "hg log"
1196 self.ui.write(_("date: %s\n") % date,
1203 self.ui.write(_("date: %s\n") % date,
1197 label='log.date')
1204 label='log.date')
1198
1205
1199 if self.ui.debugflag:
1206 if self.ui.debugflag:
1200 files = ctx.p1().status(ctx)[:3]
1207 files = ctx.p1().status(ctx)[:3]
1201 for key, value in zip([# i18n: column positioning for "hg log"
1208 for key, value in zip([# i18n: column positioning for "hg log"
1202 _("files:"),
1209 _("files:"),
1203 # i18n: column positioning for "hg log"
1210 # i18n: column positioning for "hg log"
1204 _("files+:"),
1211 _("files+:"),
1205 # i18n: column positioning for "hg log"
1212 # i18n: column positioning for "hg log"
1206 _("files-:")], files):
1213 _("files-:")], files):
1207 if value:
1214 if value:
1208 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1215 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1209 label='ui.debug log.files')
1216 label='ui.debug log.files')
1210 elif ctx.files() and self.ui.verbose:
1217 elif ctx.files() and self.ui.verbose:
1211 # i18n: column positioning for "hg log"
1218 # i18n: column positioning for "hg log"
1212 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1219 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1213 label='ui.note log.files')
1220 label='ui.note log.files')
1214 if copies and self.ui.verbose:
1221 if copies and self.ui.verbose:
1215 copies = ['%s (%s)' % c for c in copies]
1222 copies = ['%s (%s)' % c for c in copies]
1216 # i18n: column positioning for "hg log"
1223 # i18n: column positioning for "hg log"
1217 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1224 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1218 label='ui.note log.copies')
1225 label='ui.note log.copies')
1219
1226
1220 extra = ctx.extra()
1227 extra = ctx.extra()
1221 if extra and self.ui.debugflag:
1228 if extra and self.ui.debugflag:
1222 for key, value in sorted(extra.items()):
1229 for key, value in sorted(extra.items()):
1223 # i18n: column positioning for "hg log"
1230 # i18n: column positioning for "hg log"
1224 self.ui.write(_("extra: %s=%s\n")
1231 self.ui.write(_("extra: %s=%s\n")
1225 % (key, value.encode('string_escape')),
1232 % (key, value.encode('string_escape')),
1226 label='ui.debug log.extra')
1233 label='ui.debug log.extra')
1227
1234
1228 description = ctx.description().strip()
1235 description = ctx.description().strip()
1229 if description:
1236 if description:
1230 if self.ui.verbose:
1237 if self.ui.verbose:
1231 self.ui.write(_("description:\n"),
1238 self.ui.write(_("description:\n"),
1232 label='ui.note log.description')
1239 label='ui.note log.description')
1233 self.ui.write(description,
1240 self.ui.write(description,
1234 label='ui.note log.description')
1241 label='ui.note log.description')
1235 self.ui.write("\n\n")
1242 self.ui.write("\n\n")
1236 else:
1243 else:
1237 # i18n: column positioning for "hg log"
1244 # i18n: column positioning for "hg log"
1238 self.ui.write(_("summary: %s\n") %
1245 self.ui.write(_("summary: %s\n") %
1239 description.splitlines()[0],
1246 description.splitlines()[0],
1240 label='log.summary')
1247 label='log.summary')
1241 self.ui.write("\n")
1248 self.ui.write("\n")
1242
1249
1243 self.showpatch(changenode, matchfn)
1250 self.showpatch(changenode, matchfn)
1244
1251
1245 def showpatch(self, node, matchfn):
1252 def showpatch(self, node, matchfn):
1246 if not matchfn:
1253 if not matchfn:
1247 matchfn = self.matchfn
1254 matchfn = self.matchfn
1248 if matchfn:
1255 if matchfn:
1249 stat = self.diffopts.get('stat')
1256 stat = self.diffopts.get('stat')
1250 diff = self.diffopts.get('patch')
1257 diff = self.diffopts.get('patch')
1251 diffopts = patch.diffallopts(self.ui, self.diffopts)
1258 diffopts = patch.diffallopts(self.ui, self.diffopts)
1252 prev = self.repo.changelog.parents(node)[0]
1259 prev = self.repo.changelog.parents(node)[0]
1253 if stat:
1260 if stat:
1254 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1261 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1255 match=matchfn, stat=True)
1262 match=matchfn, stat=True)
1256 if diff:
1263 if diff:
1257 if stat:
1264 if stat:
1258 self.ui.write("\n")
1265 self.ui.write("\n")
1259 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1266 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1260 match=matchfn, stat=False)
1267 match=matchfn, stat=False)
1261 self.ui.write("\n")
1268 self.ui.write("\n")
1262
1269
1263 def _meaningful_parentrevs(self, ctx):
1270 def _meaningful_parentrevs(self, ctx):
1264 """Return list of meaningful (or all if debug) parentrevs for rev.
1271 """Return list of meaningful (or all if debug) parentrevs for rev.
1265
1272
1266 For merges (two non-nullrev revisions) both parents are meaningful.
1273 For merges (two non-nullrev revisions) both parents are meaningful.
1267 Otherwise the first parent revision is considered meaningful if it
1274 Otherwise the first parent revision is considered meaningful if it
1268 is not the preceding revision.
1275 is not the preceding revision.
1269 """
1276 """
1270 parents = ctx.parents()
1277 parents = ctx.parents()
1271 if len(parents) > 1:
1278 if len(parents) > 1:
1272 return parents
1279 return parents
1273 if self.ui.debugflag:
1280 if self.ui.debugflag:
1274 return [parents[0], self.repo['null']]
1281 return [parents[0], self.repo['null']]
1275 if parents[0].rev() >= scmutil.intrev(ctx.rev()) - 1:
1282 if parents[0].rev() >= scmutil.intrev(ctx.rev()) - 1:
1276 return []
1283 return []
1277 return parents
1284 return parents
1278
1285
1279 class jsonchangeset(changeset_printer):
1286 class jsonchangeset(changeset_printer):
1280 '''format changeset information.'''
1287 '''format changeset information.'''
1281
1288
1282 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1289 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1283 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1290 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1284 self.cache = {}
1291 self.cache = {}
1285 self._first = True
1292 self._first = True
1286
1293
1287 def close(self):
1294 def close(self):
1288 if not self._first:
1295 if not self._first:
1289 self.ui.write("\n]\n")
1296 self.ui.write("\n]\n")
1290 else:
1297 else:
1291 self.ui.write("[]\n")
1298 self.ui.write("[]\n")
1292
1299
1293 def _show(self, ctx, copies, matchfn, props):
1300 def _show(self, ctx, copies, matchfn, props):
1294 '''show a single changeset or file revision'''
1301 '''show a single changeset or file revision'''
1295 rev = ctx.rev()
1302 rev = ctx.rev()
1296 if rev is None:
1303 if rev is None:
1297 jrev = jnode = 'null'
1304 jrev = jnode = 'null'
1298 else:
1305 else:
1299 jrev = str(rev)
1306 jrev = str(rev)
1300 jnode = '"%s"' % hex(ctx.node())
1307 jnode = '"%s"' % hex(ctx.node())
1301 j = encoding.jsonescape
1308 j = encoding.jsonescape
1302
1309
1303 if self._first:
1310 if self._first:
1304 self.ui.write("[\n {")
1311 self.ui.write("[\n {")
1305 self._first = False
1312 self._first = False
1306 else:
1313 else:
1307 self.ui.write(",\n {")
1314 self.ui.write(",\n {")
1308
1315
1309 if self.ui.quiet:
1316 if self.ui.quiet:
1310 self.ui.write('\n "rev": %s' % jrev)
1317 self.ui.write('\n "rev": %s' % jrev)
1311 self.ui.write(',\n "node": %s' % jnode)
1318 self.ui.write(',\n "node": %s' % jnode)
1312 self.ui.write('\n }')
1319 self.ui.write('\n }')
1313 return
1320 return
1314
1321
1315 self.ui.write('\n "rev": %s' % jrev)
1322 self.ui.write('\n "rev": %s' % jrev)
1316 self.ui.write(',\n "node": %s' % jnode)
1323 self.ui.write(',\n "node": %s' % jnode)
1317 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1324 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1318 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1325 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1319 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1326 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1320 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1327 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1321 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1328 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1322
1329
1323 self.ui.write(',\n "bookmarks": [%s]' %
1330 self.ui.write(',\n "bookmarks": [%s]' %
1324 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1331 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1325 self.ui.write(',\n "tags": [%s]' %
1332 self.ui.write(',\n "tags": [%s]' %
1326 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1333 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1327 self.ui.write(',\n "parents": [%s]' %
1334 self.ui.write(',\n "parents": [%s]' %
1328 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1335 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1329
1336
1330 if self.ui.debugflag:
1337 if self.ui.debugflag:
1331 if rev is None:
1338 if rev is None:
1332 jmanifestnode = 'null'
1339 jmanifestnode = 'null'
1333 else:
1340 else:
1334 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1341 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1335 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1342 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1336
1343
1337 self.ui.write(',\n "extra": {%s}' %
1344 self.ui.write(',\n "extra": {%s}' %
1338 ", ".join('"%s": "%s"' % (j(k), j(v))
1345 ", ".join('"%s": "%s"' % (j(k), j(v))
1339 for k, v in ctx.extra().items()))
1346 for k, v in ctx.extra().items()))
1340
1347
1341 files = ctx.p1().status(ctx)
1348 files = ctx.p1().status(ctx)
1342 self.ui.write(',\n "modified": [%s]' %
1349 self.ui.write(',\n "modified": [%s]' %
1343 ", ".join('"%s"' % j(f) for f in files[0]))
1350 ", ".join('"%s"' % j(f) for f in files[0]))
1344 self.ui.write(',\n "added": [%s]' %
1351 self.ui.write(',\n "added": [%s]' %
1345 ", ".join('"%s"' % j(f) for f in files[1]))
1352 ", ".join('"%s"' % j(f) for f in files[1]))
1346 self.ui.write(',\n "removed": [%s]' %
1353 self.ui.write(',\n "removed": [%s]' %
1347 ", ".join('"%s"' % j(f) for f in files[2]))
1354 ", ".join('"%s"' % j(f) for f in files[2]))
1348
1355
1349 elif self.ui.verbose:
1356 elif self.ui.verbose:
1350 self.ui.write(',\n "files": [%s]' %
1357 self.ui.write(',\n "files": [%s]' %
1351 ", ".join('"%s"' % j(f) for f in ctx.files()))
1358 ", ".join('"%s"' % j(f) for f in ctx.files()))
1352
1359
1353 if copies:
1360 if copies:
1354 self.ui.write(',\n "copies": {%s}' %
1361 self.ui.write(',\n "copies": {%s}' %
1355 ", ".join('"%s": "%s"' % (j(k), j(v))
1362 ", ".join('"%s": "%s"' % (j(k), j(v))
1356 for k, v in copies))
1363 for k, v in copies))
1357
1364
1358 matchfn = self.matchfn
1365 matchfn = self.matchfn
1359 if matchfn:
1366 if matchfn:
1360 stat = self.diffopts.get('stat')
1367 stat = self.diffopts.get('stat')
1361 diff = self.diffopts.get('patch')
1368 diff = self.diffopts.get('patch')
1362 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1369 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1363 node, prev = ctx.node(), ctx.p1().node()
1370 node, prev = ctx.node(), ctx.p1().node()
1364 if stat:
1371 if stat:
1365 self.ui.pushbuffer()
1372 self.ui.pushbuffer()
1366 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1373 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1367 match=matchfn, stat=True)
1374 match=matchfn, stat=True)
1368 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1375 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1369 if diff:
1376 if diff:
1370 self.ui.pushbuffer()
1377 self.ui.pushbuffer()
1371 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1378 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1372 match=matchfn, stat=False)
1379 match=matchfn, stat=False)
1373 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1380 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1374
1381
1375 self.ui.write("\n }")
1382 self.ui.write("\n }")
1376
1383
1377 class changeset_templater(changeset_printer):
1384 class changeset_templater(changeset_printer):
1378 '''format changeset information.'''
1385 '''format changeset information.'''
1379
1386
1380 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1387 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1381 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1388 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1382 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1389 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1383 defaulttempl = {
1390 defaulttempl = {
1384 'parent': '{rev}:{node|formatnode} ',
1391 'parent': '{rev}:{node|formatnode} ',
1385 'manifest': '{rev}:{node|formatnode}',
1392 'manifest': '{rev}:{node|formatnode}',
1386 'file_copy': '{name} ({source})',
1393 'file_copy': '{name} ({source})',
1387 'extra': '{key}={value|stringescape}'
1394 'extra': '{key}={value|stringescape}'
1388 }
1395 }
1389 # filecopy is preserved for compatibility reasons
1396 # filecopy is preserved for compatibility reasons
1390 defaulttempl['filecopy'] = defaulttempl['file_copy']
1397 defaulttempl['filecopy'] = defaulttempl['file_copy']
1391 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1398 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1392 cache=defaulttempl)
1399 cache=defaulttempl)
1393 if tmpl:
1400 if tmpl:
1394 self.t.cache['changeset'] = tmpl
1401 self.t.cache['changeset'] = tmpl
1395
1402
1396 self.cache = {}
1403 self.cache = {}
1397
1404
1398 def _show(self, ctx, copies, matchfn, props):
1405 def _show(self, ctx, copies, matchfn, props):
1399 '''show a single changeset or file revision'''
1406 '''show a single changeset or file revision'''
1400
1407
1401 showlist = templatekw.showlist
1408 showlist = templatekw.showlist
1402
1409
1403 # showparents() behaviour depends on ui trace level which
1410 # showparents() behaviour depends on ui trace level which
1404 # causes unexpected behaviours at templating level and makes
1411 # causes unexpected behaviours at templating level and makes
1405 # it harder to extract it in a standalone function. Its
1412 # it harder to extract it in a standalone function. Its
1406 # behaviour cannot be changed so leave it here for now.
1413 # behaviour cannot be changed so leave it here for now.
1407 def showparents(**args):
1414 def showparents(**args):
1408 ctx = args['ctx']
1415 ctx = args['ctx']
1409 parents = [[('rev', p.rev()),
1416 parents = [[('rev', p.rev()),
1410 ('node', p.hex()),
1417 ('node', p.hex()),
1411 ('phase', p.phasestr())]
1418 ('phase', p.phasestr())]
1412 for p in self._meaningful_parentrevs(ctx)]
1419 for p in self._meaningful_parentrevs(ctx)]
1413 return showlist('parent', parents, **args)
1420 return showlist('parent', parents, **args)
1414
1421
1415 props = props.copy()
1422 props = props.copy()
1416 props.update(templatekw.keywords)
1423 props.update(templatekw.keywords)
1417 props['parents'] = showparents
1424 props['parents'] = showparents
1418 props['templ'] = self.t
1425 props['templ'] = self.t
1419 props['ctx'] = ctx
1426 props['ctx'] = ctx
1420 props['repo'] = self.repo
1427 props['repo'] = self.repo
1421 props['revcache'] = {'copies': copies}
1428 props['revcache'] = {'copies': copies}
1422 props['cache'] = self.cache
1429 props['cache'] = self.cache
1423
1430
1424 # find correct templates for current mode
1431 # find correct templates for current mode
1425
1432
1426 tmplmodes = [
1433 tmplmodes = [
1427 (True, None),
1434 (True, None),
1428 (self.ui.verbose, 'verbose'),
1435 (self.ui.verbose, 'verbose'),
1429 (self.ui.quiet, 'quiet'),
1436 (self.ui.quiet, 'quiet'),
1430 (self.ui.debugflag, 'debug'),
1437 (self.ui.debugflag, 'debug'),
1431 ]
1438 ]
1432
1439
1433 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1440 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1434 for mode, postfix in tmplmodes:
1441 for mode, postfix in tmplmodes:
1435 for type in types:
1442 for type in types:
1436 cur = postfix and ('%s_%s' % (type, postfix)) or type
1443 cur = postfix and ('%s_%s' % (type, postfix)) or type
1437 if mode and cur in self.t:
1444 if mode and cur in self.t:
1438 types[type] = cur
1445 types[type] = cur
1439
1446
1440 try:
1447 try:
1441
1448
1442 # write header
1449 # write header
1443 if types['header']:
1450 if types['header']:
1444 h = templater.stringify(self.t(types['header'], **props))
1451 h = templater.stringify(self.t(types['header'], **props))
1445 if self.buffered:
1452 if self.buffered:
1446 self.header[ctx.rev()] = h
1453 self.header[ctx.rev()] = h
1447 else:
1454 else:
1448 if self.lastheader != h:
1455 if self.lastheader != h:
1449 self.lastheader = h
1456 self.lastheader = h
1450 self.ui.write(h)
1457 self.ui.write(h)
1451
1458
1452 # write changeset metadata, then patch if requested
1459 # write changeset metadata, then patch if requested
1453 key = types['changeset']
1460 key = types['changeset']
1454 self.ui.write(templater.stringify(self.t(key, **props)))
1461 self.ui.write(templater.stringify(self.t(key, **props)))
1455 self.showpatch(ctx.node(), matchfn)
1462 self.showpatch(ctx.node(), matchfn)
1456
1463
1457 if types['footer']:
1464 if types['footer']:
1458 if not self.footer:
1465 if not self.footer:
1459 self.footer = templater.stringify(self.t(types['footer'],
1466 self.footer = templater.stringify(self.t(types['footer'],
1460 **props))
1467 **props))
1461
1468
1462 except KeyError as inst:
1469 except KeyError as inst:
1463 msg = _("%s: no key named '%s'")
1470 msg = _("%s: no key named '%s'")
1464 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1471 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1465 except SyntaxError as inst:
1472 except SyntaxError as inst:
1466 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1473 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1467
1474
1468 def gettemplate(ui, tmpl, style):
1475 def gettemplate(ui, tmpl, style):
1469 """
1476 """
1470 Find the template matching the given template spec or style.
1477 Find the template matching the given template spec or style.
1471 """
1478 """
1472
1479
1473 # ui settings
1480 # ui settings
1474 if not tmpl and not style: # template are stronger than style
1481 if not tmpl and not style: # template are stronger than style
1475 tmpl = ui.config('ui', 'logtemplate')
1482 tmpl = ui.config('ui', 'logtemplate')
1476 if tmpl:
1483 if tmpl:
1477 try:
1484 try:
1478 tmpl = templater.unquotestring(tmpl)
1485 tmpl = templater.unquotestring(tmpl)
1479 except SyntaxError:
1486 except SyntaxError:
1480 pass
1487 pass
1481 return tmpl, None
1488 return tmpl, None
1482 else:
1489 else:
1483 style = util.expandpath(ui.config('ui', 'style', ''))
1490 style = util.expandpath(ui.config('ui', 'style', ''))
1484
1491
1485 if not tmpl and style:
1492 if not tmpl and style:
1486 mapfile = style
1493 mapfile = style
1487 if not os.path.split(mapfile)[0]:
1494 if not os.path.split(mapfile)[0]:
1488 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1495 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1489 or templater.templatepath(mapfile))
1496 or templater.templatepath(mapfile))
1490 if mapname:
1497 if mapname:
1491 mapfile = mapname
1498 mapfile = mapname
1492 return None, mapfile
1499 return None, mapfile
1493
1500
1494 if not tmpl:
1501 if not tmpl:
1495 return None, None
1502 return None, None
1496
1503
1497 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1504 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1498
1505
1499 def show_changeset(ui, repo, opts, buffered=False):
1506 def show_changeset(ui, repo, opts, buffered=False):
1500 """show one changeset using template or regular display.
1507 """show one changeset using template or regular display.
1501
1508
1502 Display format will be the first non-empty hit of:
1509 Display format will be the first non-empty hit of:
1503 1. option 'template'
1510 1. option 'template'
1504 2. option 'style'
1511 2. option 'style'
1505 3. [ui] setting 'logtemplate'
1512 3. [ui] setting 'logtemplate'
1506 4. [ui] setting 'style'
1513 4. [ui] setting 'style'
1507 If all of these values are either the unset or the empty string,
1514 If all of these values are either the unset or the empty string,
1508 regular display via changeset_printer() is done.
1515 regular display via changeset_printer() is done.
1509 """
1516 """
1510 # options
1517 # options
1511 matchfn = None
1518 matchfn = None
1512 if opts.get('patch') or opts.get('stat'):
1519 if opts.get('patch') or opts.get('stat'):
1513 matchfn = scmutil.matchall(repo)
1520 matchfn = scmutil.matchall(repo)
1514
1521
1515 if opts.get('template') == 'json':
1522 if opts.get('template') == 'json':
1516 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1523 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1517
1524
1518 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1525 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1519
1526
1520 if not tmpl and not mapfile:
1527 if not tmpl and not mapfile:
1521 return changeset_printer(ui, repo, matchfn, opts, buffered)
1528 return changeset_printer(ui, repo, matchfn, opts, buffered)
1522
1529
1523 try:
1530 try:
1524 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1531 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1525 buffered)
1532 buffered)
1526 except SyntaxError as inst:
1533 except SyntaxError as inst:
1527 raise util.Abort(inst.args[0])
1534 raise util.Abort(inst.args[0])
1528 return t
1535 return t
1529
1536
1530 def showmarker(ui, marker):
1537 def showmarker(ui, marker):
1531 """utility function to display obsolescence marker in a readable way
1538 """utility function to display obsolescence marker in a readable way
1532
1539
1533 To be used by debug function."""
1540 To be used by debug function."""
1534 ui.write(hex(marker.precnode()))
1541 ui.write(hex(marker.precnode()))
1535 for repl in marker.succnodes():
1542 for repl in marker.succnodes():
1536 ui.write(' ')
1543 ui.write(' ')
1537 ui.write(hex(repl))
1544 ui.write(hex(repl))
1538 ui.write(' %X ' % marker.flags())
1545 ui.write(' %X ' % marker.flags())
1539 parents = marker.parentnodes()
1546 parents = marker.parentnodes()
1540 if parents is not None:
1547 if parents is not None:
1541 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1548 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1542 ui.write('(%s) ' % util.datestr(marker.date()))
1549 ui.write('(%s) ' % util.datestr(marker.date()))
1543 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1550 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1544 sorted(marker.metadata().items())
1551 sorted(marker.metadata().items())
1545 if t[0] != 'date')))
1552 if t[0] != 'date')))
1546 ui.write('\n')
1553 ui.write('\n')
1547
1554
1548 def finddate(ui, repo, date):
1555 def finddate(ui, repo, date):
1549 """Find the tipmost changeset that matches the given date spec"""
1556 """Find the tipmost changeset that matches the given date spec"""
1550
1557
1551 df = util.matchdate(date)
1558 df = util.matchdate(date)
1552 m = scmutil.matchall(repo)
1559 m = scmutil.matchall(repo)
1553 results = {}
1560 results = {}
1554
1561
1555 def prep(ctx, fns):
1562 def prep(ctx, fns):
1556 d = ctx.date()
1563 d = ctx.date()
1557 if df(d[0]):
1564 if df(d[0]):
1558 results[ctx.rev()] = d
1565 results[ctx.rev()] = d
1559
1566
1560 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1567 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1561 rev = ctx.rev()
1568 rev = ctx.rev()
1562 if rev in results:
1569 if rev in results:
1563 ui.status(_("found revision %s from %s\n") %
1570 ui.status(_("found revision %s from %s\n") %
1564 (rev, util.datestr(results[rev])))
1571 (rev, util.datestr(results[rev])))
1565 return str(rev)
1572 return str(rev)
1566
1573
1567 raise util.Abort(_("revision matching date not found"))
1574 raise util.Abort(_("revision matching date not found"))
1568
1575
1569 def increasingwindows(windowsize=8, sizelimit=512):
1576 def increasingwindows(windowsize=8, sizelimit=512):
1570 while True:
1577 while True:
1571 yield windowsize
1578 yield windowsize
1572 if windowsize < sizelimit:
1579 if windowsize < sizelimit:
1573 windowsize *= 2
1580 windowsize *= 2
1574
1581
1575 class FileWalkError(Exception):
1582 class FileWalkError(Exception):
1576 pass
1583 pass
1577
1584
1578 def walkfilerevs(repo, match, follow, revs, fncache):
1585 def walkfilerevs(repo, match, follow, revs, fncache):
1579 '''Walks the file history for the matched files.
1586 '''Walks the file history for the matched files.
1580
1587
1581 Returns the changeset revs that are involved in the file history.
1588 Returns the changeset revs that are involved in the file history.
1582
1589
1583 Throws FileWalkError if the file history can't be walked using
1590 Throws FileWalkError if the file history can't be walked using
1584 filelogs alone.
1591 filelogs alone.
1585 '''
1592 '''
1586 wanted = set()
1593 wanted = set()
1587 copies = []
1594 copies = []
1588 minrev, maxrev = min(revs), max(revs)
1595 minrev, maxrev = min(revs), max(revs)
1589 def filerevgen(filelog, last):
1596 def filerevgen(filelog, last):
1590 """
1597 """
1591 Only files, no patterns. Check the history of each file.
1598 Only files, no patterns. Check the history of each file.
1592
1599
1593 Examines filelog entries within minrev, maxrev linkrev range
1600 Examines filelog entries within minrev, maxrev linkrev range
1594 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1601 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1595 tuples in backwards order
1602 tuples in backwards order
1596 """
1603 """
1597 cl_count = len(repo)
1604 cl_count = len(repo)
1598 revs = []
1605 revs = []
1599 for j in xrange(0, last + 1):
1606 for j in xrange(0, last + 1):
1600 linkrev = filelog.linkrev(j)
1607 linkrev = filelog.linkrev(j)
1601 if linkrev < minrev:
1608 if linkrev < minrev:
1602 continue
1609 continue
1603 # only yield rev for which we have the changelog, it can
1610 # only yield rev for which we have the changelog, it can
1604 # happen while doing "hg log" during a pull or commit
1611 # happen while doing "hg log" during a pull or commit
1605 if linkrev >= cl_count:
1612 if linkrev >= cl_count:
1606 break
1613 break
1607
1614
1608 parentlinkrevs = []
1615 parentlinkrevs = []
1609 for p in filelog.parentrevs(j):
1616 for p in filelog.parentrevs(j):
1610 if p != nullrev:
1617 if p != nullrev:
1611 parentlinkrevs.append(filelog.linkrev(p))
1618 parentlinkrevs.append(filelog.linkrev(p))
1612 n = filelog.node(j)
1619 n = filelog.node(j)
1613 revs.append((linkrev, parentlinkrevs,
1620 revs.append((linkrev, parentlinkrevs,
1614 follow and filelog.renamed(n)))
1621 follow and filelog.renamed(n)))
1615
1622
1616 return reversed(revs)
1623 return reversed(revs)
1617 def iterfiles():
1624 def iterfiles():
1618 pctx = repo['.']
1625 pctx = repo['.']
1619 for filename in match.files():
1626 for filename in match.files():
1620 if follow:
1627 if follow:
1621 if filename not in pctx:
1628 if filename not in pctx:
1622 raise util.Abort(_('cannot follow file not in parent '
1629 raise util.Abort(_('cannot follow file not in parent '
1623 'revision: "%s"') % filename)
1630 'revision: "%s"') % filename)
1624 yield filename, pctx[filename].filenode()
1631 yield filename, pctx[filename].filenode()
1625 else:
1632 else:
1626 yield filename, None
1633 yield filename, None
1627 for filename_node in copies:
1634 for filename_node in copies:
1628 yield filename_node
1635 yield filename_node
1629
1636
1630 for file_, node in iterfiles():
1637 for file_, node in iterfiles():
1631 filelog = repo.file(file_)
1638 filelog = repo.file(file_)
1632 if not len(filelog):
1639 if not len(filelog):
1633 if node is None:
1640 if node is None:
1634 # A zero count may be a directory or deleted file, so
1641 # A zero count may be a directory or deleted file, so
1635 # try to find matching entries on the slow path.
1642 # try to find matching entries on the slow path.
1636 if follow:
1643 if follow:
1637 raise util.Abort(
1644 raise util.Abort(
1638 _('cannot follow nonexistent file: "%s"') % file_)
1645 _('cannot follow nonexistent file: "%s"') % file_)
1639 raise FileWalkError("Cannot walk via filelog")
1646 raise FileWalkError("Cannot walk via filelog")
1640 else:
1647 else:
1641 continue
1648 continue
1642
1649
1643 if node is None:
1650 if node is None:
1644 last = len(filelog) - 1
1651 last = len(filelog) - 1
1645 else:
1652 else:
1646 last = filelog.rev(node)
1653 last = filelog.rev(node)
1647
1654
1648 # keep track of all ancestors of the file
1655 # keep track of all ancestors of the file
1649 ancestors = set([filelog.linkrev(last)])
1656 ancestors = set([filelog.linkrev(last)])
1650
1657
1651 # iterate from latest to oldest revision
1658 # iterate from latest to oldest revision
1652 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1659 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1653 if not follow:
1660 if not follow:
1654 if rev > maxrev:
1661 if rev > maxrev:
1655 continue
1662 continue
1656 else:
1663 else:
1657 # Note that last might not be the first interesting
1664 # Note that last might not be the first interesting
1658 # rev to us:
1665 # rev to us:
1659 # if the file has been changed after maxrev, we'll
1666 # if the file has been changed after maxrev, we'll
1660 # have linkrev(last) > maxrev, and we still need
1667 # have linkrev(last) > maxrev, and we still need
1661 # to explore the file graph
1668 # to explore the file graph
1662 if rev not in ancestors:
1669 if rev not in ancestors:
1663 continue
1670 continue
1664 # XXX insert 1327 fix here
1671 # XXX insert 1327 fix here
1665 if flparentlinkrevs:
1672 if flparentlinkrevs:
1666 ancestors.update(flparentlinkrevs)
1673 ancestors.update(flparentlinkrevs)
1667
1674
1668 fncache.setdefault(rev, []).append(file_)
1675 fncache.setdefault(rev, []).append(file_)
1669 wanted.add(rev)
1676 wanted.add(rev)
1670 if copied:
1677 if copied:
1671 copies.append(copied)
1678 copies.append(copied)
1672
1679
1673 return wanted
1680 return wanted
1674
1681
1675 class _followfilter(object):
1682 class _followfilter(object):
1676 def __init__(self, repo, onlyfirst=False):
1683 def __init__(self, repo, onlyfirst=False):
1677 self.repo = repo
1684 self.repo = repo
1678 self.startrev = nullrev
1685 self.startrev = nullrev
1679 self.roots = set()
1686 self.roots = set()
1680 self.onlyfirst = onlyfirst
1687 self.onlyfirst = onlyfirst
1681
1688
1682 def match(self, rev):
1689 def match(self, rev):
1683 def realparents(rev):
1690 def realparents(rev):
1684 if self.onlyfirst:
1691 if self.onlyfirst:
1685 return self.repo.changelog.parentrevs(rev)[0:1]
1692 return self.repo.changelog.parentrevs(rev)[0:1]
1686 else:
1693 else:
1687 return filter(lambda x: x != nullrev,
1694 return filter(lambda x: x != nullrev,
1688 self.repo.changelog.parentrevs(rev))
1695 self.repo.changelog.parentrevs(rev))
1689
1696
1690 if self.startrev == nullrev:
1697 if self.startrev == nullrev:
1691 self.startrev = rev
1698 self.startrev = rev
1692 return True
1699 return True
1693
1700
1694 if rev > self.startrev:
1701 if rev > self.startrev:
1695 # forward: all descendants
1702 # forward: all descendants
1696 if not self.roots:
1703 if not self.roots:
1697 self.roots.add(self.startrev)
1704 self.roots.add(self.startrev)
1698 for parent in realparents(rev):
1705 for parent in realparents(rev):
1699 if parent in self.roots:
1706 if parent in self.roots:
1700 self.roots.add(rev)
1707 self.roots.add(rev)
1701 return True
1708 return True
1702 else:
1709 else:
1703 # backwards: all parents
1710 # backwards: all parents
1704 if not self.roots:
1711 if not self.roots:
1705 self.roots.update(realparents(self.startrev))
1712 self.roots.update(realparents(self.startrev))
1706 if rev in self.roots:
1713 if rev in self.roots:
1707 self.roots.remove(rev)
1714 self.roots.remove(rev)
1708 self.roots.update(realparents(rev))
1715 self.roots.update(realparents(rev))
1709 return True
1716 return True
1710
1717
1711 return False
1718 return False
1712
1719
1713 def walkchangerevs(repo, match, opts, prepare):
1720 def walkchangerevs(repo, match, opts, prepare):
1714 '''Iterate over files and the revs in which they changed.
1721 '''Iterate over files and the revs in which they changed.
1715
1722
1716 Callers most commonly need to iterate backwards over the history
1723 Callers most commonly need to iterate backwards over the history
1717 in which they are interested. Doing so has awful (quadratic-looking)
1724 in which they are interested. Doing so has awful (quadratic-looking)
1718 performance, so we use iterators in a "windowed" way.
1725 performance, so we use iterators in a "windowed" way.
1719
1726
1720 We walk a window of revisions in the desired order. Within the
1727 We walk a window of revisions in the desired order. Within the
1721 window, we first walk forwards to gather data, then in the desired
1728 window, we first walk forwards to gather data, then in the desired
1722 order (usually backwards) to display it.
1729 order (usually backwards) to display it.
1723
1730
1724 This function returns an iterator yielding contexts. Before
1731 This function returns an iterator yielding contexts. Before
1725 yielding each context, the iterator will first call the prepare
1732 yielding each context, the iterator will first call the prepare
1726 function on each context in the window in forward order.'''
1733 function on each context in the window in forward order.'''
1727
1734
1728 follow = opts.get('follow') or opts.get('follow_first')
1735 follow = opts.get('follow') or opts.get('follow_first')
1729 revs = _logrevs(repo, opts)
1736 revs = _logrevs(repo, opts)
1730 if not revs:
1737 if not revs:
1731 return []
1738 return []
1732 wanted = set()
1739 wanted = set()
1733 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1740 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1734 opts.get('removed'))
1741 opts.get('removed'))
1735 fncache = {}
1742 fncache = {}
1736 change = repo.changectx
1743 change = repo.changectx
1737
1744
1738 # First step is to fill wanted, the set of revisions that we want to yield.
1745 # First step is to fill wanted, the set of revisions that we want to yield.
1739 # When it does not induce extra cost, we also fill fncache for revisions in
1746 # When it does not induce extra cost, we also fill fncache for revisions in
1740 # wanted: a cache of filenames that were changed (ctx.files()) and that
1747 # wanted: a cache of filenames that were changed (ctx.files()) and that
1741 # match the file filtering conditions.
1748 # match the file filtering conditions.
1742
1749
1743 if match.always():
1750 if match.always():
1744 # No files, no patterns. Display all revs.
1751 # No files, no patterns. Display all revs.
1745 wanted = revs
1752 wanted = revs
1746 elif not slowpath:
1753 elif not slowpath:
1747 # We only have to read through the filelog to find wanted revisions
1754 # We only have to read through the filelog to find wanted revisions
1748
1755
1749 try:
1756 try:
1750 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1757 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1751 except FileWalkError:
1758 except FileWalkError:
1752 slowpath = True
1759 slowpath = True
1753
1760
1754 # We decided to fall back to the slowpath because at least one
1761 # We decided to fall back to the slowpath because at least one
1755 # of the paths was not a file. Check to see if at least one of them
1762 # of the paths was not a file. Check to see if at least one of them
1756 # existed in history, otherwise simply return
1763 # existed in history, otherwise simply return
1757 for path in match.files():
1764 for path in match.files():
1758 if path == '.' or path in repo.store:
1765 if path == '.' or path in repo.store:
1759 break
1766 break
1760 else:
1767 else:
1761 return []
1768 return []
1762
1769
1763 if slowpath:
1770 if slowpath:
1764 # We have to read the changelog to match filenames against
1771 # We have to read the changelog to match filenames against
1765 # changed files
1772 # changed files
1766
1773
1767 if follow:
1774 if follow:
1768 raise util.Abort(_('can only follow copies/renames for explicit '
1775 raise util.Abort(_('can only follow copies/renames for explicit '
1769 'filenames'))
1776 'filenames'))
1770
1777
1771 # The slow path checks files modified in every changeset.
1778 # The slow path checks files modified in every changeset.
1772 # This is really slow on large repos, so compute the set lazily.
1779 # This is really slow on large repos, so compute the set lazily.
1773 class lazywantedset(object):
1780 class lazywantedset(object):
1774 def __init__(self):
1781 def __init__(self):
1775 self.set = set()
1782 self.set = set()
1776 self.revs = set(revs)
1783 self.revs = set(revs)
1777
1784
1778 # No need to worry about locality here because it will be accessed
1785 # No need to worry about locality here because it will be accessed
1779 # in the same order as the increasing window below.
1786 # in the same order as the increasing window below.
1780 def __contains__(self, value):
1787 def __contains__(self, value):
1781 if value in self.set:
1788 if value in self.set:
1782 return True
1789 return True
1783 elif not value in self.revs:
1790 elif not value in self.revs:
1784 return False
1791 return False
1785 else:
1792 else:
1786 self.revs.discard(value)
1793 self.revs.discard(value)
1787 ctx = change(value)
1794 ctx = change(value)
1788 matches = filter(match, ctx.files())
1795 matches = filter(match, ctx.files())
1789 if matches:
1796 if matches:
1790 fncache[value] = matches
1797 fncache[value] = matches
1791 self.set.add(value)
1798 self.set.add(value)
1792 return True
1799 return True
1793 return False
1800 return False
1794
1801
1795 def discard(self, value):
1802 def discard(self, value):
1796 self.revs.discard(value)
1803 self.revs.discard(value)
1797 self.set.discard(value)
1804 self.set.discard(value)
1798
1805
1799 wanted = lazywantedset()
1806 wanted = lazywantedset()
1800
1807
1801 # it might be worthwhile to do this in the iterator if the rev range
1808 # it might be worthwhile to do this in the iterator if the rev range
1802 # is descending and the prune args are all within that range
1809 # is descending and the prune args are all within that range
1803 for rev in opts.get('prune', ()):
1810 for rev in opts.get('prune', ()):
1804 rev = repo[rev].rev()
1811 rev = repo[rev].rev()
1805 ff = _followfilter(repo)
1812 ff = _followfilter(repo)
1806 stop = min(revs[0], revs[-1])
1813 stop = min(revs[0], revs[-1])
1807 for x in xrange(rev, stop - 1, -1):
1814 for x in xrange(rev, stop - 1, -1):
1808 if ff.match(x):
1815 if ff.match(x):
1809 wanted = wanted - [x]
1816 wanted = wanted - [x]
1810
1817
1811 # Now that wanted is correctly initialized, we can iterate over the
1818 # Now that wanted is correctly initialized, we can iterate over the
1812 # revision range, yielding only revisions in wanted.
1819 # revision range, yielding only revisions in wanted.
1813 def iterate():
1820 def iterate():
1814 if follow and match.always():
1821 if follow and match.always():
1815 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1822 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1816 def want(rev):
1823 def want(rev):
1817 return ff.match(rev) and rev in wanted
1824 return ff.match(rev) and rev in wanted
1818 else:
1825 else:
1819 def want(rev):
1826 def want(rev):
1820 return rev in wanted
1827 return rev in wanted
1821
1828
1822 it = iter(revs)
1829 it = iter(revs)
1823 stopiteration = False
1830 stopiteration = False
1824 for windowsize in increasingwindows():
1831 for windowsize in increasingwindows():
1825 nrevs = []
1832 nrevs = []
1826 for i in xrange(windowsize):
1833 for i in xrange(windowsize):
1827 rev = next(it, None)
1834 rev = next(it, None)
1828 if rev is None:
1835 if rev is None:
1829 stopiteration = True
1836 stopiteration = True
1830 break
1837 break
1831 elif want(rev):
1838 elif want(rev):
1832 nrevs.append(rev)
1839 nrevs.append(rev)
1833 for rev in sorted(nrevs):
1840 for rev in sorted(nrevs):
1834 fns = fncache.get(rev)
1841 fns = fncache.get(rev)
1835 ctx = change(rev)
1842 ctx = change(rev)
1836 if not fns:
1843 if not fns:
1837 def fns_generator():
1844 def fns_generator():
1838 for f in ctx.files():
1845 for f in ctx.files():
1839 if match(f):
1846 if match(f):
1840 yield f
1847 yield f
1841 fns = fns_generator()
1848 fns = fns_generator()
1842 prepare(ctx, fns)
1849 prepare(ctx, fns)
1843 for rev in nrevs:
1850 for rev in nrevs:
1844 yield change(rev)
1851 yield change(rev)
1845
1852
1846 if stopiteration:
1853 if stopiteration:
1847 break
1854 break
1848
1855
1849 return iterate()
1856 return iterate()
1850
1857
1851 def _makefollowlogfilematcher(repo, files, followfirst):
1858 def _makefollowlogfilematcher(repo, files, followfirst):
1852 # When displaying a revision with --patch --follow FILE, we have
1859 # When displaying a revision with --patch --follow FILE, we have
1853 # to know which file of the revision must be diffed. With
1860 # to know which file of the revision must be diffed. With
1854 # --follow, we want the names of the ancestors of FILE in the
1861 # --follow, we want the names of the ancestors of FILE in the
1855 # revision, stored in "fcache". "fcache" is populated by
1862 # revision, stored in "fcache". "fcache" is populated by
1856 # reproducing the graph traversal already done by --follow revset
1863 # reproducing the graph traversal already done by --follow revset
1857 # and relating linkrevs to file names (which is not "correct" but
1864 # and relating linkrevs to file names (which is not "correct" but
1858 # good enough).
1865 # good enough).
1859 fcache = {}
1866 fcache = {}
1860 fcacheready = [False]
1867 fcacheready = [False]
1861 pctx = repo['.']
1868 pctx = repo['.']
1862
1869
1863 def populate():
1870 def populate():
1864 for fn in files:
1871 for fn in files:
1865 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1872 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1866 for c in i:
1873 for c in i:
1867 fcache.setdefault(c.linkrev(), set()).add(c.path())
1874 fcache.setdefault(c.linkrev(), set()).add(c.path())
1868
1875
1869 def filematcher(rev):
1876 def filematcher(rev):
1870 if not fcacheready[0]:
1877 if not fcacheready[0]:
1871 # Lazy initialization
1878 # Lazy initialization
1872 fcacheready[0] = True
1879 fcacheready[0] = True
1873 populate()
1880 populate()
1874 return scmutil.matchfiles(repo, fcache.get(rev, []))
1881 return scmutil.matchfiles(repo, fcache.get(rev, []))
1875
1882
1876 return filematcher
1883 return filematcher
1877
1884
1878 def _makenofollowlogfilematcher(repo, pats, opts):
1885 def _makenofollowlogfilematcher(repo, pats, opts):
1879 '''hook for extensions to override the filematcher for non-follow cases'''
1886 '''hook for extensions to override the filematcher for non-follow cases'''
1880 return None
1887 return None
1881
1888
1882 def _makelogrevset(repo, pats, opts, revs):
1889 def _makelogrevset(repo, pats, opts, revs):
1883 """Return (expr, filematcher) where expr is a revset string built
1890 """Return (expr, filematcher) where expr is a revset string built
1884 from log options and file patterns or None. If --stat or --patch
1891 from log options and file patterns or None. If --stat or --patch
1885 are not passed filematcher is None. Otherwise it is a callable
1892 are not passed filematcher is None. Otherwise it is a callable
1886 taking a revision number and returning a match objects filtering
1893 taking a revision number and returning a match objects filtering
1887 the files to be detailed when displaying the revision.
1894 the files to be detailed when displaying the revision.
1888 """
1895 """
1889 opt2revset = {
1896 opt2revset = {
1890 'no_merges': ('not merge()', None),
1897 'no_merges': ('not merge()', None),
1891 'only_merges': ('merge()', None),
1898 'only_merges': ('merge()', None),
1892 '_ancestors': ('ancestors(%(val)s)', None),
1899 '_ancestors': ('ancestors(%(val)s)', None),
1893 '_fancestors': ('_firstancestors(%(val)s)', None),
1900 '_fancestors': ('_firstancestors(%(val)s)', None),
1894 '_descendants': ('descendants(%(val)s)', None),
1901 '_descendants': ('descendants(%(val)s)', None),
1895 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1902 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1896 '_matchfiles': ('_matchfiles(%(val)s)', None),
1903 '_matchfiles': ('_matchfiles(%(val)s)', None),
1897 'date': ('date(%(val)r)', None),
1904 'date': ('date(%(val)r)', None),
1898 'branch': ('branch(%(val)r)', ' or '),
1905 'branch': ('branch(%(val)r)', ' or '),
1899 '_patslog': ('filelog(%(val)r)', ' or '),
1906 '_patslog': ('filelog(%(val)r)', ' or '),
1900 '_patsfollow': ('follow(%(val)r)', ' or '),
1907 '_patsfollow': ('follow(%(val)r)', ' or '),
1901 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1908 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1902 'keyword': ('keyword(%(val)r)', ' or '),
1909 'keyword': ('keyword(%(val)r)', ' or '),
1903 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1910 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1904 'user': ('user(%(val)r)', ' or '),
1911 'user': ('user(%(val)r)', ' or '),
1905 }
1912 }
1906
1913
1907 opts = dict(opts)
1914 opts = dict(opts)
1908 # follow or not follow?
1915 # follow or not follow?
1909 follow = opts.get('follow') or opts.get('follow_first')
1916 follow = opts.get('follow') or opts.get('follow_first')
1910 if opts.get('follow_first'):
1917 if opts.get('follow_first'):
1911 followfirst = 1
1918 followfirst = 1
1912 else:
1919 else:
1913 followfirst = 0
1920 followfirst = 0
1914 # --follow with FILE behaviour depends on revs...
1921 # --follow with FILE behaviour depends on revs...
1915 it = iter(revs)
1922 it = iter(revs)
1916 startrev = it.next()
1923 startrev = it.next()
1917 followdescendants = startrev < next(it, startrev)
1924 followdescendants = startrev < next(it, startrev)
1918
1925
1919 # branch and only_branch are really aliases and must be handled at
1926 # branch and only_branch are really aliases and must be handled at
1920 # the same time
1927 # the same time
1921 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1928 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1922 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1929 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1923 # pats/include/exclude are passed to match.match() directly in
1930 # pats/include/exclude are passed to match.match() directly in
1924 # _matchfiles() revset but walkchangerevs() builds its matcher with
1931 # _matchfiles() revset but walkchangerevs() builds its matcher with
1925 # scmutil.match(). The difference is input pats are globbed on
1932 # scmutil.match(). The difference is input pats are globbed on
1926 # platforms without shell expansion (windows).
1933 # platforms without shell expansion (windows).
1927 wctx = repo[None]
1934 wctx = repo[None]
1928 match, pats = scmutil.matchandpats(wctx, pats, opts)
1935 match, pats = scmutil.matchandpats(wctx, pats, opts)
1929 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1936 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1930 opts.get('removed'))
1937 opts.get('removed'))
1931 if not slowpath:
1938 if not slowpath:
1932 for f in match.files():
1939 for f in match.files():
1933 if follow and f not in wctx:
1940 if follow and f not in wctx:
1934 # If the file exists, it may be a directory, so let it
1941 # If the file exists, it may be a directory, so let it
1935 # take the slow path.
1942 # take the slow path.
1936 if os.path.exists(repo.wjoin(f)):
1943 if os.path.exists(repo.wjoin(f)):
1937 slowpath = True
1944 slowpath = True
1938 continue
1945 continue
1939 else:
1946 else:
1940 raise util.Abort(_('cannot follow file not in parent '
1947 raise util.Abort(_('cannot follow file not in parent '
1941 'revision: "%s"') % f)
1948 'revision: "%s"') % f)
1942 filelog = repo.file(f)
1949 filelog = repo.file(f)
1943 if not filelog:
1950 if not filelog:
1944 # A zero count may be a directory or deleted file, so
1951 # A zero count may be a directory or deleted file, so
1945 # try to find matching entries on the slow path.
1952 # try to find matching entries on the slow path.
1946 if follow:
1953 if follow:
1947 raise util.Abort(
1954 raise util.Abort(
1948 _('cannot follow nonexistent file: "%s"') % f)
1955 _('cannot follow nonexistent file: "%s"') % f)
1949 slowpath = True
1956 slowpath = True
1950
1957
1951 # We decided to fall back to the slowpath because at least one
1958 # We decided to fall back to the slowpath because at least one
1952 # of the paths was not a file. Check to see if at least one of them
1959 # of the paths was not a file. Check to see if at least one of them
1953 # existed in history - in that case, we'll continue down the
1960 # existed in history - in that case, we'll continue down the
1954 # slowpath; otherwise, we can turn off the slowpath
1961 # slowpath; otherwise, we can turn off the slowpath
1955 if slowpath:
1962 if slowpath:
1956 for path in match.files():
1963 for path in match.files():
1957 if path == '.' or path in repo.store:
1964 if path == '.' or path in repo.store:
1958 break
1965 break
1959 else:
1966 else:
1960 slowpath = False
1967 slowpath = False
1961
1968
1962 fpats = ('_patsfollow', '_patsfollowfirst')
1969 fpats = ('_patsfollow', '_patsfollowfirst')
1963 fnopats = (('_ancestors', '_fancestors'),
1970 fnopats = (('_ancestors', '_fancestors'),
1964 ('_descendants', '_fdescendants'))
1971 ('_descendants', '_fdescendants'))
1965 if slowpath:
1972 if slowpath:
1966 # See walkchangerevs() slow path.
1973 # See walkchangerevs() slow path.
1967 #
1974 #
1968 # pats/include/exclude cannot be represented as separate
1975 # pats/include/exclude cannot be represented as separate
1969 # revset expressions as their filtering logic applies at file
1976 # revset expressions as their filtering logic applies at file
1970 # level. For instance "-I a -X a" matches a revision touching
1977 # level. For instance "-I a -X a" matches a revision touching
1971 # "a" and "b" while "file(a) and not file(b)" does
1978 # "a" and "b" while "file(a) and not file(b)" does
1972 # not. Besides, filesets are evaluated against the working
1979 # not. Besides, filesets are evaluated against the working
1973 # directory.
1980 # directory.
1974 matchargs = ['r:', 'd:relpath']
1981 matchargs = ['r:', 'd:relpath']
1975 for p in pats:
1982 for p in pats:
1976 matchargs.append('p:' + p)
1983 matchargs.append('p:' + p)
1977 for p in opts.get('include', []):
1984 for p in opts.get('include', []):
1978 matchargs.append('i:' + p)
1985 matchargs.append('i:' + p)
1979 for p in opts.get('exclude', []):
1986 for p in opts.get('exclude', []):
1980 matchargs.append('x:' + p)
1987 matchargs.append('x:' + p)
1981 matchargs = ','.join(('%r' % p) for p in matchargs)
1988 matchargs = ','.join(('%r' % p) for p in matchargs)
1982 opts['_matchfiles'] = matchargs
1989 opts['_matchfiles'] = matchargs
1983 if follow:
1990 if follow:
1984 opts[fnopats[0][followfirst]] = '.'
1991 opts[fnopats[0][followfirst]] = '.'
1985 else:
1992 else:
1986 if follow:
1993 if follow:
1987 if pats:
1994 if pats:
1988 # follow() revset interprets its file argument as a
1995 # follow() revset interprets its file argument as a
1989 # manifest entry, so use match.files(), not pats.
1996 # manifest entry, so use match.files(), not pats.
1990 opts[fpats[followfirst]] = list(match.files())
1997 opts[fpats[followfirst]] = list(match.files())
1991 else:
1998 else:
1992 op = fnopats[followdescendants][followfirst]
1999 op = fnopats[followdescendants][followfirst]
1993 opts[op] = 'rev(%d)' % startrev
2000 opts[op] = 'rev(%d)' % startrev
1994 else:
2001 else:
1995 opts['_patslog'] = list(pats)
2002 opts['_patslog'] = list(pats)
1996
2003
1997 filematcher = None
2004 filematcher = None
1998 if opts.get('patch') or opts.get('stat'):
2005 if opts.get('patch') or opts.get('stat'):
1999 # When following files, track renames via a special matcher.
2006 # When following files, track renames via a special matcher.
2000 # If we're forced to take the slowpath it means we're following
2007 # If we're forced to take the slowpath it means we're following
2001 # at least one pattern/directory, so don't bother with rename tracking.
2008 # at least one pattern/directory, so don't bother with rename tracking.
2002 if follow and not match.always() and not slowpath:
2009 if follow and not match.always() and not slowpath:
2003 # _makefollowlogfilematcher expects its files argument to be
2010 # _makefollowlogfilematcher expects its files argument to be
2004 # relative to the repo root, so use match.files(), not pats.
2011 # relative to the repo root, so use match.files(), not pats.
2005 filematcher = _makefollowlogfilematcher(repo, match.files(),
2012 filematcher = _makefollowlogfilematcher(repo, match.files(),
2006 followfirst)
2013 followfirst)
2007 else:
2014 else:
2008 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2015 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2009 if filematcher is None:
2016 if filematcher is None:
2010 filematcher = lambda rev: match
2017 filematcher = lambda rev: match
2011
2018
2012 expr = []
2019 expr = []
2013 for op, val in sorted(opts.iteritems()):
2020 for op, val in sorted(opts.iteritems()):
2014 if not val:
2021 if not val:
2015 continue
2022 continue
2016 if op not in opt2revset:
2023 if op not in opt2revset:
2017 continue
2024 continue
2018 revop, andor = opt2revset[op]
2025 revop, andor = opt2revset[op]
2019 if '%(val)' not in revop:
2026 if '%(val)' not in revop:
2020 expr.append(revop)
2027 expr.append(revop)
2021 else:
2028 else:
2022 if not isinstance(val, list):
2029 if not isinstance(val, list):
2023 e = revop % {'val': val}
2030 e = revop % {'val': val}
2024 else:
2031 else:
2025 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2032 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2026 expr.append(e)
2033 expr.append(e)
2027
2034
2028 if expr:
2035 if expr:
2029 expr = '(' + ' and '.join(expr) + ')'
2036 expr = '(' + ' and '.join(expr) + ')'
2030 else:
2037 else:
2031 expr = None
2038 expr = None
2032 return expr, filematcher
2039 return expr, filematcher
2033
2040
2034 def _logrevs(repo, opts):
2041 def _logrevs(repo, opts):
2035 # Default --rev value depends on --follow but --follow behaviour
2042 # Default --rev value depends on --follow but --follow behaviour
2036 # depends on revisions resolved from --rev...
2043 # depends on revisions resolved from --rev...
2037 follow = opts.get('follow') or opts.get('follow_first')
2044 follow = opts.get('follow') or opts.get('follow_first')
2038 if opts.get('rev'):
2045 if opts.get('rev'):
2039 revs = scmutil.revrange(repo, opts['rev'])
2046 revs = scmutil.revrange(repo, opts['rev'])
2040 elif follow and repo.dirstate.p1() == nullid:
2047 elif follow and repo.dirstate.p1() == nullid:
2041 revs = revset.baseset()
2048 revs = revset.baseset()
2042 elif follow:
2049 elif follow:
2043 revs = repo.revs('reverse(:.)')
2050 revs = repo.revs('reverse(:.)')
2044 else:
2051 else:
2045 revs = revset.spanset(repo)
2052 revs = revset.spanset(repo)
2046 revs.reverse()
2053 revs.reverse()
2047 return revs
2054 return revs
2048
2055
2049 def getgraphlogrevs(repo, pats, opts):
2056 def getgraphlogrevs(repo, pats, opts):
2050 """Return (revs, expr, filematcher) where revs is an iterable of
2057 """Return (revs, expr, filematcher) where revs is an iterable of
2051 revision numbers, expr is a revset string built from log options
2058 revision numbers, expr is a revset string built from log options
2052 and file patterns or None, and used to filter 'revs'. If --stat or
2059 and file patterns or None, and used to filter 'revs'. If --stat or
2053 --patch are not passed filematcher is None. Otherwise it is a
2060 --patch are not passed filematcher is None. Otherwise it is a
2054 callable taking a revision number and returning a match objects
2061 callable taking a revision number and returning a match objects
2055 filtering the files to be detailed when displaying the revision.
2062 filtering the files to be detailed when displaying the revision.
2056 """
2063 """
2057 limit = loglimit(opts)
2064 limit = loglimit(opts)
2058 revs = _logrevs(repo, opts)
2065 revs = _logrevs(repo, opts)
2059 if not revs:
2066 if not revs:
2060 return revset.baseset(), None, None
2067 return revset.baseset(), None, None
2061 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2068 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2062 if opts.get('rev'):
2069 if opts.get('rev'):
2063 # User-specified revs might be unsorted, but don't sort before
2070 # User-specified revs might be unsorted, but don't sort before
2064 # _makelogrevset because it might depend on the order of revs
2071 # _makelogrevset because it might depend on the order of revs
2065 revs.sort(reverse=True)
2072 revs.sort(reverse=True)
2066 if expr:
2073 if expr:
2067 # Revset matchers often operate faster on revisions in changelog
2074 # Revset matchers often operate faster on revisions in changelog
2068 # order, because most filters deal with the changelog.
2075 # order, because most filters deal with the changelog.
2069 revs.reverse()
2076 revs.reverse()
2070 matcher = revset.match(repo.ui, expr)
2077 matcher = revset.match(repo.ui, expr)
2071 # Revset matches can reorder revisions. "A or B" typically returns
2078 # Revset matches can reorder revisions. "A or B" typically returns
2072 # returns the revision matching A then the revision matching B. Sort
2079 # returns the revision matching A then the revision matching B. Sort
2073 # again to fix that.
2080 # again to fix that.
2074 revs = matcher(repo, revs)
2081 revs = matcher(repo, revs)
2075 revs.sort(reverse=True)
2082 revs.sort(reverse=True)
2076 if limit is not None:
2083 if limit is not None:
2077 limitedrevs = []
2084 limitedrevs = []
2078 for idx, rev in enumerate(revs):
2085 for idx, rev in enumerate(revs):
2079 if idx >= limit:
2086 if idx >= limit:
2080 break
2087 break
2081 limitedrevs.append(rev)
2088 limitedrevs.append(rev)
2082 revs = revset.baseset(limitedrevs)
2089 revs = revset.baseset(limitedrevs)
2083
2090
2084 return revs, expr, filematcher
2091 return revs, expr, filematcher
2085
2092
2086 def getlogrevs(repo, pats, opts):
2093 def getlogrevs(repo, pats, opts):
2087 """Return (revs, expr, filematcher) where revs is an iterable of
2094 """Return (revs, expr, filematcher) where revs is an iterable of
2088 revision numbers, expr is a revset string built from log options
2095 revision numbers, expr is a revset string built from log options
2089 and file patterns or None, and used to filter 'revs'. If --stat or
2096 and file patterns or None, and used to filter 'revs'. If --stat or
2090 --patch are not passed filematcher is None. Otherwise it is a
2097 --patch are not passed filematcher is None. Otherwise it is a
2091 callable taking a revision number and returning a match objects
2098 callable taking a revision number and returning a match objects
2092 filtering the files to be detailed when displaying the revision.
2099 filtering the files to be detailed when displaying the revision.
2093 """
2100 """
2094 limit = loglimit(opts)
2101 limit = loglimit(opts)
2095 revs = _logrevs(repo, opts)
2102 revs = _logrevs(repo, opts)
2096 if not revs:
2103 if not revs:
2097 return revset.baseset([]), None, None
2104 return revset.baseset([]), None, None
2098 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2105 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2099 if expr:
2106 if expr:
2100 # Revset matchers often operate faster on revisions in changelog
2107 # Revset matchers often operate faster on revisions in changelog
2101 # order, because most filters deal with the changelog.
2108 # order, because most filters deal with the changelog.
2102 if not opts.get('rev'):
2109 if not opts.get('rev'):
2103 revs.reverse()
2110 revs.reverse()
2104 matcher = revset.match(repo.ui, expr)
2111 matcher = revset.match(repo.ui, expr)
2105 # Revset matches can reorder revisions. "A or B" typically returns
2112 # Revset matches can reorder revisions. "A or B" typically returns
2106 # returns the revision matching A then the revision matching B. Sort
2113 # returns the revision matching A then the revision matching B. Sort
2107 # again to fix that.
2114 # again to fix that.
2108 revs = matcher(repo, revs)
2115 revs = matcher(repo, revs)
2109 if not opts.get('rev'):
2116 if not opts.get('rev'):
2110 revs.sort(reverse=True)
2117 revs.sort(reverse=True)
2111 if limit is not None:
2118 if limit is not None:
2112 limitedrevs = []
2119 limitedrevs = []
2113 for idx, r in enumerate(revs):
2120 for idx, r in enumerate(revs):
2114 if limit <= idx:
2121 if limit <= idx:
2115 break
2122 break
2116 limitedrevs.append(r)
2123 limitedrevs.append(r)
2117 revs = revset.baseset(limitedrevs)
2124 revs = revset.baseset(limitedrevs)
2118
2125
2119 return revs, expr, filematcher
2126 return revs, expr, filematcher
2120
2127
2121 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2128 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2122 filematcher=None):
2129 filematcher=None):
2123 seen, state = [], graphmod.asciistate()
2130 seen, state = [], graphmod.asciistate()
2124 for rev, type, ctx, parents in dag:
2131 for rev, type, ctx, parents in dag:
2125 char = 'o'
2132 char = 'o'
2126 if ctx.node() in showparents:
2133 if ctx.node() in showparents:
2127 char = '@'
2134 char = '@'
2128 elif ctx.obsolete():
2135 elif ctx.obsolete():
2129 char = 'x'
2136 char = 'x'
2130 elif ctx.closesbranch():
2137 elif ctx.closesbranch():
2131 char = '_'
2138 char = '_'
2132 copies = None
2139 copies = None
2133 if getrenamed and ctx.rev():
2140 if getrenamed and ctx.rev():
2134 copies = []
2141 copies = []
2135 for fn in ctx.files():
2142 for fn in ctx.files():
2136 rename = getrenamed(fn, ctx.rev())
2143 rename = getrenamed(fn, ctx.rev())
2137 if rename:
2144 if rename:
2138 copies.append((fn, rename[0]))
2145 copies.append((fn, rename[0]))
2139 revmatchfn = None
2146 revmatchfn = None
2140 if filematcher is not None:
2147 if filematcher is not None:
2141 revmatchfn = filematcher(ctx.rev())
2148 revmatchfn = filematcher(ctx.rev())
2142 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2149 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2143 lines = displayer.hunk.pop(rev).split('\n')
2150 lines = displayer.hunk.pop(rev).split('\n')
2144 if not lines[-1]:
2151 if not lines[-1]:
2145 del lines[-1]
2152 del lines[-1]
2146 displayer.flush(rev)
2153 displayer.flush(rev)
2147 edges = edgefn(type, char, lines, seen, rev, parents)
2154 edges = edgefn(type, char, lines, seen, rev, parents)
2148 for type, char, lines, coldata in edges:
2155 for type, char, lines, coldata in edges:
2149 graphmod.ascii(ui, state, type, char, lines, coldata)
2156 graphmod.ascii(ui, state, type, char, lines, coldata)
2150 displayer.close()
2157 displayer.close()
2151
2158
2152 def graphlog(ui, repo, *pats, **opts):
2159 def graphlog(ui, repo, *pats, **opts):
2153 # Parameters are identical to log command ones
2160 # Parameters are identical to log command ones
2154 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2161 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2155 revdag = graphmod.dagwalker(repo, revs)
2162 revdag = graphmod.dagwalker(repo, revs)
2156
2163
2157 getrenamed = None
2164 getrenamed = None
2158 if opts.get('copies'):
2165 if opts.get('copies'):
2159 endrev = None
2166 endrev = None
2160 if opts.get('rev'):
2167 if opts.get('rev'):
2161 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2168 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2162 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2169 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2163 displayer = show_changeset(ui, repo, opts, buffered=True)
2170 displayer = show_changeset(ui, repo, opts, buffered=True)
2164 showparents = [ctx.node() for ctx in repo[None].parents()]
2171 showparents = [ctx.node() for ctx in repo[None].parents()]
2165 displaygraph(ui, revdag, displayer, showparents,
2172 displaygraph(ui, revdag, displayer, showparents,
2166 graphmod.asciiedges, getrenamed, filematcher)
2173 graphmod.asciiedges, getrenamed, filematcher)
2167
2174
2168 def checkunsupportedgraphflags(pats, opts):
2175 def checkunsupportedgraphflags(pats, opts):
2169 for op in ["newest_first"]:
2176 for op in ["newest_first"]:
2170 if op in opts and opts[op]:
2177 if op in opts and opts[op]:
2171 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2178 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2172 % op.replace("_", "-"))
2179 % op.replace("_", "-"))
2173
2180
2174 def graphrevs(repo, nodes, opts):
2181 def graphrevs(repo, nodes, opts):
2175 limit = loglimit(opts)
2182 limit = loglimit(opts)
2176 nodes.reverse()
2183 nodes.reverse()
2177 if limit is not None:
2184 if limit is not None:
2178 nodes = nodes[:limit]
2185 nodes = nodes[:limit]
2179 return graphmod.nodes(repo, nodes)
2186 return graphmod.nodes(repo, nodes)
2180
2187
2181 def add(ui, repo, match, prefix, explicitonly, **opts):
2188 def add(ui, repo, match, prefix, explicitonly, **opts):
2182 join = lambda f: os.path.join(prefix, f)
2189 join = lambda f: os.path.join(prefix, f)
2183 bad = []
2190 bad = []
2184
2191
2185 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2192 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2186 names = []
2193 names = []
2187 wctx = repo[None]
2194 wctx = repo[None]
2188 cca = None
2195 cca = None
2189 abort, warn = scmutil.checkportabilityalert(ui)
2196 abort, warn = scmutil.checkportabilityalert(ui)
2190 if abort or warn:
2197 if abort or warn:
2191 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2198 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2192
2199
2193 for f in wctx.walk(matchmod.badmatch(match, badfn)):
2200 for f in wctx.walk(matchmod.badmatch(match, badfn)):
2194 exact = match.exact(f)
2201 exact = match.exact(f)
2195 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2202 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2196 if cca:
2203 if cca:
2197 cca(f)
2204 cca(f)
2198 names.append(f)
2205 names.append(f)
2199 if ui.verbose or not exact:
2206 if ui.verbose or not exact:
2200 ui.status(_('adding %s\n') % match.rel(f))
2207 ui.status(_('adding %s\n') % match.rel(f))
2201
2208
2202 for subpath in sorted(wctx.substate):
2209 for subpath in sorted(wctx.substate):
2203 sub = wctx.sub(subpath)
2210 sub = wctx.sub(subpath)
2204 try:
2211 try:
2205 submatch = matchmod.narrowmatcher(subpath, match)
2212 submatch = matchmod.narrowmatcher(subpath, match)
2206 if opts.get('subrepos'):
2213 if opts.get('subrepos'):
2207 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2214 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2208 else:
2215 else:
2209 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2216 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2210 except error.LookupError:
2217 except error.LookupError:
2211 ui.status(_("skipping missing subrepository: %s\n")
2218 ui.status(_("skipping missing subrepository: %s\n")
2212 % join(subpath))
2219 % join(subpath))
2213
2220
2214 if not opts.get('dry_run'):
2221 if not opts.get('dry_run'):
2215 rejected = wctx.add(names, prefix)
2222 rejected = wctx.add(names, prefix)
2216 bad.extend(f for f in rejected if f in match.files())
2223 bad.extend(f for f in rejected if f in match.files())
2217 return bad
2224 return bad
2218
2225
2219 def forget(ui, repo, match, prefix, explicitonly):
2226 def forget(ui, repo, match, prefix, explicitonly):
2220 join = lambda f: os.path.join(prefix, f)
2227 join = lambda f: os.path.join(prefix, f)
2221 bad = []
2228 bad = []
2222 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2229 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2223 wctx = repo[None]
2230 wctx = repo[None]
2224 forgot = []
2231 forgot = []
2225
2232
2226 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2233 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2227 forget = sorted(s[0] + s[1] + s[3] + s[6])
2234 forget = sorted(s[0] + s[1] + s[3] + s[6])
2228 if explicitonly:
2235 if explicitonly:
2229 forget = [f for f in forget if match.exact(f)]
2236 forget = [f for f in forget if match.exact(f)]
2230
2237
2231 for subpath in sorted(wctx.substate):
2238 for subpath in sorted(wctx.substate):
2232 sub = wctx.sub(subpath)
2239 sub = wctx.sub(subpath)
2233 try:
2240 try:
2234 submatch = matchmod.narrowmatcher(subpath, match)
2241 submatch = matchmod.narrowmatcher(subpath, match)
2235 subbad, subforgot = sub.forget(submatch, prefix)
2242 subbad, subforgot = sub.forget(submatch, prefix)
2236 bad.extend([subpath + '/' + f for f in subbad])
2243 bad.extend([subpath + '/' + f for f in subbad])
2237 forgot.extend([subpath + '/' + f for f in subforgot])
2244 forgot.extend([subpath + '/' + f for f in subforgot])
2238 except error.LookupError:
2245 except error.LookupError:
2239 ui.status(_("skipping missing subrepository: %s\n")
2246 ui.status(_("skipping missing subrepository: %s\n")
2240 % join(subpath))
2247 % join(subpath))
2241
2248
2242 if not explicitonly:
2249 if not explicitonly:
2243 for f in match.files():
2250 for f in match.files():
2244 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2251 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2245 if f not in forgot:
2252 if f not in forgot:
2246 if repo.wvfs.exists(f):
2253 if repo.wvfs.exists(f):
2247 # Don't complain if the exact case match wasn't given.
2254 # Don't complain if the exact case match wasn't given.
2248 # But don't do this until after checking 'forgot', so
2255 # But don't do this until after checking 'forgot', so
2249 # that subrepo files aren't normalized, and this op is
2256 # that subrepo files aren't normalized, and this op is
2250 # purely from data cached by the status walk above.
2257 # purely from data cached by the status walk above.
2251 if repo.dirstate.normalize(f) in repo.dirstate:
2258 if repo.dirstate.normalize(f) in repo.dirstate:
2252 continue
2259 continue
2253 ui.warn(_('not removing %s: '
2260 ui.warn(_('not removing %s: '
2254 'file is already untracked\n')
2261 'file is already untracked\n')
2255 % match.rel(f))
2262 % match.rel(f))
2256 bad.append(f)
2263 bad.append(f)
2257
2264
2258 for f in forget:
2265 for f in forget:
2259 if ui.verbose or not match.exact(f):
2266 if ui.verbose or not match.exact(f):
2260 ui.status(_('removing %s\n') % match.rel(f))
2267 ui.status(_('removing %s\n') % match.rel(f))
2261
2268
2262 rejected = wctx.forget(forget, prefix)
2269 rejected = wctx.forget(forget, prefix)
2263 bad.extend(f for f in rejected if f in match.files())
2270 bad.extend(f for f in rejected if f in match.files())
2264 forgot.extend(f for f in forget if f not in rejected)
2271 forgot.extend(f for f in forget if f not in rejected)
2265 return bad, forgot
2272 return bad, forgot
2266
2273
2267 def files(ui, ctx, m, fm, fmt, subrepos):
2274 def files(ui, ctx, m, fm, fmt, subrepos):
2268 rev = ctx.rev()
2275 rev = ctx.rev()
2269 ret = 1
2276 ret = 1
2270 ds = ctx.repo().dirstate
2277 ds = ctx.repo().dirstate
2271
2278
2272 for f in ctx.matches(m):
2279 for f in ctx.matches(m):
2273 if rev is None and ds[f] == 'r':
2280 if rev is None and ds[f] == 'r':
2274 continue
2281 continue
2275 fm.startitem()
2282 fm.startitem()
2276 if ui.verbose:
2283 if ui.verbose:
2277 fc = ctx[f]
2284 fc = ctx[f]
2278 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2285 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2279 fm.data(abspath=f)
2286 fm.data(abspath=f)
2280 fm.write('path', fmt, m.rel(f))
2287 fm.write('path', fmt, m.rel(f))
2281 ret = 0
2288 ret = 0
2282
2289
2283 for subpath in sorted(ctx.substate):
2290 for subpath in sorted(ctx.substate):
2284 def matchessubrepo(subpath):
2291 def matchessubrepo(subpath):
2285 return (m.always() or m.exact(subpath)
2292 return (m.always() or m.exact(subpath)
2286 or any(f.startswith(subpath + '/') for f in m.files()))
2293 or any(f.startswith(subpath + '/') for f in m.files()))
2287
2294
2288 if subrepos or matchessubrepo(subpath):
2295 if subrepos or matchessubrepo(subpath):
2289 sub = ctx.sub(subpath)
2296 sub = ctx.sub(subpath)
2290 try:
2297 try:
2291 submatch = matchmod.narrowmatcher(subpath, m)
2298 submatch = matchmod.narrowmatcher(subpath, m)
2292 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2299 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2293 ret = 0
2300 ret = 0
2294 except error.LookupError:
2301 except error.LookupError:
2295 ui.status(_("skipping missing subrepository: %s\n")
2302 ui.status(_("skipping missing subrepository: %s\n")
2296 % m.abs(subpath))
2303 % m.abs(subpath))
2297
2304
2298 return ret
2305 return ret
2299
2306
2300 def remove(ui, repo, m, prefix, after, force, subrepos):
2307 def remove(ui, repo, m, prefix, after, force, subrepos):
2301 join = lambda f: os.path.join(prefix, f)
2308 join = lambda f: os.path.join(prefix, f)
2302 ret = 0
2309 ret = 0
2303 s = repo.status(match=m, clean=True)
2310 s = repo.status(match=m, clean=True)
2304 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2311 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2305
2312
2306 wctx = repo[None]
2313 wctx = repo[None]
2307
2314
2308 for subpath in sorted(wctx.substate):
2315 for subpath in sorted(wctx.substate):
2309 def matchessubrepo(matcher, subpath):
2316 def matchessubrepo(matcher, subpath):
2310 if matcher.exact(subpath):
2317 if matcher.exact(subpath):
2311 return True
2318 return True
2312 for f in matcher.files():
2319 for f in matcher.files():
2313 if f.startswith(subpath):
2320 if f.startswith(subpath):
2314 return True
2321 return True
2315 return False
2322 return False
2316
2323
2317 if subrepos or matchessubrepo(m, subpath):
2324 if subrepos or matchessubrepo(m, subpath):
2318 sub = wctx.sub(subpath)
2325 sub = wctx.sub(subpath)
2319 try:
2326 try:
2320 submatch = matchmod.narrowmatcher(subpath, m)
2327 submatch = matchmod.narrowmatcher(subpath, m)
2321 if sub.removefiles(submatch, prefix, after, force, subrepos):
2328 if sub.removefiles(submatch, prefix, after, force, subrepos):
2322 ret = 1
2329 ret = 1
2323 except error.LookupError:
2330 except error.LookupError:
2324 ui.status(_("skipping missing subrepository: %s\n")
2331 ui.status(_("skipping missing subrepository: %s\n")
2325 % join(subpath))
2332 % join(subpath))
2326
2333
2327 # warn about failure to delete explicit files/dirs
2334 # warn about failure to delete explicit files/dirs
2328 deleteddirs = util.dirs(deleted)
2335 deleteddirs = util.dirs(deleted)
2329 for f in m.files():
2336 for f in m.files():
2330 def insubrepo():
2337 def insubrepo():
2331 for subpath in wctx.substate:
2338 for subpath in wctx.substate:
2332 if f.startswith(subpath):
2339 if f.startswith(subpath):
2333 return True
2340 return True
2334 return False
2341 return False
2335
2342
2336 isdir = f in deleteddirs or wctx.hasdir(f)
2343 isdir = f in deleteddirs or wctx.hasdir(f)
2337 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2344 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2338 continue
2345 continue
2339
2346
2340 if repo.wvfs.exists(f):
2347 if repo.wvfs.exists(f):
2341 if repo.wvfs.isdir(f):
2348 if repo.wvfs.isdir(f):
2342 ui.warn(_('not removing %s: no tracked files\n')
2349 ui.warn(_('not removing %s: no tracked files\n')
2343 % m.rel(f))
2350 % m.rel(f))
2344 else:
2351 else:
2345 ui.warn(_('not removing %s: file is untracked\n')
2352 ui.warn(_('not removing %s: file is untracked\n')
2346 % m.rel(f))
2353 % m.rel(f))
2347 # missing files will generate a warning elsewhere
2354 # missing files will generate a warning elsewhere
2348 ret = 1
2355 ret = 1
2349
2356
2350 if force:
2357 if force:
2351 list = modified + deleted + clean + added
2358 list = modified + deleted + clean + added
2352 elif after:
2359 elif after:
2353 list = deleted
2360 list = deleted
2354 for f in modified + added + clean:
2361 for f in modified + added + clean:
2355 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2362 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2356 ret = 1
2363 ret = 1
2357 else:
2364 else:
2358 list = deleted + clean
2365 list = deleted + clean
2359 for f in modified:
2366 for f in modified:
2360 ui.warn(_('not removing %s: file is modified (use -f'
2367 ui.warn(_('not removing %s: file is modified (use -f'
2361 ' to force removal)\n') % m.rel(f))
2368 ' to force removal)\n') % m.rel(f))
2362 ret = 1
2369 ret = 1
2363 for f in added:
2370 for f in added:
2364 ui.warn(_('not removing %s: file has been marked for add'
2371 ui.warn(_('not removing %s: file has been marked for add'
2365 ' (use forget to undo)\n') % m.rel(f))
2372 ' (use forget to undo)\n') % m.rel(f))
2366 ret = 1
2373 ret = 1
2367
2374
2368 for f in sorted(list):
2375 for f in sorted(list):
2369 if ui.verbose or not m.exact(f):
2376 if ui.verbose or not m.exact(f):
2370 ui.status(_('removing %s\n') % m.rel(f))
2377 ui.status(_('removing %s\n') % m.rel(f))
2371
2378
2372 wlock = repo.wlock()
2379 wlock = repo.wlock()
2373 try:
2380 try:
2374 if not after:
2381 if not after:
2375 for f in list:
2382 for f in list:
2376 if f in added:
2383 if f in added:
2377 continue # we never unlink added files on remove
2384 continue # we never unlink added files on remove
2378 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2385 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2379 repo[None].forget(list)
2386 repo[None].forget(list)
2380 finally:
2387 finally:
2381 wlock.release()
2388 wlock.release()
2382
2389
2383 return ret
2390 return ret
2384
2391
2385 def cat(ui, repo, ctx, matcher, prefix, **opts):
2392 def cat(ui, repo, ctx, matcher, prefix, **opts):
2386 err = 1
2393 err = 1
2387
2394
2388 def write(path):
2395 def write(path):
2389 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2396 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2390 pathname=os.path.join(prefix, path))
2397 pathname=os.path.join(prefix, path))
2391 data = ctx[path].data()
2398 data = ctx[path].data()
2392 if opts.get('decode'):
2399 if opts.get('decode'):
2393 data = repo.wwritedata(path, data)
2400 data = repo.wwritedata(path, data)
2394 fp.write(data)
2401 fp.write(data)
2395 fp.close()
2402 fp.close()
2396
2403
2397 # Automation often uses hg cat on single files, so special case it
2404 # Automation often uses hg cat on single files, so special case it
2398 # for performance to avoid the cost of parsing the manifest.
2405 # for performance to avoid the cost of parsing the manifest.
2399 if len(matcher.files()) == 1 and not matcher.anypats():
2406 if len(matcher.files()) == 1 and not matcher.anypats():
2400 file = matcher.files()[0]
2407 file = matcher.files()[0]
2401 mf = repo.manifest
2408 mf = repo.manifest
2402 mfnode = ctx.manifestnode()
2409 mfnode = ctx.manifestnode()
2403 if mfnode and mf.find(mfnode, file)[0]:
2410 if mfnode and mf.find(mfnode, file)[0]:
2404 write(file)
2411 write(file)
2405 return 0
2412 return 0
2406
2413
2407 # Don't warn about "missing" files that are really in subrepos
2414 # Don't warn about "missing" files that are really in subrepos
2408 def badfn(path, msg):
2415 def badfn(path, msg):
2409 for subpath in ctx.substate:
2416 for subpath in ctx.substate:
2410 if path.startswith(subpath):
2417 if path.startswith(subpath):
2411 return
2418 return
2412 matcher.bad(path, msg)
2419 matcher.bad(path, msg)
2413
2420
2414 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2421 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2415 write(abs)
2422 write(abs)
2416 err = 0
2423 err = 0
2417
2424
2418 for subpath in sorted(ctx.substate):
2425 for subpath in sorted(ctx.substate):
2419 sub = ctx.sub(subpath)
2426 sub = ctx.sub(subpath)
2420 try:
2427 try:
2421 submatch = matchmod.narrowmatcher(subpath, matcher)
2428 submatch = matchmod.narrowmatcher(subpath, matcher)
2422
2429
2423 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2430 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2424 **opts):
2431 **opts):
2425 err = 0
2432 err = 0
2426 except error.RepoLookupError:
2433 except error.RepoLookupError:
2427 ui.status(_("skipping missing subrepository: %s\n")
2434 ui.status(_("skipping missing subrepository: %s\n")
2428 % os.path.join(prefix, subpath))
2435 % os.path.join(prefix, subpath))
2429
2436
2430 return err
2437 return err
2431
2438
2432 def commit(ui, repo, commitfunc, pats, opts):
2439 def commit(ui, repo, commitfunc, pats, opts):
2433 '''commit the specified files or all outstanding changes'''
2440 '''commit the specified files or all outstanding changes'''
2434 date = opts.get('date')
2441 date = opts.get('date')
2435 if date:
2442 if date:
2436 opts['date'] = util.parsedate(date)
2443 opts['date'] = util.parsedate(date)
2437 message = logmessage(ui, opts)
2444 message = logmessage(ui, opts)
2438 matcher = scmutil.match(repo[None], pats, opts)
2445 matcher = scmutil.match(repo[None], pats, opts)
2439
2446
2440 # extract addremove carefully -- this function can be called from a command
2447 # extract addremove carefully -- this function can be called from a command
2441 # that doesn't support addremove
2448 # that doesn't support addremove
2442 if opts.get('addremove'):
2449 if opts.get('addremove'):
2443 if scmutil.addremove(repo, matcher, "", opts) != 0:
2450 if scmutil.addremove(repo, matcher, "", opts) != 0:
2444 raise util.Abort(
2451 raise util.Abort(
2445 _("failed to mark all new/missing files as added/removed"))
2452 _("failed to mark all new/missing files as added/removed"))
2446
2453
2447 return commitfunc(ui, repo, message, matcher, opts)
2454 return commitfunc(ui, repo, message, matcher, opts)
2448
2455
2449 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2456 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2450 # amend will reuse the existing user if not specified, but the obsolete
2457 # amend will reuse the existing user if not specified, but the obsolete
2451 # marker creation requires that the current user's name is specified.
2458 # marker creation requires that the current user's name is specified.
2452 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2459 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2453 ui.username() # raise exception if username not set
2460 ui.username() # raise exception if username not set
2454
2461
2455 ui.note(_('amending changeset %s\n') % old)
2462 ui.note(_('amending changeset %s\n') % old)
2456 base = old.p1()
2463 base = old.p1()
2457 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2464 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2458
2465
2459 wlock = dsguard = lock = newid = None
2466 wlock = dsguard = lock = newid = None
2460 try:
2467 try:
2461 wlock = repo.wlock()
2468 wlock = repo.wlock()
2462 dsguard = dirstateguard(repo, 'amend')
2469 dsguard = dirstateguard(repo, 'amend')
2463 lock = repo.lock()
2470 lock = repo.lock()
2464 tr = repo.transaction('amend')
2471 tr = repo.transaction('amend')
2465 try:
2472 try:
2466 # See if we got a message from -m or -l, if not, open the editor
2473 # See if we got a message from -m or -l, if not, open the editor
2467 # with the message of the changeset to amend
2474 # with the message of the changeset to amend
2468 message = logmessage(ui, opts)
2475 message = logmessage(ui, opts)
2469 # ensure logfile does not conflict with later enforcement of the
2476 # ensure logfile does not conflict with later enforcement of the
2470 # message. potential logfile content has been processed by
2477 # message. potential logfile content has been processed by
2471 # `logmessage` anyway.
2478 # `logmessage` anyway.
2472 opts.pop('logfile')
2479 opts.pop('logfile')
2473 # First, do a regular commit to record all changes in the working
2480 # First, do a regular commit to record all changes in the working
2474 # directory (if there are any)
2481 # directory (if there are any)
2475 ui.callhooks = False
2482 ui.callhooks = False
2476 activebookmark = repo._activebookmark
2483 activebookmark = repo._activebookmark
2477 try:
2484 try:
2478 repo._activebookmark = None
2485 repo._activebookmark = None
2479 opts['message'] = 'temporary amend commit for %s' % old
2486 opts['message'] = 'temporary amend commit for %s' % old
2480 node = commit(ui, repo, commitfunc, pats, opts)
2487 node = commit(ui, repo, commitfunc, pats, opts)
2481 finally:
2488 finally:
2482 repo._activebookmark = activebookmark
2489 repo._activebookmark = activebookmark
2483 ui.callhooks = True
2490 ui.callhooks = True
2484 ctx = repo[node]
2491 ctx = repo[node]
2485
2492
2486 # Participating changesets:
2493 # Participating changesets:
2487 #
2494 #
2488 # node/ctx o - new (intermediate) commit that contains changes
2495 # node/ctx o - new (intermediate) commit that contains changes
2489 # | from working dir to go into amending commit
2496 # | from working dir to go into amending commit
2490 # | (or a workingctx if there were no changes)
2497 # | (or a workingctx if there were no changes)
2491 # |
2498 # |
2492 # old o - changeset to amend
2499 # old o - changeset to amend
2493 # |
2500 # |
2494 # base o - parent of amending changeset
2501 # base o - parent of amending changeset
2495
2502
2496 # Update extra dict from amended commit (e.g. to preserve graft
2503 # Update extra dict from amended commit (e.g. to preserve graft
2497 # source)
2504 # source)
2498 extra.update(old.extra())
2505 extra.update(old.extra())
2499
2506
2500 # Also update it from the intermediate commit or from the wctx
2507 # Also update it from the intermediate commit or from the wctx
2501 extra.update(ctx.extra())
2508 extra.update(ctx.extra())
2502
2509
2503 if len(old.parents()) > 1:
2510 if len(old.parents()) > 1:
2504 # ctx.files() isn't reliable for merges, so fall back to the
2511 # ctx.files() isn't reliable for merges, so fall back to the
2505 # slower repo.status() method
2512 # slower repo.status() method
2506 files = set([fn for st in repo.status(base, old)[:3]
2513 files = set([fn for st in repo.status(base, old)[:3]
2507 for fn in st])
2514 for fn in st])
2508 else:
2515 else:
2509 files = set(old.files())
2516 files = set(old.files())
2510
2517
2511 # Second, we use either the commit we just did, or if there were no
2518 # Second, we use either the commit we just did, or if there were no
2512 # changes the parent of the working directory as the version of the
2519 # changes the parent of the working directory as the version of the
2513 # files in the final amend commit
2520 # files in the final amend commit
2514 if node:
2521 if node:
2515 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2522 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2516
2523
2517 user = ctx.user()
2524 user = ctx.user()
2518 date = ctx.date()
2525 date = ctx.date()
2519 # Recompute copies (avoid recording a -> b -> a)
2526 # Recompute copies (avoid recording a -> b -> a)
2520 copied = copies.pathcopies(base, ctx)
2527 copied = copies.pathcopies(base, ctx)
2521 if old.p2:
2528 if old.p2:
2522 copied.update(copies.pathcopies(old.p2(), ctx))
2529 copied.update(copies.pathcopies(old.p2(), ctx))
2523
2530
2524 # Prune files which were reverted by the updates: if old
2531 # Prune files which were reverted by the updates: if old
2525 # introduced file X and our intermediate commit, node,
2532 # introduced file X and our intermediate commit, node,
2526 # renamed that file, then those two files are the same and
2533 # renamed that file, then those two files are the same and
2527 # we can discard X from our list of files. Likewise if X
2534 # we can discard X from our list of files. Likewise if X
2528 # was deleted, it's no longer relevant
2535 # was deleted, it's no longer relevant
2529 files.update(ctx.files())
2536 files.update(ctx.files())
2530
2537
2531 def samefile(f):
2538 def samefile(f):
2532 if f in ctx.manifest():
2539 if f in ctx.manifest():
2533 a = ctx.filectx(f)
2540 a = ctx.filectx(f)
2534 if f in base.manifest():
2541 if f in base.manifest():
2535 b = base.filectx(f)
2542 b = base.filectx(f)
2536 return (not a.cmp(b)
2543 return (not a.cmp(b)
2537 and a.flags() == b.flags())
2544 and a.flags() == b.flags())
2538 else:
2545 else:
2539 return False
2546 return False
2540 else:
2547 else:
2541 return f not in base.manifest()
2548 return f not in base.manifest()
2542 files = [f for f in files if not samefile(f)]
2549 files = [f for f in files if not samefile(f)]
2543
2550
2544 def filectxfn(repo, ctx_, path):
2551 def filectxfn(repo, ctx_, path):
2545 try:
2552 try:
2546 fctx = ctx[path]
2553 fctx = ctx[path]
2547 flags = fctx.flags()
2554 flags = fctx.flags()
2548 mctx = context.memfilectx(repo,
2555 mctx = context.memfilectx(repo,
2549 fctx.path(), fctx.data(),
2556 fctx.path(), fctx.data(),
2550 islink='l' in flags,
2557 islink='l' in flags,
2551 isexec='x' in flags,
2558 isexec='x' in flags,
2552 copied=copied.get(path))
2559 copied=copied.get(path))
2553 return mctx
2560 return mctx
2554 except KeyError:
2561 except KeyError:
2555 return None
2562 return None
2556 else:
2563 else:
2557 ui.note(_('copying changeset %s to %s\n') % (old, base))
2564 ui.note(_('copying changeset %s to %s\n') % (old, base))
2558
2565
2559 # Use version of files as in the old cset
2566 # Use version of files as in the old cset
2560 def filectxfn(repo, ctx_, path):
2567 def filectxfn(repo, ctx_, path):
2561 try:
2568 try:
2562 return old.filectx(path)
2569 return old.filectx(path)
2563 except KeyError:
2570 except KeyError:
2564 return None
2571 return None
2565
2572
2566 user = opts.get('user') or old.user()
2573 user = opts.get('user') or old.user()
2567 date = opts.get('date') or old.date()
2574 date = opts.get('date') or old.date()
2568 editform = mergeeditform(old, 'commit.amend')
2575 editform = mergeeditform(old, 'commit.amend')
2569 editor = getcommiteditor(editform=editform, **opts)
2576 editor = getcommiteditor(editform=editform, **opts)
2570 if not message:
2577 if not message:
2571 editor = getcommiteditor(edit=True, editform=editform)
2578 editor = getcommiteditor(edit=True, editform=editform)
2572 message = old.description()
2579 message = old.description()
2573
2580
2574 pureextra = extra.copy()
2581 pureextra = extra.copy()
2575 extra['amend_source'] = old.hex()
2582 extra['amend_source'] = old.hex()
2576
2583
2577 new = context.memctx(repo,
2584 new = context.memctx(repo,
2578 parents=[base.node(), old.p2().node()],
2585 parents=[base.node(), old.p2().node()],
2579 text=message,
2586 text=message,
2580 files=files,
2587 files=files,
2581 filectxfn=filectxfn,
2588 filectxfn=filectxfn,
2582 user=user,
2589 user=user,
2583 date=date,
2590 date=date,
2584 extra=extra,
2591 extra=extra,
2585 editor=editor)
2592 editor=editor)
2586
2593
2587 newdesc = changelog.stripdesc(new.description())
2594 newdesc = changelog.stripdesc(new.description())
2588 if ((not node)
2595 if ((not node)
2589 and newdesc == old.description()
2596 and newdesc == old.description()
2590 and user == old.user()
2597 and user == old.user()
2591 and date == old.date()
2598 and date == old.date()
2592 and pureextra == old.extra()):
2599 and pureextra == old.extra()):
2593 # nothing changed. continuing here would create a new node
2600 # nothing changed. continuing here would create a new node
2594 # anyway because of the amend_source noise.
2601 # anyway because of the amend_source noise.
2595 #
2602 #
2596 # This not what we expect from amend.
2603 # This not what we expect from amend.
2597 return old.node()
2604 return old.node()
2598
2605
2599 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2606 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2600 try:
2607 try:
2601 if opts.get('secret'):
2608 if opts.get('secret'):
2602 commitphase = 'secret'
2609 commitphase = 'secret'
2603 else:
2610 else:
2604 commitphase = old.phase()
2611 commitphase = old.phase()
2605 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2612 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2606 newid = repo.commitctx(new)
2613 newid = repo.commitctx(new)
2607 finally:
2614 finally:
2608 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2615 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2609 if newid != old.node():
2616 if newid != old.node():
2610 # Reroute the working copy parent to the new changeset
2617 # Reroute the working copy parent to the new changeset
2611 repo.setparents(newid, nullid)
2618 repo.setparents(newid, nullid)
2612
2619
2613 # Move bookmarks from old parent to amend commit
2620 # Move bookmarks from old parent to amend commit
2614 bms = repo.nodebookmarks(old.node())
2621 bms = repo.nodebookmarks(old.node())
2615 if bms:
2622 if bms:
2616 marks = repo._bookmarks
2623 marks = repo._bookmarks
2617 for bm in bms:
2624 for bm in bms:
2618 ui.debug('moving bookmarks %r from %s to %s\n' %
2625 ui.debug('moving bookmarks %r from %s to %s\n' %
2619 (marks, old.hex(), hex(newid)))
2626 (marks, old.hex(), hex(newid)))
2620 marks[bm] = newid
2627 marks[bm] = newid
2621 marks.recordchange(tr)
2628 marks.recordchange(tr)
2622 #commit the whole amend process
2629 #commit the whole amend process
2623 if createmarkers:
2630 if createmarkers:
2624 # mark the new changeset as successor of the rewritten one
2631 # mark the new changeset as successor of the rewritten one
2625 new = repo[newid]
2632 new = repo[newid]
2626 obs = [(old, (new,))]
2633 obs = [(old, (new,))]
2627 if node:
2634 if node:
2628 obs.append((ctx, ()))
2635 obs.append((ctx, ()))
2629
2636
2630 obsolete.createmarkers(repo, obs)
2637 obsolete.createmarkers(repo, obs)
2631 tr.close()
2638 tr.close()
2632 finally:
2639 finally:
2633 tr.release()
2640 tr.release()
2634 dsguard.close()
2641 dsguard.close()
2635 if not createmarkers and newid != old.node():
2642 if not createmarkers and newid != old.node():
2636 # Strip the intermediate commit (if there was one) and the amended
2643 # Strip the intermediate commit (if there was one) and the amended
2637 # commit
2644 # commit
2638 if node:
2645 if node:
2639 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2646 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2640 ui.note(_('stripping amended changeset %s\n') % old)
2647 ui.note(_('stripping amended changeset %s\n') % old)
2641 repair.strip(ui, repo, old.node(), topic='amend-backup')
2648 repair.strip(ui, repo, old.node(), topic='amend-backup')
2642 finally:
2649 finally:
2643 lockmod.release(lock, dsguard, wlock)
2650 lockmod.release(lock, dsguard, wlock)
2644 return newid
2651 return newid
2645
2652
2646 def commiteditor(repo, ctx, subs, editform=''):
2653 def commiteditor(repo, ctx, subs, editform=''):
2647 if ctx.description():
2654 if ctx.description():
2648 return ctx.description()
2655 return ctx.description()
2649 return commitforceeditor(repo, ctx, subs, editform=editform)
2656 return commitforceeditor(repo, ctx, subs, editform=editform)
2650
2657
2651 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2658 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2652 editform=''):
2659 editform=''):
2653 if not extramsg:
2660 if not extramsg:
2654 extramsg = _("Leave message empty to abort commit.")
2661 extramsg = _("Leave message empty to abort commit.")
2655
2662
2656 forms = [e for e in editform.split('.') if e]
2663 forms = [e for e in editform.split('.') if e]
2657 forms.insert(0, 'changeset')
2664 forms.insert(0, 'changeset')
2658 while forms:
2665 while forms:
2659 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2666 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2660 if tmpl:
2667 if tmpl:
2661 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2668 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2662 break
2669 break
2663 forms.pop()
2670 forms.pop()
2664 else:
2671 else:
2665 committext = buildcommittext(repo, ctx, subs, extramsg)
2672 committext = buildcommittext(repo, ctx, subs, extramsg)
2666
2673
2667 # run editor in the repository root
2674 # run editor in the repository root
2668 olddir = os.getcwd()
2675 olddir = os.getcwd()
2669 os.chdir(repo.root)
2676 os.chdir(repo.root)
2670 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2677 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2671 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2678 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2672 os.chdir(olddir)
2679 os.chdir(olddir)
2673
2680
2674 if finishdesc:
2681 if finishdesc:
2675 text = finishdesc(text)
2682 text = finishdesc(text)
2676 if not text.strip():
2683 if not text.strip():
2677 raise util.Abort(_("empty commit message"))
2684 raise util.Abort(_("empty commit message"))
2678
2685
2679 return text
2686 return text
2680
2687
2681 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2688 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2682 ui = repo.ui
2689 ui = repo.ui
2683 tmpl, mapfile = gettemplate(ui, tmpl, None)
2690 tmpl, mapfile = gettemplate(ui, tmpl, None)
2684
2691
2685 try:
2692 try:
2686 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2693 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2687 except SyntaxError as inst:
2694 except SyntaxError as inst:
2688 raise util.Abort(inst.args[0])
2695 raise util.Abort(inst.args[0])
2689
2696
2690 for k, v in repo.ui.configitems('committemplate'):
2697 for k, v in repo.ui.configitems('committemplate'):
2691 if k != 'changeset':
2698 if k != 'changeset':
2692 t.t.cache[k] = v
2699 t.t.cache[k] = v
2693
2700
2694 if not extramsg:
2701 if not extramsg:
2695 extramsg = '' # ensure that extramsg is string
2702 extramsg = '' # ensure that extramsg is string
2696
2703
2697 ui.pushbuffer()
2704 ui.pushbuffer()
2698 t.show(ctx, extramsg=extramsg)
2705 t.show(ctx, extramsg=extramsg)
2699 return ui.popbuffer()
2706 return ui.popbuffer()
2700
2707
2701 def buildcommittext(repo, ctx, subs, extramsg):
2708 def buildcommittext(repo, ctx, subs, extramsg):
2702 edittext = []
2709 edittext = []
2703 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2710 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2704 if ctx.description():
2711 if ctx.description():
2705 edittext.append(ctx.description())
2712 edittext.append(ctx.description())
2706 edittext.append("")
2713 edittext.append("")
2707 edittext.append("") # Empty line between message and comments.
2714 edittext.append("") # Empty line between message and comments.
2708 edittext.append(_("HG: Enter commit message."
2715 edittext.append(_("HG: Enter commit message."
2709 " Lines beginning with 'HG:' are removed."))
2716 " Lines beginning with 'HG:' are removed."))
2710 edittext.append("HG: %s" % extramsg)
2717 edittext.append("HG: %s" % extramsg)
2711 edittext.append("HG: --")
2718 edittext.append("HG: --")
2712 edittext.append(_("HG: user: %s") % ctx.user())
2719 edittext.append(_("HG: user: %s") % ctx.user())
2713 if ctx.p2():
2720 if ctx.p2():
2714 edittext.append(_("HG: branch merge"))
2721 edittext.append(_("HG: branch merge"))
2715 if ctx.branch():
2722 if ctx.branch():
2716 edittext.append(_("HG: branch '%s'") % ctx.branch())
2723 edittext.append(_("HG: branch '%s'") % ctx.branch())
2717 if bookmarks.isactivewdirparent(repo):
2724 if bookmarks.isactivewdirparent(repo):
2718 edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
2725 edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
2719 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2726 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2720 edittext.extend([_("HG: added %s") % f for f in added])
2727 edittext.extend([_("HG: added %s") % f for f in added])
2721 edittext.extend([_("HG: changed %s") % f for f in modified])
2728 edittext.extend([_("HG: changed %s") % f for f in modified])
2722 edittext.extend([_("HG: removed %s") % f for f in removed])
2729 edittext.extend([_("HG: removed %s") % f for f in removed])
2723 if not added and not modified and not removed:
2730 if not added and not modified and not removed:
2724 edittext.append(_("HG: no files changed"))
2731 edittext.append(_("HG: no files changed"))
2725 edittext.append("")
2732 edittext.append("")
2726
2733
2727 return "\n".join(edittext)
2734 return "\n".join(edittext)
2728
2735
2729 def commitstatus(repo, node, branch, bheads=None, opts={}):
2736 def commitstatus(repo, node, branch, bheads=None, opts={}):
2730 ctx = repo[node]
2737 ctx = repo[node]
2731 parents = ctx.parents()
2738 parents = ctx.parents()
2732
2739
2733 if (not opts.get('amend') and bheads and node not in bheads and not
2740 if (not opts.get('amend') and bheads and node not in bheads and not
2734 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2741 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2735 repo.ui.status(_('created new head\n'))
2742 repo.ui.status(_('created new head\n'))
2736 # The message is not printed for initial roots. For the other
2743 # The message is not printed for initial roots. For the other
2737 # changesets, it is printed in the following situations:
2744 # changesets, it is printed in the following situations:
2738 #
2745 #
2739 # Par column: for the 2 parents with ...
2746 # Par column: for the 2 parents with ...
2740 # N: null or no parent
2747 # N: null or no parent
2741 # B: parent is on another named branch
2748 # B: parent is on another named branch
2742 # C: parent is a regular non head changeset
2749 # C: parent is a regular non head changeset
2743 # H: parent was a branch head of the current branch
2750 # H: parent was a branch head of the current branch
2744 # Msg column: whether we print "created new head" message
2751 # Msg column: whether we print "created new head" message
2745 # In the following, it is assumed that there already exists some
2752 # In the following, it is assumed that there already exists some
2746 # initial branch heads of the current branch, otherwise nothing is
2753 # initial branch heads of the current branch, otherwise nothing is
2747 # printed anyway.
2754 # printed anyway.
2748 #
2755 #
2749 # Par Msg Comment
2756 # Par Msg Comment
2750 # N N y additional topo root
2757 # N N y additional topo root
2751 #
2758 #
2752 # B N y additional branch root
2759 # B N y additional branch root
2753 # C N y additional topo head
2760 # C N y additional topo head
2754 # H N n usual case
2761 # H N n usual case
2755 #
2762 #
2756 # B B y weird additional branch root
2763 # B B y weird additional branch root
2757 # C B y branch merge
2764 # C B y branch merge
2758 # H B n merge with named branch
2765 # H B n merge with named branch
2759 #
2766 #
2760 # C C y additional head from merge
2767 # C C y additional head from merge
2761 # C H n merge with a head
2768 # C H n merge with a head
2762 #
2769 #
2763 # H H n head merge: head count decreases
2770 # H H n head merge: head count decreases
2764
2771
2765 if not opts.get('close_branch'):
2772 if not opts.get('close_branch'):
2766 for r in parents:
2773 for r in parents:
2767 if r.closesbranch() and r.branch() == branch:
2774 if r.closesbranch() and r.branch() == branch:
2768 repo.ui.status(_('reopening closed branch head %d\n') % r)
2775 repo.ui.status(_('reopening closed branch head %d\n') % r)
2769
2776
2770 if repo.ui.debugflag:
2777 if repo.ui.debugflag:
2771 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2778 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2772 elif repo.ui.verbose:
2779 elif repo.ui.verbose:
2773 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2780 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2774
2781
2775 def revert(ui, repo, ctx, parents, *pats, **opts):
2782 def revert(ui, repo, ctx, parents, *pats, **opts):
2776 parent, p2 = parents
2783 parent, p2 = parents
2777 node = ctx.node()
2784 node = ctx.node()
2778
2785
2779 mf = ctx.manifest()
2786 mf = ctx.manifest()
2780 if node == p2:
2787 if node == p2:
2781 parent = p2
2788 parent = p2
2782 if node == parent:
2789 if node == parent:
2783 pmf = mf
2790 pmf = mf
2784 else:
2791 else:
2785 pmf = None
2792 pmf = None
2786
2793
2787 # need all matching names in dirstate and manifest of target rev,
2794 # need all matching names in dirstate and manifest of target rev,
2788 # so have to walk both. do not print errors if files exist in one
2795 # so have to walk both. do not print errors if files exist in one
2789 # but not other. in both cases, filesets should be evaluated against
2796 # but not other. in both cases, filesets should be evaluated against
2790 # workingctx to get consistent result (issue4497). this means 'set:**'
2797 # workingctx to get consistent result (issue4497). this means 'set:**'
2791 # cannot be used to select missing files from target rev.
2798 # cannot be used to select missing files from target rev.
2792
2799
2793 # `names` is a mapping for all elements in working copy and target revision
2800 # `names` is a mapping for all elements in working copy and target revision
2794 # The mapping is in the form:
2801 # The mapping is in the form:
2795 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2802 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2796 names = {}
2803 names = {}
2797
2804
2798 wlock = repo.wlock()
2805 wlock = repo.wlock()
2799 try:
2806 try:
2800 ## filling of the `names` mapping
2807 ## filling of the `names` mapping
2801 # walk dirstate to fill `names`
2808 # walk dirstate to fill `names`
2802
2809
2803 interactive = opts.get('interactive', False)
2810 interactive = opts.get('interactive', False)
2804 wctx = repo[None]
2811 wctx = repo[None]
2805 m = scmutil.match(wctx, pats, opts)
2812 m = scmutil.match(wctx, pats, opts)
2806
2813
2807 # we'll need this later
2814 # we'll need this later
2808 targetsubs = sorted(s for s in wctx.substate if m(s))
2815 targetsubs = sorted(s for s in wctx.substate if m(s))
2809
2816
2810 if not m.always():
2817 if not m.always():
2811 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2818 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2812 names[abs] = m.rel(abs), m.exact(abs)
2819 names[abs] = m.rel(abs), m.exact(abs)
2813
2820
2814 # walk target manifest to fill `names`
2821 # walk target manifest to fill `names`
2815
2822
2816 def badfn(path, msg):
2823 def badfn(path, msg):
2817 if path in names:
2824 if path in names:
2818 return
2825 return
2819 if path in ctx.substate:
2826 if path in ctx.substate:
2820 return
2827 return
2821 path_ = path + '/'
2828 path_ = path + '/'
2822 for f in names:
2829 for f in names:
2823 if f.startswith(path_):
2830 if f.startswith(path_):
2824 return
2831 return
2825 ui.warn("%s: %s\n" % (m.rel(path), msg))
2832 ui.warn("%s: %s\n" % (m.rel(path), msg))
2826
2833
2827 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2834 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2828 if abs not in names:
2835 if abs not in names:
2829 names[abs] = m.rel(abs), m.exact(abs)
2836 names[abs] = m.rel(abs), m.exact(abs)
2830
2837
2831 # Find status of all file in `names`.
2838 # Find status of all file in `names`.
2832 m = scmutil.matchfiles(repo, names)
2839 m = scmutil.matchfiles(repo, names)
2833
2840
2834 changes = repo.status(node1=node, match=m,
2841 changes = repo.status(node1=node, match=m,
2835 unknown=True, ignored=True, clean=True)
2842 unknown=True, ignored=True, clean=True)
2836 else:
2843 else:
2837 changes = repo.status(node1=node, match=m)
2844 changes = repo.status(node1=node, match=m)
2838 for kind in changes:
2845 for kind in changes:
2839 for abs in kind:
2846 for abs in kind:
2840 names[abs] = m.rel(abs), m.exact(abs)
2847 names[abs] = m.rel(abs), m.exact(abs)
2841
2848
2842 m = scmutil.matchfiles(repo, names)
2849 m = scmutil.matchfiles(repo, names)
2843
2850
2844 modified = set(changes.modified)
2851 modified = set(changes.modified)
2845 added = set(changes.added)
2852 added = set(changes.added)
2846 removed = set(changes.removed)
2853 removed = set(changes.removed)
2847 _deleted = set(changes.deleted)
2854 _deleted = set(changes.deleted)
2848 unknown = set(changes.unknown)
2855 unknown = set(changes.unknown)
2849 unknown.update(changes.ignored)
2856 unknown.update(changes.ignored)
2850 clean = set(changes.clean)
2857 clean = set(changes.clean)
2851 modadded = set()
2858 modadded = set()
2852
2859
2853 # split between files known in target manifest and the others
2860 # split between files known in target manifest and the others
2854 smf = set(mf)
2861 smf = set(mf)
2855
2862
2856 # determine the exact nature of the deleted changesets
2863 # determine the exact nature of the deleted changesets
2857 deladded = _deleted - smf
2864 deladded = _deleted - smf
2858 deleted = _deleted - deladded
2865 deleted = _deleted - deladded
2859
2866
2860 # We need to account for the state of the file in the dirstate,
2867 # We need to account for the state of the file in the dirstate,
2861 # even when we revert against something else than parent. This will
2868 # even when we revert against something else than parent. This will
2862 # slightly alter the behavior of revert (doing back up or not, delete
2869 # slightly alter the behavior of revert (doing back up or not, delete
2863 # or just forget etc).
2870 # or just forget etc).
2864 if parent == node:
2871 if parent == node:
2865 dsmodified = modified
2872 dsmodified = modified
2866 dsadded = added
2873 dsadded = added
2867 dsremoved = removed
2874 dsremoved = removed
2868 # store all local modifications, useful later for rename detection
2875 # store all local modifications, useful later for rename detection
2869 localchanges = dsmodified | dsadded
2876 localchanges = dsmodified | dsadded
2870 modified, added, removed = set(), set(), set()
2877 modified, added, removed = set(), set(), set()
2871 else:
2878 else:
2872 changes = repo.status(node1=parent, match=m)
2879 changes = repo.status(node1=parent, match=m)
2873 dsmodified = set(changes.modified)
2880 dsmodified = set(changes.modified)
2874 dsadded = set(changes.added)
2881 dsadded = set(changes.added)
2875 dsremoved = set(changes.removed)
2882 dsremoved = set(changes.removed)
2876 # store all local modifications, useful later for rename detection
2883 # store all local modifications, useful later for rename detection
2877 localchanges = dsmodified | dsadded
2884 localchanges = dsmodified | dsadded
2878
2885
2879 # only take into account for removes between wc and target
2886 # only take into account for removes between wc and target
2880 clean |= dsremoved - removed
2887 clean |= dsremoved - removed
2881 dsremoved &= removed
2888 dsremoved &= removed
2882 # distinct between dirstate remove and other
2889 # distinct between dirstate remove and other
2883 removed -= dsremoved
2890 removed -= dsremoved
2884
2891
2885 modadded = added & dsmodified
2892 modadded = added & dsmodified
2886 added -= modadded
2893 added -= modadded
2887
2894
2888 # tell newly modified apart.
2895 # tell newly modified apart.
2889 dsmodified &= modified
2896 dsmodified &= modified
2890 dsmodified |= modified & dsadded # dirstate added may needs backup
2897 dsmodified |= modified & dsadded # dirstate added may needs backup
2891 modified -= dsmodified
2898 modified -= dsmodified
2892
2899
2893 # We need to wait for some post-processing to update this set
2900 # We need to wait for some post-processing to update this set
2894 # before making the distinction. The dirstate will be used for
2901 # before making the distinction. The dirstate will be used for
2895 # that purpose.
2902 # that purpose.
2896 dsadded = added
2903 dsadded = added
2897
2904
2898 # in case of merge, files that are actually added can be reported as
2905 # in case of merge, files that are actually added can be reported as
2899 # modified, we need to post process the result
2906 # modified, we need to post process the result
2900 if p2 != nullid:
2907 if p2 != nullid:
2901 if pmf is None:
2908 if pmf is None:
2902 # only need parent manifest in the merge case,
2909 # only need parent manifest in the merge case,
2903 # so do not read by default
2910 # so do not read by default
2904 pmf = repo[parent].manifest()
2911 pmf = repo[parent].manifest()
2905 mergeadd = dsmodified - set(pmf)
2912 mergeadd = dsmodified - set(pmf)
2906 dsadded |= mergeadd
2913 dsadded |= mergeadd
2907 dsmodified -= mergeadd
2914 dsmodified -= mergeadd
2908
2915
2909 # if f is a rename, update `names` to also revert the source
2916 # if f is a rename, update `names` to also revert the source
2910 cwd = repo.getcwd()
2917 cwd = repo.getcwd()
2911 for f in localchanges:
2918 for f in localchanges:
2912 src = repo.dirstate.copied(f)
2919 src = repo.dirstate.copied(f)
2913 # XXX should we check for rename down to target node?
2920 # XXX should we check for rename down to target node?
2914 if src and src not in names and repo.dirstate[src] == 'r':
2921 if src and src not in names and repo.dirstate[src] == 'r':
2915 dsremoved.add(src)
2922 dsremoved.add(src)
2916 names[src] = (repo.pathto(src, cwd), True)
2923 names[src] = (repo.pathto(src, cwd), True)
2917
2924
2918 # distinguish between file to forget and the other
2925 # distinguish between file to forget and the other
2919 added = set()
2926 added = set()
2920 for abs in dsadded:
2927 for abs in dsadded:
2921 if repo.dirstate[abs] != 'a':
2928 if repo.dirstate[abs] != 'a':
2922 added.add(abs)
2929 added.add(abs)
2923 dsadded -= added
2930 dsadded -= added
2924
2931
2925 for abs in deladded:
2932 for abs in deladded:
2926 if repo.dirstate[abs] == 'a':
2933 if repo.dirstate[abs] == 'a':
2927 dsadded.add(abs)
2934 dsadded.add(abs)
2928 deladded -= dsadded
2935 deladded -= dsadded
2929
2936
2930 # For files marked as removed, we check if an unknown file is present at
2937 # For files marked as removed, we check if an unknown file is present at
2931 # the same path. If a such file exists it may need to be backed up.
2938 # the same path. If a such file exists it may need to be backed up.
2932 # Making the distinction at this stage helps have simpler backup
2939 # Making the distinction at this stage helps have simpler backup
2933 # logic.
2940 # logic.
2934 removunk = set()
2941 removunk = set()
2935 for abs in removed:
2942 for abs in removed:
2936 target = repo.wjoin(abs)
2943 target = repo.wjoin(abs)
2937 if os.path.lexists(target):
2944 if os.path.lexists(target):
2938 removunk.add(abs)
2945 removunk.add(abs)
2939 removed -= removunk
2946 removed -= removunk
2940
2947
2941 dsremovunk = set()
2948 dsremovunk = set()
2942 for abs in dsremoved:
2949 for abs in dsremoved:
2943 target = repo.wjoin(abs)
2950 target = repo.wjoin(abs)
2944 if os.path.lexists(target):
2951 if os.path.lexists(target):
2945 dsremovunk.add(abs)
2952 dsremovunk.add(abs)
2946 dsremoved -= dsremovunk
2953 dsremoved -= dsremovunk
2947
2954
2948 # action to be actually performed by revert
2955 # action to be actually performed by revert
2949 # (<list of file>, message>) tuple
2956 # (<list of file>, message>) tuple
2950 actions = {'revert': ([], _('reverting %s\n')),
2957 actions = {'revert': ([], _('reverting %s\n')),
2951 'add': ([], _('adding %s\n')),
2958 'add': ([], _('adding %s\n')),
2952 'remove': ([], _('removing %s\n')),
2959 'remove': ([], _('removing %s\n')),
2953 'drop': ([], _('removing %s\n')),
2960 'drop': ([], _('removing %s\n')),
2954 'forget': ([], _('forgetting %s\n')),
2961 'forget': ([], _('forgetting %s\n')),
2955 'undelete': ([], _('undeleting %s\n')),
2962 'undelete': ([], _('undeleting %s\n')),
2956 'noop': (None, _('no changes needed to %s\n')),
2963 'noop': (None, _('no changes needed to %s\n')),
2957 'unknown': (None, _('file not managed: %s\n')),
2964 'unknown': (None, _('file not managed: %s\n')),
2958 }
2965 }
2959
2966
2960 # "constant" that convey the backup strategy.
2967 # "constant" that convey the backup strategy.
2961 # All set to `discard` if `no-backup` is set do avoid checking
2968 # All set to `discard` if `no-backup` is set do avoid checking
2962 # no_backup lower in the code.
2969 # no_backup lower in the code.
2963 # These values are ordered for comparison purposes
2970 # These values are ordered for comparison purposes
2964 backup = 2 # unconditionally do backup
2971 backup = 2 # unconditionally do backup
2965 check = 1 # check if the existing file differs from target
2972 check = 1 # check if the existing file differs from target
2966 discard = 0 # never do backup
2973 discard = 0 # never do backup
2967 if opts.get('no_backup'):
2974 if opts.get('no_backup'):
2968 backup = check = discard
2975 backup = check = discard
2969
2976
2970 backupanddel = actions['remove']
2977 backupanddel = actions['remove']
2971 if not opts.get('no_backup'):
2978 if not opts.get('no_backup'):
2972 backupanddel = actions['drop']
2979 backupanddel = actions['drop']
2973
2980
2974 disptable = (
2981 disptable = (
2975 # dispatch table:
2982 # dispatch table:
2976 # file state
2983 # file state
2977 # action
2984 # action
2978 # make backup
2985 # make backup
2979
2986
2980 ## Sets that results that will change file on disk
2987 ## Sets that results that will change file on disk
2981 # Modified compared to target, no local change
2988 # Modified compared to target, no local change
2982 (modified, actions['revert'], discard),
2989 (modified, actions['revert'], discard),
2983 # Modified compared to target, but local file is deleted
2990 # Modified compared to target, but local file is deleted
2984 (deleted, actions['revert'], discard),
2991 (deleted, actions['revert'], discard),
2985 # Modified compared to target, local change
2992 # Modified compared to target, local change
2986 (dsmodified, actions['revert'], backup),
2993 (dsmodified, actions['revert'], backup),
2987 # Added since target
2994 # Added since target
2988 (added, actions['remove'], discard),
2995 (added, actions['remove'], discard),
2989 # Added in working directory
2996 # Added in working directory
2990 (dsadded, actions['forget'], discard),
2997 (dsadded, actions['forget'], discard),
2991 # Added since target, have local modification
2998 # Added since target, have local modification
2992 (modadded, backupanddel, backup),
2999 (modadded, backupanddel, backup),
2993 # Added since target but file is missing in working directory
3000 # Added since target but file is missing in working directory
2994 (deladded, actions['drop'], discard),
3001 (deladded, actions['drop'], discard),
2995 # Removed since target, before working copy parent
3002 # Removed since target, before working copy parent
2996 (removed, actions['add'], discard),
3003 (removed, actions['add'], discard),
2997 # Same as `removed` but an unknown file exists at the same path
3004 # Same as `removed` but an unknown file exists at the same path
2998 (removunk, actions['add'], check),
3005 (removunk, actions['add'], check),
2999 # Removed since targe, marked as such in working copy parent
3006 # Removed since targe, marked as such in working copy parent
3000 (dsremoved, actions['undelete'], discard),
3007 (dsremoved, actions['undelete'], discard),
3001 # Same as `dsremoved` but an unknown file exists at the same path
3008 # Same as `dsremoved` but an unknown file exists at the same path
3002 (dsremovunk, actions['undelete'], check),
3009 (dsremovunk, actions['undelete'], check),
3003 ## the following sets does not result in any file changes
3010 ## the following sets does not result in any file changes
3004 # File with no modification
3011 # File with no modification
3005 (clean, actions['noop'], discard),
3012 (clean, actions['noop'], discard),
3006 # Existing file, not tracked anywhere
3013 # Existing file, not tracked anywhere
3007 (unknown, actions['unknown'], discard),
3014 (unknown, actions['unknown'], discard),
3008 )
3015 )
3009
3016
3010 for abs, (rel, exact) in sorted(names.items()):
3017 for abs, (rel, exact) in sorted(names.items()):
3011 # target file to be touch on disk (relative to cwd)
3018 # target file to be touch on disk (relative to cwd)
3012 target = repo.wjoin(abs)
3019 target = repo.wjoin(abs)
3013 # search the entry in the dispatch table.
3020 # search the entry in the dispatch table.
3014 # if the file is in any of these sets, it was touched in the working
3021 # if the file is in any of these sets, it was touched in the working
3015 # directory parent and we are sure it needs to be reverted.
3022 # directory parent and we are sure it needs to be reverted.
3016 for table, (xlist, msg), dobackup in disptable:
3023 for table, (xlist, msg), dobackup in disptable:
3017 if abs not in table:
3024 if abs not in table:
3018 continue
3025 continue
3019 if xlist is not None:
3026 if xlist is not None:
3020 xlist.append(abs)
3027 xlist.append(abs)
3021 if dobackup and (backup <= dobackup
3028 if dobackup and (backup <= dobackup
3022 or wctx[abs].cmp(ctx[abs])):
3029 or wctx[abs].cmp(ctx[abs])):
3023 bakname = "%s.orig" % rel
3030 bakname = "%s.orig" % rel
3024 ui.note(_('saving current version of %s as %s\n') %
3031 ui.note(_('saving current version of %s as %s\n') %
3025 (rel, bakname))
3032 (rel, bakname))
3026 if not opts.get('dry_run'):
3033 if not opts.get('dry_run'):
3027 if interactive:
3034 if interactive:
3028 util.copyfile(target, bakname)
3035 util.copyfile(target, bakname)
3029 else:
3036 else:
3030 util.rename(target, bakname)
3037 util.rename(target, bakname)
3031 if ui.verbose or not exact:
3038 if ui.verbose or not exact:
3032 if not isinstance(msg, basestring):
3039 if not isinstance(msg, basestring):
3033 msg = msg(abs)
3040 msg = msg(abs)
3034 ui.status(msg % rel)
3041 ui.status(msg % rel)
3035 elif exact:
3042 elif exact:
3036 ui.warn(msg % rel)
3043 ui.warn(msg % rel)
3037 break
3044 break
3038
3045
3039 if not opts.get('dry_run'):
3046 if not opts.get('dry_run'):
3040 needdata = ('revert', 'add', 'undelete')
3047 needdata = ('revert', 'add', 'undelete')
3041 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3048 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3042 _performrevert(repo, parents, ctx, actions, interactive)
3049 _performrevert(repo, parents, ctx, actions, interactive)
3043
3050
3044 if targetsubs:
3051 if targetsubs:
3045 # Revert the subrepos on the revert list
3052 # Revert the subrepos on the revert list
3046 for sub in targetsubs:
3053 for sub in targetsubs:
3047 try:
3054 try:
3048 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3055 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3049 except KeyError:
3056 except KeyError:
3050 raise util.Abort("subrepository '%s' does not exist in %s!"
3057 raise util.Abort("subrepository '%s' does not exist in %s!"
3051 % (sub, short(ctx.node())))
3058 % (sub, short(ctx.node())))
3052 finally:
3059 finally:
3053 wlock.release()
3060 wlock.release()
3054
3061
3055 def _revertprefetch(repo, ctx, *files):
3062 def _revertprefetch(repo, ctx, *files):
3056 """Let extension changing the storage layer prefetch content"""
3063 """Let extension changing the storage layer prefetch content"""
3057 pass
3064 pass
3058
3065
3059 def _performrevert(repo, parents, ctx, actions, interactive=False):
3066 def _performrevert(repo, parents, ctx, actions, interactive=False):
3060 """function that actually perform all the actions computed for revert
3067 """function that actually perform all the actions computed for revert
3061
3068
3062 This is an independent function to let extension to plug in and react to
3069 This is an independent function to let extension to plug in and react to
3063 the imminent revert.
3070 the imminent revert.
3064
3071
3065 Make sure you have the working directory locked when calling this function.
3072 Make sure you have the working directory locked when calling this function.
3066 """
3073 """
3067 parent, p2 = parents
3074 parent, p2 = parents
3068 node = ctx.node()
3075 node = ctx.node()
3069 def checkout(f):
3076 def checkout(f):
3070 fc = ctx[f]
3077 fc = ctx[f]
3071 repo.wwrite(f, fc.data(), fc.flags())
3078 repo.wwrite(f, fc.data(), fc.flags())
3072
3079
3073 audit_path = pathutil.pathauditor(repo.root)
3080 audit_path = pathutil.pathauditor(repo.root)
3074 for f in actions['forget'][0]:
3081 for f in actions['forget'][0]:
3075 repo.dirstate.drop(f)
3082 repo.dirstate.drop(f)
3076 for f in actions['remove'][0]:
3083 for f in actions['remove'][0]:
3077 audit_path(f)
3084 audit_path(f)
3078 try:
3085 try:
3079 util.unlinkpath(repo.wjoin(f))
3086 util.unlinkpath(repo.wjoin(f))
3080 except OSError:
3087 except OSError:
3081 pass
3088 pass
3082 repo.dirstate.remove(f)
3089 repo.dirstate.remove(f)
3083 for f in actions['drop'][0]:
3090 for f in actions['drop'][0]:
3084 audit_path(f)
3091 audit_path(f)
3085 repo.dirstate.remove(f)
3092 repo.dirstate.remove(f)
3086
3093
3087 normal = None
3094 normal = None
3088 if node == parent:
3095 if node == parent:
3089 # We're reverting to our parent. If possible, we'd like status
3096 # We're reverting to our parent. If possible, we'd like status
3090 # to report the file as clean. We have to use normallookup for
3097 # to report the file as clean. We have to use normallookup for
3091 # merges to avoid losing information about merged/dirty files.
3098 # merges to avoid losing information about merged/dirty files.
3092 if p2 != nullid:
3099 if p2 != nullid:
3093 normal = repo.dirstate.normallookup
3100 normal = repo.dirstate.normallookup
3094 else:
3101 else:
3095 normal = repo.dirstate.normal
3102 normal = repo.dirstate.normal
3096
3103
3097 newlyaddedandmodifiedfiles = set()
3104 newlyaddedandmodifiedfiles = set()
3098 if interactive:
3105 if interactive:
3099 # Prompt the user for changes to revert
3106 # Prompt the user for changes to revert
3100 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3107 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3101 m = scmutil.match(ctx, torevert, {})
3108 m = scmutil.match(ctx, torevert, {})
3102 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3109 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3103 diffopts.nodates = True
3110 diffopts.nodates = True
3104 diffopts.git = True
3111 diffopts.git = True
3105 reversehunks = repo.ui.configbool('experimental',
3112 reversehunks = repo.ui.configbool('experimental',
3106 'revertalternateinteractivemode',
3113 'revertalternateinteractivemode',
3107 True)
3114 True)
3108 if reversehunks:
3115 if reversehunks:
3109 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3116 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3110 else:
3117 else:
3111 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3118 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3112 originalchunks = patch.parsepatch(diff)
3119 originalchunks = patch.parsepatch(diff)
3113
3120
3114 try:
3121 try:
3115
3122
3116 chunks = recordfilter(repo.ui, originalchunks)
3123 chunks = recordfilter(repo.ui, originalchunks)
3117 if reversehunks:
3124 if reversehunks:
3118 chunks = patch.reversehunks(chunks)
3125 chunks = patch.reversehunks(chunks)
3119
3126
3120 except patch.PatchError as err:
3127 except patch.PatchError as err:
3121 raise util.Abort(_('error parsing patch: %s') % err)
3128 raise util.Abort(_('error parsing patch: %s') % err)
3122
3129
3123 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3130 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3124 # Apply changes
3131 # Apply changes
3125 fp = cStringIO.StringIO()
3132 fp = cStringIO.StringIO()
3126 for c in chunks:
3133 for c in chunks:
3127 c.write(fp)
3134 c.write(fp)
3128 dopatch = fp.tell()
3135 dopatch = fp.tell()
3129 fp.seek(0)
3136 fp.seek(0)
3130 if dopatch:
3137 if dopatch:
3131 try:
3138 try:
3132 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3139 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3133 except patch.PatchError as err:
3140 except patch.PatchError as err:
3134 raise util.Abort(str(err))
3141 raise util.Abort(str(err))
3135 del fp
3142 del fp
3136 else:
3143 else:
3137 for f in actions['revert'][0]:
3144 for f in actions['revert'][0]:
3138 checkout(f)
3145 checkout(f)
3139 if normal:
3146 if normal:
3140 normal(f)
3147 normal(f)
3141
3148
3142 for f in actions['add'][0]:
3149 for f in actions['add'][0]:
3143 # Don't checkout modified files, they are already created by the diff
3150 # Don't checkout modified files, they are already created by the diff
3144 if f not in newlyaddedandmodifiedfiles:
3151 if f not in newlyaddedandmodifiedfiles:
3145 checkout(f)
3152 checkout(f)
3146 repo.dirstate.add(f)
3153 repo.dirstate.add(f)
3147
3154
3148 normal = repo.dirstate.normallookup
3155 normal = repo.dirstate.normallookup
3149 if node == parent and p2 == nullid:
3156 if node == parent and p2 == nullid:
3150 normal = repo.dirstate.normal
3157 normal = repo.dirstate.normal
3151 for f in actions['undelete'][0]:
3158 for f in actions['undelete'][0]:
3152 checkout(f)
3159 checkout(f)
3153 normal(f)
3160 normal(f)
3154
3161
3155 copied = copies.pathcopies(repo[parent], ctx)
3162 copied = copies.pathcopies(repo[parent], ctx)
3156
3163
3157 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3164 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3158 if f in copied:
3165 if f in copied:
3159 repo.dirstate.copy(copied[f], f)
3166 repo.dirstate.copy(copied[f], f)
3160
3167
3161 def command(table):
3168 def command(table):
3162 """Returns a function object to be used as a decorator for making commands.
3169 """Returns a function object to be used as a decorator for making commands.
3163
3170
3164 This function receives a command table as its argument. The table should
3171 This function receives a command table as its argument. The table should
3165 be a dict.
3172 be a dict.
3166
3173
3167 The returned function can be used as a decorator for adding commands
3174 The returned function can be used as a decorator for adding commands
3168 to that command table. This function accepts multiple arguments to define
3175 to that command table. This function accepts multiple arguments to define
3169 a command.
3176 a command.
3170
3177
3171 The first argument is the command name.
3178 The first argument is the command name.
3172
3179
3173 The options argument is an iterable of tuples defining command arguments.
3180 The options argument is an iterable of tuples defining command arguments.
3174 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3181 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3175
3182
3176 The synopsis argument defines a short, one line summary of how to use the
3183 The synopsis argument defines a short, one line summary of how to use the
3177 command. This shows up in the help output.
3184 command. This shows up in the help output.
3178
3185
3179 The norepo argument defines whether the command does not require a
3186 The norepo argument defines whether the command does not require a
3180 local repository. Most commands operate against a repository, thus the
3187 local repository. Most commands operate against a repository, thus the
3181 default is False.
3188 default is False.
3182
3189
3183 The optionalrepo argument defines whether the command optionally requires
3190 The optionalrepo argument defines whether the command optionally requires
3184 a local repository.
3191 a local repository.
3185
3192
3186 The inferrepo argument defines whether to try to find a repository from the
3193 The inferrepo argument defines whether to try to find a repository from the
3187 command line arguments. If True, arguments will be examined for potential
3194 command line arguments. If True, arguments will be examined for potential
3188 repository locations. See ``findrepo()``. If a repository is found, it
3195 repository locations. See ``findrepo()``. If a repository is found, it
3189 will be used.
3196 will be used.
3190 """
3197 """
3191 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3198 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3192 inferrepo=False):
3199 inferrepo=False):
3193 def decorator(func):
3200 def decorator(func):
3194 if synopsis:
3201 if synopsis:
3195 table[name] = func, list(options), synopsis
3202 table[name] = func, list(options), synopsis
3196 else:
3203 else:
3197 table[name] = func, list(options)
3204 table[name] = func, list(options)
3198
3205
3199 if norepo:
3206 if norepo:
3200 # Avoid import cycle.
3207 # Avoid import cycle.
3201 import commands
3208 import commands
3202 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3209 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3203
3210
3204 if optionalrepo:
3211 if optionalrepo:
3205 import commands
3212 import commands
3206 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3213 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3207
3214
3208 if inferrepo:
3215 if inferrepo:
3209 import commands
3216 import commands
3210 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3217 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3211
3218
3212 return func
3219 return func
3213 return decorator
3220 return decorator
3214
3221
3215 return cmd
3222 return cmd
3216
3223
3217 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3224 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3218 # commands.outgoing. "missing" is "missing" of the result of
3225 # commands.outgoing. "missing" is "missing" of the result of
3219 # "findcommonoutgoing()"
3226 # "findcommonoutgoing()"
3220 outgoinghooks = util.hooks()
3227 outgoinghooks = util.hooks()
3221
3228
3222 # a list of (ui, repo) functions called by commands.summary
3229 # a list of (ui, repo) functions called by commands.summary
3223 summaryhooks = util.hooks()
3230 summaryhooks = util.hooks()
3224
3231
3225 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3232 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3226 #
3233 #
3227 # functions should return tuple of booleans below, if 'changes' is None:
3234 # functions should return tuple of booleans below, if 'changes' is None:
3228 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3235 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3229 #
3236 #
3230 # otherwise, 'changes' is a tuple of tuples below:
3237 # otherwise, 'changes' is a tuple of tuples below:
3231 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3238 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3232 # - (desturl, destbranch, destpeer, outgoing)
3239 # - (desturl, destbranch, destpeer, outgoing)
3233 summaryremotehooks = util.hooks()
3240 summaryremotehooks = util.hooks()
3234
3241
3235 # A list of state files kept by multistep operations like graft.
3242 # A list of state files kept by multistep operations like graft.
3236 # Since graft cannot be aborted, it is considered 'clearable' by update.
3243 # Since graft cannot be aborted, it is considered 'clearable' by update.
3237 # note: bisect is intentionally excluded
3244 # note: bisect is intentionally excluded
3238 # (state file, clearable, allowcommit, error, hint)
3245 # (state file, clearable, allowcommit, error, hint)
3239 unfinishedstates = [
3246 unfinishedstates = [
3240 ('graftstate', True, False, _('graft in progress'),
3247 ('graftstate', True, False, _('graft in progress'),
3241 _("use 'hg graft --continue' or 'hg update' to abort")),
3248 _("use 'hg graft --continue' or 'hg update' to abort")),
3242 ('updatestate', True, False, _('last update was interrupted'),
3249 ('updatestate', True, False, _('last update was interrupted'),
3243 _("use 'hg update' to get a consistent checkout"))
3250 _("use 'hg update' to get a consistent checkout"))
3244 ]
3251 ]
3245
3252
3246 def checkunfinished(repo, commit=False):
3253 def checkunfinished(repo, commit=False):
3247 '''Look for an unfinished multistep operation, like graft, and abort
3254 '''Look for an unfinished multistep operation, like graft, and abort
3248 if found. It's probably good to check this right before
3255 if found. It's probably good to check this right before
3249 bailifchanged().
3256 bailifchanged().
3250 '''
3257 '''
3251 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3258 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3252 if commit and allowcommit:
3259 if commit and allowcommit:
3253 continue
3260 continue
3254 if repo.vfs.exists(f):
3261 if repo.vfs.exists(f):
3255 raise util.Abort(msg, hint=hint)
3262 raise util.Abort(msg, hint=hint)
3256
3263
3257 def clearunfinished(repo):
3264 def clearunfinished(repo):
3258 '''Check for unfinished operations (as above), and clear the ones
3265 '''Check for unfinished operations (as above), and clear the ones
3259 that are clearable.
3266 that are clearable.
3260 '''
3267 '''
3261 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3268 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3262 if not clearable and repo.vfs.exists(f):
3269 if not clearable and repo.vfs.exists(f):
3263 raise util.Abort(msg, hint=hint)
3270 raise util.Abort(msg, hint=hint)
3264 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3271 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3265 if clearable and repo.vfs.exists(f):
3272 if clearable and repo.vfs.exists(f):
3266 util.unlink(repo.join(f))
3273 util.unlink(repo.join(f))
3267
3274
3268 class dirstateguard(object):
3275 class dirstateguard(object):
3269 '''Restore dirstate at unexpected failure.
3276 '''Restore dirstate at unexpected failure.
3270
3277
3271 At the construction, this class does:
3278 At the construction, this class does:
3272
3279
3273 - write current ``repo.dirstate`` out, and
3280 - write current ``repo.dirstate`` out, and
3274 - save ``.hg/dirstate`` into the backup file
3281 - save ``.hg/dirstate`` into the backup file
3275
3282
3276 This restores ``.hg/dirstate`` from backup file, if ``release()``
3283 This restores ``.hg/dirstate`` from backup file, if ``release()``
3277 is invoked before ``close()``.
3284 is invoked before ``close()``.
3278
3285
3279 This just removes the backup file at ``close()`` before ``release()``.
3286 This just removes the backup file at ``close()`` before ``release()``.
3280 '''
3287 '''
3281
3288
3282 def __init__(self, repo, name):
3289 def __init__(self, repo, name):
3283 repo.dirstate.write()
3290 repo.dirstate.write()
3284 self._repo = repo
3291 self._repo = repo
3285 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3292 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3286 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3293 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3287 self._active = True
3294 self._active = True
3288 self._closed = False
3295 self._closed = False
3289
3296
3290 def __del__(self):
3297 def __del__(self):
3291 if self._active: # still active
3298 if self._active: # still active
3292 # this may occur, even if this class is used correctly:
3299 # this may occur, even if this class is used correctly:
3293 # for example, releasing other resources like transaction
3300 # for example, releasing other resources like transaction
3294 # may raise exception before ``dirstateguard.release`` in
3301 # may raise exception before ``dirstateguard.release`` in
3295 # ``release(tr, ....)``.
3302 # ``release(tr, ....)``.
3296 self._abort()
3303 self._abort()
3297
3304
3298 def close(self):
3305 def close(self):
3299 if not self._active: # already inactivated
3306 if not self._active: # already inactivated
3300 msg = (_("can't close already inactivated backup: %s")
3307 msg = (_("can't close already inactivated backup: %s")
3301 % self._filename)
3308 % self._filename)
3302 raise util.Abort(msg)
3309 raise util.Abort(msg)
3303
3310
3304 self._repo.vfs.unlink(self._filename)
3311 self._repo.vfs.unlink(self._filename)
3305 self._active = False
3312 self._active = False
3306 self._closed = True
3313 self._closed = True
3307
3314
3308 def _abort(self):
3315 def _abort(self):
3309 # this "invalidate()" prevents "wlock.release()" from writing
3316 # this "invalidate()" prevents "wlock.release()" from writing
3310 # changes of dirstate out after restoring to original status
3317 # changes of dirstate out after restoring to original status
3311 self._repo.dirstate.invalidate()
3318 self._repo.dirstate.invalidate()
3312
3319
3313 self._repo.vfs.rename(self._filename, 'dirstate')
3320 self._repo.vfs.rename(self._filename, 'dirstate')
3314 self._active = False
3321 self._active = False
3315
3322
3316 def release(self):
3323 def release(self):
3317 if not self._closed:
3324 if not self._closed:
3318 if not self._active: # already inactivated
3325 if not self._active: # already inactivated
3319 msg = (_("can't release already inactivated backup: %s")
3326 msg = (_("can't release already inactivated backup: %s")
3320 % self._filename)
3327 % self._filename)
3321 raise util.Abort(msg)
3328 raise util.Abort(msg)
3322 self._abort()
3329 self._abort()
General Comments 0
You need to be logged in to leave comments. Login now