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