##// END OF EJS Templates
cmdutil: stop tryimportone from using dirstateguard (BC)...
FUJIWARA Katsunori -
r26579:dc2b8c00 default
parent child Browse files
Show More
@@ -1,3429 +1,3425
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 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 ## facility to let extension process additional data into an import patch
834 ## facility to let extension process additional data into an import patch
835 # list of identifier to be executed in order
835 # list of identifier to be executed in order
836 extrapreimport = [] # run before commit
836 extrapreimport = [] # run before commit
837 extrapostimport = [] # run after commit
837 extrapostimport = [] # run after commit
838 # mapping from identifier to actual import function
838 # mapping from identifier to actual import function
839 #
839 #
840 # 'preimport' are run before the commit is made and are provided the following
840 # 'preimport' are run before the commit is made and are provided the following
841 # arguments:
841 # arguments:
842 # - repo: the localrepository instance,
842 # - repo: the localrepository instance,
843 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
843 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
844 # - extra: the future extra dictionnary of the changeset, please mutate it,
844 # - extra: the future extra dictionnary of the changeset, please mutate it,
845 # - opts: the import options.
845 # - opts: the import options.
846 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
846 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
847 # mutation of in memory commit and more. Feel free to rework the code to get
847 # mutation of in memory commit and more. Feel free to rework the code to get
848 # there.
848 # there.
849 extrapreimportmap = {}
849 extrapreimportmap = {}
850 # 'postimport' are run after the commit is made and are provided the following
850 # 'postimport' are run after the commit is made and are provided the following
851 # argument:
851 # argument:
852 # - ctx: the changectx created by import.
852 # - ctx: the changectx created by import.
853 extrapostimportmap = {}
853 extrapostimportmap = {}
854
854
855 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
855 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
856 """Utility function used by commands.import to import a single patch
856 """Utility function used by commands.import to import a single patch
857
857
858 This function is explicitly defined here to help the evolve extension to
858 This function is explicitly defined here to help the evolve extension to
859 wrap this part of the import logic.
859 wrap this part of the import logic.
860
860
861 The API is currently a bit ugly because it a simple code translation from
861 The API is currently a bit ugly because it a simple code translation from
862 the import command. Feel free to make it better.
862 the import command. Feel free to make it better.
863
863
864 :hunk: a patch (as a binary string)
864 :hunk: a patch (as a binary string)
865 :parents: nodes that will be parent of the created commit
865 :parents: nodes that will be parent of the created commit
866 :opts: the full dict of option passed to the import command
866 :opts: the full dict of option passed to the import command
867 :msgs: list to save commit message to.
867 :msgs: list to save commit message to.
868 (used in case we need to save it when failing)
868 (used in case we need to save it when failing)
869 :updatefunc: a function that update a repo to a given node
869 :updatefunc: a function that update a repo to a given node
870 updatefunc(<repo>, <node>)
870 updatefunc(<repo>, <node>)
871 """
871 """
872 # avoid cycle context -> subrepo -> cmdutil
872 # avoid cycle context -> subrepo -> cmdutil
873 import context
873 import context
874 extractdata = patch.extract(ui, hunk)
874 extractdata = patch.extract(ui, hunk)
875 tmpname = extractdata.get('filename')
875 tmpname = extractdata.get('filename')
876 message = extractdata.get('message')
876 message = extractdata.get('message')
877 user = extractdata.get('user')
877 user = extractdata.get('user')
878 date = extractdata.get('date')
878 date = extractdata.get('date')
879 branch = extractdata.get('branch')
879 branch = extractdata.get('branch')
880 nodeid = extractdata.get('nodeid')
880 nodeid = extractdata.get('nodeid')
881 p1 = extractdata.get('p1')
881 p1 = extractdata.get('p1')
882 p2 = extractdata.get('p2')
882 p2 = extractdata.get('p2')
883
883
884 update = not opts.get('bypass')
884 update = not opts.get('bypass')
885 strip = opts["strip"]
885 strip = opts["strip"]
886 prefix = opts["prefix"]
886 prefix = opts["prefix"]
887 sim = float(opts.get('similarity') or 0)
887 sim = float(opts.get('similarity') or 0)
888 if not tmpname:
888 if not tmpname:
889 return (None, None, False)
889 return (None, None, False)
890 msg = _('applied to working directory')
890 msg = _('applied to working directory')
891
891
892 rejects = False
892 rejects = False
893 dsguard = None
894
893
895 try:
894 try:
896 cmdline_message = logmessage(ui, opts)
895 cmdline_message = logmessage(ui, opts)
897 if cmdline_message:
896 if cmdline_message:
898 # pickup the cmdline msg
897 # pickup the cmdline msg
899 message = cmdline_message
898 message = cmdline_message
900 elif message:
899 elif message:
901 # pickup the patch msg
900 # pickup the patch msg
902 message = message.strip()
901 message = message.strip()
903 else:
902 else:
904 # launch the editor
903 # launch the editor
905 message = None
904 message = None
906 ui.debug('message:\n%s\n' % message)
905 ui.debug('message:\n%s\n' % message)
907
906
908 if len(parents) == 1:
907 if len(parents) == 1:
909 parents.append(repo[nullid])
908 parents.append(repo[nullid])
910 if opts.get('exact'):
909 if opts.get('exact'):
911 if not nodeid or not p1:
910 if not nodeid or not p1:
912 raise util.Abort(_('not a Mercurial patch'))
911 raise util.Abort(_('not a Mercurial patch'))
913 p1 = repo[p1]
912 p1 = repo[p1]
914 p2 = repo[p2 or nullid]
913 p2 = repo[p2 or nullid]
915 elif p2:
914 elif p2:
916 try:
915 try:
917 p1 = repo[p1]
916 p1 = repo[p1]
918 p2 = repo[p2]
917 p2 = repo[p2]
919 # Without any options, consider p2 only if the
918 # Without any options, consider p2 only if the
920 # patch is being applied on top of the recorded
919 # patch is being applied on top of the recorded
921 # first parent.
920 # first parent.
922 if p1 != parents[0]:
921 if p1 != parents[0]:
923 p1 = parents[0]
922 p1 = parents[0]
924 p2 = repo[nullid]
923 p2 = repo[nullid]
925 except error.RepoError:
924 except error.RepoError:
926 p1, p2 = parents
925 p1, p2 = parents
927 if p2.node() == nullid:
926 if p2.node() == nullid:
928 ui.warn(_("warning: import the patch as a normal revision\n"
927 ui.warn(_("warning: import the patch as a normal revision\n"
929 "(use --exact to import the patch as a merge)\n"))
928 "(use --exact to import the patch as a merge)\n"))
930 else:
929 else:
931 p1, p2 = parents
930 p1, p2 = parents
932
931
933 n = None
932 n = None
934 if update:
933 if update:
935 dsguard = dirstateguard(repo, 'tryimportone')
936 if p1 != parents[0]:
934 if p1 != parents[0]:
937 updatefunc(repo, p1.node())
935 updatefunc(repo, p1.node())
938 if p2 != parents[1]:
936 if p2 != parents[1]:
939 repo.setparents(p1.node(), p2.node())
937 repo.setparents(p1.node(), p2.node())
940
938
941 if opts.get('exact') or opts.get('import_branch'):
939 if opts.get('exact') or opts.get('import_branch'):
942 repo.dirstate.setbranch(branch or 'default')
940 repo.dirstate.setbranch(branch or 'default')
943
941
944 partial = opts.get('partial', False)
942 partial = opts.get('partial', False)
945 files = set()
943 files = set()
946 try:
944 try:
947 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
945 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
948 files=files, eolmode=None, similarity=sim / 100.0)
946 files=files, eolmode=None, similarity=sim / 100.0)
949 except patch.PatchError as e:
947 except patch.PatchError as e:
950 if not partial:
948 if not partial:
951 raise util.Abort(str(e))
949 raise util.Abort(str(e))
952 if partial:
950 if partial:
953 rejects = True
951 rejects = True
954
952
955 files = list(files)
953 files = list(files)
956 if opts.get('no_commit'):
954 if opts.get('no_commit'):
957 if message:
955 if message:
958 msgs.append(message)
956 msgs.append(message)
959 else:
957 else:
960 if opts.get('exact') or p2:
958 if opts.get('exact') or p2:
961 # If you got here, you either use --force and know what
959 # If you got here, you either use --force and know what
962 # you are doing or used --exact or a merge patch while
960 # you are doing or used --exact or a merge patch while
963 # being updated to its first parent.
961 # being updated to its first parent.
964 m = None
962 m = None
965 else:
963 else:
966 m = scmutil.matchfiles(repo, files or [])
964 m = scmutil.matchfiles(repo, files or [])
967 editform = mergeeditform(repo[None], 'import.normal')
965 editform = mergeeditform(repo[None], 'import.normal')
968 if opts.get('exact'):
966 if opts.get('exact'):
969 editor = None
967 editor = None
970 else:
968 else:
971 editor = getcommiteditor(editform=editform, **opts)
969 editor = getcommiteditor(editform=editform, **opts)
972 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
970 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
973 extra = {}
971 extra = {}
974 for idfunc in extrapreimport:
972 for idfunc in extrapreimport:
975 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
973 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
976 try:
974 try:
977 if partial:
975 if partial:
978 repo.ui.setconfig('ui', 'allowemptycommit', True)
976 repo.ui.setconfig('ui', 'allowemptycommit', True)
979 n = repo.commit(message, opts.get('user') or user,
977 n = repo.commit(message, opts.get('user') or user,
980 opts.get('date') or date, match=m,
978 opts.get('date') or date, match=m,
981 editor=editor, extra=extra)
979 editor=editor, extra=extra)
982 for idfunc in extrapostimport:
980 for idfunc in extrapostimport:
983 extrapostimportmap[idfunc](repo[n])
981 extrapostimportmap[idfunc](repo[n])
984 finally:
982 finally:
985 repo.ui.restoreconfig(allowemptyback)
983 repo.ui.restoreconfig(allowemptyback)
986 dsguard.close()
987 else:
984 else:
988 if opts.get('exact') or opts.get('import_branch'):
985 if opts.get('exact') or opts.get('import_branch'):
989 branch = branch or 'default'
986 branch = branch or 'default'
990 else:
987 else:
991 branch = p1.branch()
988 branch = p1.branch()
992 store = patch.filestore()
989 store = patch.filestore()
993 try:
990 try:
994 files = set()
991 files = set()
995 try:
992 try:
996 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
993 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
997 files, eolmode=None)
994 files, eolmode=None)
998 except patch.PatchError as e:
995 except patch.PatchError as e:
999 raise util.Abort(str(e))
996 raise util.Abort(str(e))
1000 if opts.get('exact'):
997 if opts.get('exact'):
1001 editor = None
998 editor = None
1002 else:
999 else:
1003 editor = getcommiteditor(editform='import.bypass')
1000 editor = getcommiteditor(editform='import.bypass')
1004 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1001 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1005 message,
1002 message,
1006 opts.get('user') or user,
1003 opts.get('user') or user,
1007 opts.get('date') or date,
1004 opts.get('date') or date,
1008 branch, files, store,
1005 branch, files, store,
1009 editor=editor)
1006 editor=editor)
1010 n = memctx.commit()
1007 n = memctx.commit()
1011 finally:
1008 finally:
1012 store.close()
1009 store.close()
1013 if opts.get('exact') and opts.get('no_commit'):
1010 if opts.get('exact') and opts.get('no_commit'):
1014 # --exact with --no-commit is still useful in that it does merge
1011 # --exact with --no-commit is still useful in that it does merge
1015 # and branch bits
1012 # and branch bits
1016 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1013 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1017 elif opts.get('exact') and hex(n) != nodeid:
1014 elif opts.get('exact') and hex(n) != nodeid:
1018 raise util.Abort(_('patch is damaged or loses information'))
1015 raise util.Abort(_('patch is damaged or loses information'))
1019 if n:
1016 if n:
1020 # i18n: refers to a short changeset id
1017 # i18n: refers to a short changeset id
1021 msg = _('created %s') % short(n)
1018 msg = _('created %s') % short(n)
1022 return (msg, n, rejects)
1019 return (msg, n, rejects)
1023 finally:
1020 finally:
1024 lockmod.release(dsguard)
1025 os.unlink(tmpname)
1021 os.unlink(tmpname)
1026
1022
1027 # facility to let extensions include additional data in an exported patch
1023 # facility to let extensions include additional data in an exported patch
1028 # list of identifiers to be executed in order
1024 # list of identifiers to be executed in order
1029 extraexport = []
1025 extraexport = []
1030 # mapping from identifier to actual export function
1026 # mapping from identifier to actual export function
1031 # function as to return a string to be added to the header or None
1027 # function as to return a string to be added to the header or None
1032 # it is given two arguments (sequencenumber, changectx)
1028 # it is given two arguments (sequencenumber, changectx)
1033 extraexportmap = {}
1029 extraexportmap = {}
1034
1030
1035 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1031 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1036 opts=None, match=None):
1032 opts=None, match=None):
1037 '''export changesets as hg patches.'''
1033 '''export changesets as hg patches.'''
1038
1034
1039 total = len(revs)
1035 total = len(revs)
1040 revwidth = max([len(str(rev)) for rev in revs])
1036 revwidth = max([len(str(rev)) for rev in revs])
1041 filemode = {}
1037 filemode = {}
1042
1038
1043 def single(rev, seqno, fp):
1039 def single(rev, seqno, fp):
1044 ctx = repo[rev]
1040 ctx = repo[rev]
1045 node = ctx.node()
1041 node = ctx.node()
1046 parents = [p.node() for p in ctx.parents() if p]
1042 parents = [p.node() for p in ctx.parents() if p]
1047 branch = ctx.branch()
1043 branch = ctx.branch()
1048 if switch_parent:
1044 if switch_parent:
1049 parents.reverse()
1045 parents.reverse()
1050
1046
1051 if parents:
1047 if parents:
1052 prev = parents[0]
1048 prev = parents[0]
1053 else:
1049 else:
1054 prev = nullid
1050 prev = nullid
1055
1051
1056 shouldclose = False
1052 shouldclose = False
1057 if not fp and len(template) > 0:
1053 if not fp and len(template) > 0:
1058 desc_lines = ctx.description().rstrip().split('\n')
1054 desc_lines = ctx.description().rstrip().split('\n')
1059 desc = desc_lines[0] #Commit always has a first line.
1055 desc = desc_lines[0] #Commit always has a first line.
1060 fp = makefileobj(repo, template, node, desc=desc, total=total,
1056 fp = makefileobj(repo, template, node, desc=desc, total=total,
1061 seqno=seqno, revwidth=revwidth, mode='wb',
1057 seqno=seqno, revwidth=revwidth, mode='wb',
1062 modemap=filemode)
1058 modemap=filemode)
1063 if fp != template:
1059 if fp != template:
1064 shouldclose = True
1060 shouldclose = True
1065 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1061 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1066 repo.ui.note("%s\n" % fp.name)
1062 repo.ui.note("%s\n" % fp.name)
1067
1063
1068 if not fp:
1064 if not fp:
1069 write = repo.ui.write
1065 write = repo.ui.write
1070 else:
1066 else:
1071 def write(s, **kw):
1067 def write(s, **kw):
1072 fp.write(s)
1068 fp.write(s)
1073
1069
1074 write("# HG changeset patch\n")
1070 write("# HG changeset patch\n")
1075 write("# User %s\n" % ctx.user())
1071 write("# User %s\n" % ctx.user())
1076 write("# Date %d %d\n" % ctx.date())
1072 write("# Date %d %d\n" % ctx.date())
1077 write("# %s\n" % util.datestr(ctx.date()))
1073 write("# %s\n" % util.datestr(ctx.date()))
1078 if branch and branch != 'default':
1074 if branch and branch != 'default':
1079 write("# Branch %s\n" % branch)
1075 write("# Branch %s\n" % branch)
1080 write("# Node ID %s\n" % hex(node))
1076 write("# Node ID %s\n" % hex(node))
1081 write("# Parent %s\n" % hex(prev))
1077 write("# Parent %s\n" % hex(prev))
1082 if len(parents) > 1:
1078 if len(parents) > 1:
1083 write("# Parent %s\n" % hex(parents[1]))
1079 write("# Parent %s\n" % hex(parents[1]))
1084
1080
1085 for headerid in extraexport:
1081 for headerid in extraexport:
1086 header = extraexportmap[headerid](seqno, ctx)
1082 header = extraexportmap[headerid](seqno, ctx)
1087 if header is not None:
1083 if header is not None:
1088 write('# %s\n' % header)
1084 write('# %s\n' % header)
1089 write(ctx.description().rstrip())
1085 write(ctx.description().rstrip())
1090 write("\n\n")
1086 write("\n\n")
1091
1087
1092 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1088 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1093 write(chunk, label=label)
1089 write(chunk, label=label)
1094
1090
1095 if shouldclose:
1091 if shouldclose:
1096 fp.close()
1092 fp.close()
1097
1093
1098 for seqno, rev in enumerate(revs):
1094 for seqno, rev in enumerate(revs):
1099 single(rev, seqno + 1, fp)
1095 single(rev, seqno + 1, fp)
1100
1096
1101 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1097 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1102 changes=None, stat=False, fp=None, prefix='',
1098 changes=None, stat=False, fp=None, prefix='',
1103 root='', listsubrepos=False):
1099 root='', listsubrepos=False):
1104 '''show diff or diffstat.'''
1100 '''show diff or diffstat.'''
1105 if fp is None:
1101 if fp is None:
1106 write = ui.write
1102 write = ui.write
1107 else:
1103 else:
1108 def write(s, **kw):
1104 def write(s, **kw):
1109 fp.write(s)
1105 fp.write(s)
1110
1106
1111 if root:
1107 if root:
1112 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1108 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1113 else:
1109 else:
1114 relroot = ''
1110 relroot = ''
1115 if relroot != '':
1111 if relroot != '':
1116 # XXX relative roots currently don't work if the root is within a
1112 # XXX relative roots currently don't work if the root is within a
1117 # subrepo
1113 # subrepo
1118 uirelroot = match.uipath(relroot)
1114 uirelroot = match.uipath(relroot)
1119 relroot += '/'
1115 relroot += '/'
1120 for matchroot in match.files():
1116 for matchroot in match.files():
1121 if not matchroot.startswith(relroot):
1117 if not matchroot.startswith(relroot):
1122 ui.warn(_('warning: %s not inside relative root %s\n') % (
1118 ui.warn(_('warning: %s not inside relative root %s\n') % (
1123 match.uipath(matchroot), uirelroot))
1119 match.uipath(matchroot), uirelroot))
1124
1120
1125 if stat:
1121 if stat:
1126 diffopts = diffopts.copy(context=0)
1122 diffopts = diffopts.copy(context=0)
1127 width = 80
1123 width = 80
1128 if not ui.plain():
1124 if not ui.plain():
1129 width = ui.termwidth()
1125 width = ui.termwidth()
1130 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1126 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1131 prefix=prefix, relroot=relroot)
1127 prefix=prefix, relroot=relroot)
1132 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1128 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1133 width=width,
1129 width=width,
1134 git=diffopts.git):
1130 git=diffopts.git):
1135 write(chunk, label=label)
1131 write(chunk, label=label)
1136 else:
1132 else:
1137 for chunk, label in patch.diffui(repo, node1, node2, match,
1133 for chunk, label in patch.diffui(repo, node1, node2, match,
1138 changes, diffopts, prefix=prefix,
1134 changes, diffopts, prefix=prefix,
1139 relroot=relroot):
1135 relroot=relroot):
1140 write(chunk, label=label)
1136 write(chunk, label=label)
1141
1137
1142 if listsubrepos:
1138 if listsubrepos:
1143 ctx1 = repo[node1]
1139 ctx1 = repo[node1]
1144 ctx2 = repo[node2]
1140 ctx2 = repo[node2]
1145 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1141 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1146 tempnode2 = node2
1142 tempnode2 = node2
1147 try:
1143 try:
1148 if node2 is not None:
1144 if node2 is not None:
1149 tempnode2 = ctx2.substate[subpath][1]
1145 tempnode2 = ctx2.substate[subpath][1]
1150 except KeyError:
1146 except KeyError:
1151 # A subrepo that existed in node1 was deleted between node1 and
1147 # A subrepo that existed in node1 was deleted between node1 and
1152 # node2 (inclusive). Thus, ctx2's substate won't contain that
1148 # node2 (inclusive). Thus, ctx2's substate won't contain that
1153 # subpath. The best we can do is to ignore it.
1149 # subpath. The best we can do is to ignore it.
1154 tempnode2 = None
1150 tempnode2 = None
1155 submatch = matchmod.narrowmatcher(subpath, match)
1151 submatch = matchmod.narrowmatcher(subpath, match)
1156 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1152 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1157 stat=stat, fp=fp, prefix=prefix)
1153 stat=stat, fp=fp, prefix=prefix)
1158
1154
1159 class changeset_printer(object):
1155 class changeset_printer(object):
1160 '''show changeset information when templating not requested.'''
1156 '''show changeset information when templating not requested.'''
1161
1157
1162 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1158 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1163 self.ui = ui
1159 self.ui = ui
1164 self.repo = repo
1160 self.repo = repo
1165 self.buffered = buffered
1161 self.buffered = buffered
1166 self.matchfn = matchfn
1162 self.matchfn = matchfn
1167 self.diffopts = diffopts
1163 self.diffopts = diffopts
1168 self.header = {}
1164 self.header = {}
1169 self.hunk = {}
1165 self.hunk = {}
1170 self.lastheader = None
1166 self.lastheader = None
1171 self.footer = None
1167 self.footer = None
1172
1168
1173 def flush(self, ctx):
1169 def flush(self, ctx):
1174 rev = ctx.rev()
1170 rev = ctx.rev()
1175 if rev in self.header:
1171 if rev in self.header:
1176 h = self.header[rev]
1172 h = self.header[rev]
1177 if h != self.lastheader:
1173 if h != self.lastheader:
1178 self.lastheader = h
1174 self.lastheader = h
1179 self.ui.write(h)
1175 self.ui.write(h)
1180 del self.header[rev]
1176 del self.header[rev]
1181 if rev in self.hunk:
1177 if rev in self.hunk:
1182 self.ui.write(self.hunk[rev])
1178 self.ui.write(self.hunk[rev])
1183 del self.hunk[rev]
1179 del self.hunk[rev]
1184 return 1
1180 return 1
1185 return 0
1181 return 0
1186
1182
1187 def close(self):
1183 def close(self):
1188 if self.footer:
1184 if self.footer:
1189 self.ui.write(self.footer)
1185 self.ui.write(self.footer)
1190
1186
1191 def show(self, ctx, copies=None, matchfn=None, **props):
1187 def show(self, ctx, copies=None, matchfn=None, **props):
1192 if self.buffered:
1188 if self.buffered:
1193 self.ui.pushbuffer()
1189 self.ui.pushbuffer()
1194 self._show(ctx, copies, matchfn, props)
1190 self._show(ctx, copies, matchfn, props)
1195 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1191 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1196 else:
1192 else:
1197 self._show(ctx, copies, matchfn, props)
1193 self._show(ctx, copies, matchfn, props)
1198
1194
1199 def _show(self, ctx, copies, matchfn, props):
1195 def _show(self, ctx, copies, matchfn, props):
1200 '''show a single changeset or file revision'''
1196 '''show a single changeset or file revision'''
1201 changenode = ctx.node()
1197 changenode = ctx.node()
1202 rev = ctx.rev()
1198 rev = ctx.rev()
1203 if self.ui.debugflag:
1199 if self.ui.debugflag:
1204 hexfunc = hex
1200 hexfunc = hex
1205 else:
1201 else:
1206 hexfunc = short
1202 hexfunc = short
1207 # as of now, wctx.node() and wctx.rev() return None, but we want to
1203 # as of now, wctx.node() and wctx.rev() return None, but we want to
1208 # show the same values as {node} and {rev} templatekw
1204 # show the same values as {node} and {rev} templatekw
1209 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1205 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1210
1206
1211 if self.ui.quiet:
1207 if self.ui.quiet:
1212 self.ui.write("%d:%s\n" % revnode, label='log.node')
1208 self.ui.write("%d:%s\n" % revnode, label='log.node')
1213 return
1209 return
1214
1210
1215 date = util.datestr(ctx.date())
1211 date = util.datestr(ctx.date())
1216
1212
1217 # i18n: column positioning for "hg log"
1213 # i18n: column positioning for "hg log"
1218 self.ui.write(_("changeset: %d:%s\n") % revnode,
1214 self.ui.write(_("changeset: %d:%s\n") % revnode,
1219 label='log.changeset changeset.%s' % ctx.phasestr())
1215 label='log.changeset changeset.%s' % ctx.phasestr())
1220
1216
1221 # branches are shown first before any other names due to backwards
1217 # branches are shown first before any other names due to backwards
1222 # compatibility
1218 # compatibility
1223 branch = ctx.branch()
1219 branch = ctx.branch()
1224 # don't show the default branch name
1220 # don't show the default branch name
1225 if branch != 'default':
1221 if branch != 'default':
1226 # i18n: column positioning for "hg log"
1222 # i18n: column positioning for "hg log"
1227 self.ui.write(_("branch: %s\n") % branch,
1223 self.ui.write(_("branch: %s\n") % branch,
1228 label='log.branch')
1224 label='log.branch')
1229
1225
1230 for name, ns in self.repo.names.iteritems():
1226 for name, ns in self.repo.names.iteritems():
1231 # branches has special logic already handled above, so here we just
1227 # branches has special logic already handled above, so here we just
1232 # skip it
1228 # skip it
1233 if name == 'branches':
1229 if name == 'branches':
1234 continue
1230 continue
1235 # we will use the templatename as the color name since those two
1231 # we will use the templatename as the color name since those two
1236 # should be the same
1232 # should be the same
1237 for name in ns.names(self.repo, changenode):
1233 for name in ns.names(self.repo, changenode):
1238 self.ui.write(ns.logfmt % name,
1234 self.ui.write(ns.logfmt % name,
1239 label='log.%s' % ns.colorname)
1235 label='log.%s' % ns.colorname)
1240 if self.ui.debugflag:
1236 if self.ui.debugflag:
1241 # i18n: column positioning for "hg log"
1237 # i18n: column positioning for "hg log"
1242 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1238 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1243 label='log.phase')
1239 label='log.phase')
1244 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1240 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1245 label = 'log.parent changeset.%s' % pctx.phasestr()
1241 label = 'log.parent changeset.%s' % pctx.phasestr()
1246 # i18n: column positioning for "hg log"
1242 # i18n: column positioning for "hg log"
1247 self.ui.write(_("parent: %d:%s\n")
1243 self.ui.write(_("parent: %d:%s\n")
1248 % (pctx.rev(), hexfunc(pctx.node())),
1244 % (pctx.rev(), hexfunc(pctx.node())),
1249 label=label)
1245 label=label)
1250
1246
1251 if self.ui.debugflag and rev is not None:
1247 if self.ui.debugflag and rev is not None:
1252 mnode = ctx.manifestnode()
1248 mnode = ctx.manifestnode()
1253 # i18n: column positioning for "hg log"
1249 # i18n: column positioning for "hg log"
1254 self.ui.write(_("manifest: %d:%s\n") %
1250 self.ui.write(_("manifest: %d:%s\n") %
1255 (self.repo.manifest.rev(mnode), hex(mnode)),
1251 (self.repo.manifest.rev(mnode), hex(mnode)),
1256 label='ui.debug log.manifest')
1252 label='ui.debug log.manifest')
1257 # i18n: column positioning for "hg log"
1253 # i18n: column positioning for "hg log"
1258 self.ui.write(_("user: %s\n") % ctx.user(),
1254 self.ui.write(_("user: %s\n") % ctx.user(),
1259 label='log.user')
1255 label='log.user')
1260 # i18n: column positioning for "hg log"
1256 # i18n: column positioning for "hg log"
1261 self.ui.write(_("date: %s\n") % date,
1257 self.ui.write(_("date: %s\n") % date,
1262 label='log.date')
1258 label='log.date')
1263
1259
1264 if self.ui.debugflag:
1260 if self.ui.debugflag:
1265 files = ctx.p1().status(ctx)[:3]
1261 files = ctx.p1().status(ctx)[:3]
1266 for key, value in zip([# i18n: column positioning for "hg log"
1262 for key, value in zip([# i18n: column positioning for "hg log"
1267 _("files:"),
1263 _("files:"),
1268 # i18n: column positioning for "hg log"
1264 # i18n: column positioning for "hg log"
1269 _("files+:"),
1265 _("files+:"),
1270 # i18n: column positioning for "hg log"
1266 # i18n: column positioning for "hg log"
1271 _("files-:")], files):
1267 _("files-:")], files):
1272 if value:
1268 if value:
1273 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1269 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1274 label='ui.debug log.files')
1270 label='ui.debug log.files')
1275 elif ctx.files() and self.ui.verbose:
1271 elif ctx.files() and self.ui.verbose:
1276 # i18n: column positioning for "hg log"
1272 # i18n: column positioning for "hg log"
1277 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1273 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1278 label='ui.note log.files')
1274 label='ui.note log.files')
1279 if copies and self.ui.verbose:
1275 if copies and self.ui.verbose:
1280 copies = ['%s (%s)' % c for c in copies]
1276 copies = ['%s (%s)' % c for c in copies]
1281 # i18n: column positioning for "hg log"
1277 # i18n: column positioning for "hg log"
1282 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1278 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1283 label='ui.note log.copies')
1279 label='ui.note log.copies')
1284
1280
1285 extra = ctx.extra()
1281 extra = ctx.extra()
1286 if extra and self.ui.debugflag:
1282 if extra and self.ui.debugflag:
1287 for key, value in sorted(extra.items()):
1283 for key, value in sorted(extra.items()):
1288 # i18n: column positioning for "hg log"
1284 # i18n: column positioning for "hg log"
1289 self.ui.write(_("extra: %s=%s\n")
1285 self.ui.write(_("extra: %s=%s\n")
1290 % (key, value.encode('string_escape')),
1286 % (key, value.encode('string_escape')),
1291 label='ui.debug log.extra')
1287 label='ui.debug log.extra')
1292
1288
1293 description = ctx.description().strip()
1289 description = ctx.description().strip()
1294 if description:
1290 if description:
1295 if self.ui.verbose:
1291 if self.ui.verbose:
1296 self.ui.write(_("description:\n"),
1292 self.ui.write(_("description:\n"),
1297 label='ui.note log.description')
1293 label='ui.note log.description')
1298 self.ui.write(description,
1294 self.ui.write(description,
1299 label='ui.note log.description')
1295 label='ui.note log.description')
1300 self.ui.write("\n\n")
1296 self.ui.write("\n\n")
1301 else:
1297 else:
1302 # i18n: column positioning for "hg log"
1298 # i18n: column positioning for "hg log"
1303 self.ui.write(_("summary: %s\n") %
1299 self.ui.write(_("summary: %s\n") %
1304 description.splitlines()[0],
1300 description.splitlines()[0],
1305 label='log.summary')
1301 label='log.summary')
1306 self.ui.write("\n")
1302 self.ui.write("\n")
1307
1303
1308 self.showpatch(changenode, matchfn)
1304 self.showpatch(changenode, matchfn)
1309
1305
1310 def showpatch(self, node, matchfn):
1306 def showpatch(self, node, matchfn):
1311 if not matchfn:
1307 if not matchfn:
1312 matchfn = self.matchfn
1308 matchfn = self.matchfn
1313 if matchfn:
1309 if matchfn:
1314 stat = self.diffopts.get('stat')
1310 stat = self.diffopts.get('stat')
1315 diff = self.diffopts.get('patch')
1311 diff = self.diffopts.get('patch')
1316 diffopts = patch.diffallopts(self.ui, self.diffopts)
1312 diffopts = patch.diffallopts(self.ui, self.diffopts)
1317 prev = self.repo.changelog.parents(node)[0]
1313 prev = self.repo.changelog.parents(node)[0]
1318 if stat:
1314 if stat:
1319 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1315 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1320 match=matchfn, stat=True)
1316 match=matchfn, stat=True)
1321 if diff:
1317 if diff:
1322 if stat:
1318 if stat:
1323 self.ui.write("\n")
1319 self.ui.write("\n")
1324 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1320 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1325 match=matchfn, stat=False)
1321 match=matchfn, stat=False)
1326 self.ui.write("\n")
1322 self.ui.write("\n")
1327
1323
1328 class jsonchangeset(changeset_printer):
1324 class jsonchangeset(changeset_printer):
1329 '''format changeset information.'''
1325 '''format changeset information.'''
1330
1326
1331 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1327 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1332 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1328 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1333 self.cache = {}
1329 self.cache = {}
1334 self._first = True
1330 self._first = True
1335
1331
1336 def close(self):
1332 def close(self):
1337 if not self._first:
1333 if not self._first:
1338 self.ui.write("\n]\n")
1334 self.ui.write("\n]\n")
1339 else:
1335 else:
1340 self.ui.write("[]\n")
1336 self.ui.write("[]\n")
1341
1337
1342 def _show(self, ctx, copies, matchfn, props):
1338 def _show(self, ctx, copies, matchfn, props):
1343 '''show a single changeset or file revision'''
1339 '''show a single changeset or file revision'''
1344 rev = ctx.rev()
1340 rev = ctx.rev()
1345 if rev is None:
1341 if rev is None:
1346 jrev = jnode = 'null'
1342 jrev = jnode = 'null'
1347 else:
1343 else:
1348 jrev = str(rev)
1344 jrev = str(rev)
1349 jnode = '"%s"' % hex(ctx.node())
1345 jnode = '"%s"' % hex(ctx.node())
1350 j = encoding.jsonescape
1346 j = encoding.jsonescape
1351
1347
1352 if self._first:
1348 if self._first:
1353 self.ui.write("[\n {")
1349 self.ui.write("[\n {")
1354 self._first = False
1350 self._first = False
1355 else:
1351 else:
1356 self.ui.write(",\n {")
1352 self.ui.write(",\n {")
1357
1353
1358 if self.ui.quiet:
1354 if self.ui.quiet:
1359 self.ui.write('\n "rev": %s' % jrev)
1355 self.ui.write('\n "rev": %s' % jrev)
1360 self.ui.write(',\n "node": %s' % jnode)
1356 self.ui.write(',\n "node": %s' % jnode)
1361 self.ui.write('\n }')
1357 self.ui.write('\n }')
1362 return
1358 return
1363
1359
1364 self.ui.write('\n "rev": %s' % jrev)
1360 self.ui.write('\n "rev": %s' % jrev)
1365 self.ui.write(',\n "node": %s' % jnode)
1361 self.ui.write(',\n "node": %s' % jnode)
1366 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1362 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1367 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1363 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1368 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1364 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1369 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1365 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1370 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1366 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1371
1367
1372 self.ui.write(',\n "bookmarks": [%s]' %
1368 self.ui.write(',\n "bookmarks": [%s]' %
1373 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1369 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1374 self.ui.write(',\n "tags": [%s]' %
1370 self.ui.write(',\n "tags": [%s]' %
1375 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1371 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1376 self.ui.write(',\n "parents": [%s]' %
1372 self.ui.write(',\n "parents": [%s]' %
1377 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1373 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1378
1374
1379 if self.ui.debugflag:
1375 if self.ui.debugflag:
1380 if rev is None:
1376 if rev is None:
1381 jmanifestnode = 'null'
1377 jmanifestnode = 'null'
1382 else:
1378 else:
1383 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1379 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1384 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1380 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1385
1381
1386 self.ui.write(',\n "extra": {%s}' %
1382 self.ui.write(',\n "extra": {%s}' %
1387 ", ".join('"%s": "%s"' % (j(k), j(v))
1383 ", ".join('"%s": "%s"' % (j(k), j(v))
1388 for k, v in ctx.extra().items()))
1384 for k, v in ctx.extra().items()))
1389
1385
1390 files = ctx.p1().status(ctx)
1386 files = ctx.p1().status(ctx)
1391 self.ui.write(',\n "modified": [%s]' %
1387 self.ui.write(',\n "modified": [%s]' %
1392 ", ".join('"%s"' % j(f) for f in files[0]))
1388 ", ".join('"%s"' % j(f) for f in files[0]))
1393 self.ui.write(',\n "added": [%s]' %
1389 self.ui.write(',\n "added": [%s]' %
1394 ", ".join('"%s"' % j(f) for f in files[1]))
1390 ", ".join('"%s"' % j(f) for f in files[1]))
1395 self.ui.write(',\n "removed": [%s]' %
1391 self.ui.write(',\n "removed": [%s]' %
1396 ", ".join('"%s"' % j(f) for f in files[2]))
1392 ", ".join('"%s"' % j(f) for f in files[2]))
1397
1393
1398 elif self.ui.verbose:
1394 elif self.ui.verbose:
1399 self.ui.write(',\n "files": [%s]' %
1395 self.ui.write(',\n "files": [%s]' %
1400 ", ".join('"%s"' % j(f) for f in ctx.files()))
1396 ", ".join('"%s"' % j(f) for f in ctx.files()))
1401
1397
1402 if copies:
1398 if copies:
1403 self.ui.write(',\n "copies": {%s}' %
1399 self.ui.write(',\n "copies": {%s}' %
1404 ", ".join('"%s": "%s"' % (j(k), j(v))
1400 ", ".join('"%s": "%s"' % (j(k), j(v))
1405 for k, v in copies))
1401 for k, v in copies))
1406
1402
1407 matchfn = self.matchfn
1403 matchfn = self.matchfn
1408 if matchfn:
1404 if matchfn:
1409 stat = self.diffopts.get('stat')
1405 stat = self.diffopts.get('stat')
1410 diff = self.diffopts.get('patch')
1406 diff = self.diffopts.get('patch')
1411 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1407 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1412 node, prev = ctx.node(), ctx.p1().node()
1408 node, prev = ctx.node(), ctx.p1().node()
1413 if stat:
1409 if stat:
1414 self.ui.pushbuffer()
1410 self.ui.pushbuffer()
1415 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1411 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1416 match=matchfn, stat=True)
1412 match=matchfn, stat=True)
1417 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1413 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1418 if diff:
1414 if diff:
1419 self.ui.pushbuffer()
1415 self.ui.pushbuffer()
1420 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1416 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1421 match=matchfn, stat=False)
1417 match=matchfn, stat=False)
1422 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1418 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1423
1419
1424 self.ui.write("\n }")
1420 self.ui.write("\n }")
1425
1421
1426 class changeset_templater(changeset_printer):
1422 class changeset_templater(changeset_printer):
1427 '''format changeset information.'''
1423 '''format changeset information.'''
1428
1424
1429 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1425 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1430 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1426 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1431 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1427 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1432 defaulttempl = {
1428 defaulttempl = {
1433 'parent': '{rev}:{node|formatnode} ',
1429 'parent': '{rev}:{node|formatnode} ',
1434 'manifest': '{rev}:{node|formatnode}',
1430 'manifest': '{rev}:{node|formatnode}',
1435 'file_copy': '{name} ({source})',
1431 'file_copy': '{name} ({source})',
1436 'extra': '{key}={value|stringescape}'
1432 'extra': '{key}={value|stringescape}'
1437 }
1433 }
1438 # filecopy is preserved for compatibility reasons
1434 # filecopy is preserved for compatibility reasons
1439 defaulttempl['filecopy'] = defaulttempl['file_copy']
1435 defaulttempl['filecopy'] = defaulttempl['file_copy']
1440 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1436 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1441 cache=defaulttempl)
1437 cache=defaulttempl)
1442 if tmpl:
1438 if tmpl:
1443 self.t.cache['changeset'] = tmpl
1439 self.t.cache['changeset'] = tmpl
1444
1440
1445 self.cache = {}
1441 self.cache = {}
1446
1442
1447 # find correct templates for current mode
1443 # find correct templates for current mode
1448 tmplmodes = [
1444 tmplmodes = [
1449 (True, None),
1445 (True, None),
1450 (self.ui.verbose, 'verbose'),
1446 (self.ui.verbose, 'verbose'),
1451 (self.ui.quiet, 'quiet'),
1447 (self.ui.quiet, 'quiet'),
1452 (self.ui.debugflag, 'debug'),
1448 (self.ui.debugflag, 'debug'),
1453 ]
1449 ]
1454
1450
1455 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1451 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1456 'docheader': '', 'docfooter': ''}
1452 'docheader': '', 'docfooter': ''}
1457 for mode, postfix in tmplmodes:
1453 for mode, postfix in tmplmodes:
1458 for t in self._parts:
1454 for t in self._parts:
1459 cur = t
1455 cur = t
1460 if postfix:
1456 if postfix:
1461 cur += "_" + postfix
1457 cur += "_" + postfix
1462 if mode and cur in self.t:
1458 if mode and cur in self.t:
1463 self._parts[t] = cur
1459 self._parts[t] = cur
1464
1460
1465 if self._parts['docheader']:
1461 if self._parts['docheader']:
1466 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1462 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1467
1463
1468 def close(self):
1464 def close(self):
1469 if self._parts['docfooter']:
1465 if self._parts['docfooter']:
1470 if not self.footer:
1466 if not self.footer:
1471 self.footer = ""
1467 self.footer = ""
1472 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1468 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1473 return super(changeset_templater, self).close()
1469 return super(changeset_templater, self).close()
1474
1470
1475 def _show(self, ctx, copies, matchfn, props):
1471 def _show(self, ctx, copies, matchfn, props):
1476 '''show a single changeset or file revision'''
1472 '''show a single changeset or file revision'''
1477 props = props.copy()
1473 props = props.copy()
1478 props.update(templatekw.keywords)
1474 props.update(templatekw.keywords)
1479 props['templ'] = self.t
1475 props['templ'] = self.t
1480 props['ctx'] = ctx
1476 props['ctx'] = ctx
1481 props['repo'] = self.repo
1477 props['repo'] = self.repo
1482 props['revcache'] = {'copies': copies}
1478 props['revcache'] = {'copies': copies}
1483 props['cache'] = self.cache
1479 props['cache'] = self.cache
1484
1480
1485 try:
1481 try:
1486 # write header
1482 # write header
1487 if self._parts['header']:
1483 if self._parts['header']:
1488 h = templater.stringify(self.t(self._parts['header'], **props))
1484 h = templater.stringify(self.t(self._parts['header'], **props))
1489 if self.buffered:
1485 if self.buffered:
1490 self.header[ctx.rev()] = h
1486 self.header[ctx.rev()] = h
1491 else:
1487 else:
1492 if self.lastheader != h:
1488 if self.lastheader != h:
1493 self.lastheader = h
1489 self.lastheader = h
1494 self.ui.write(h)
1490 self.ui.write(h)
1495
1491
1496 # write changeset metadata, then patch if requested
1492 # write changeset metadata, then patch if requested
1497 key = self._parts['changeset']
1493 key = self._parts['changeset']
1498 self.ui.write(templater.stringify(self.t(key, **props)))
1494 self.ui.write(templater.stringify(self.t(key, **props)))
1499 self.showpatch(ctx.node(), matchfn)
1495 self.showpatch(ctx.node(), matchfn)
1500
1496
1501 if self._parts['footer']:
1497 if self._parts['footer']:
1502 if not self.footer:
1498 if not self.footer:
1503 self.footer = templater.stringify(
1499 self.footer = templater.stringify(
1504 self.t(self._parts['footer'], **props))
1500 self.t(self._parts['footer'], **props))
1505 except KeyError as inst:
1501 except KeyError as inst:
1506 msg = _("%s: no key named '%s'")
1502 msg = _("%s: no key named '%s'")
1507 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1503 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1508 except SyntaxError as inst:
1504 except SyntaxError as inst:
1509 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1505 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1510
1506
1511 def gettemplate(ui, tmpl, style):
1507 def gettemplate(ui, tmpl, style):
1512 """
1508 """
1513 Find the template matching the given template spec or style.
1509 Find the template matching the given template spec or style.
1514 """
1510 """
1515
1511
1516 # ui settings
1512 # ui settings
1517 if not tmpl and not style: # template are stronger than style
1513 if not tmpl and not style: # template are stronger than style
1518 tmpl = ui.config('ui', 'logtemplate')
1514 tmpl = ui.config('ui', 'logtemplate')
1519 if tmpl:
1515 if tmpl:
1520 try:
1516 try:
1521 tmpl = templater.unquotestring(tmpl)
1517 tmpl = templater.unquotestring(tmpl)
1522 except SyntaxError:
1518 except SyntaxError:
1523 pass
1519 pass
1524 return tmpl, None
1520 return tmpl, None
1525 else:
1521 else:
1526 style = util.expandpath(ui.config('ui', 'style', ''))
1522 style = util.expandpath(ui.config('ui', 'style', ''))
1527
1523
1528 if not tmpl and style:
1524 if not tmpl and style:
1529 mapfile = style
1525 mapfile = style
1530 if not os.path.split(mapfile)[0]:
1526 if not os.path.split(mapfile)[0]:
1531 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1527 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1532 or templater.templatepath(mapfile))
1528 or templater.templatepath(mapfile))
1533 if mapname:
1529 if mapname:
1534 mapfile = mapname
1530 mapfile = mapname
1535 return None, mapfile
1531 return None, mapfile
1536
1532
1537 if not tmpl:
1533 if not tmpl:
1538 return None, None
1534 return None, None
1539
1535
1540 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1536 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1541
1537
1542 def show_changeset(ui, repo, opts, buffered=False):
1538 def show_changeset(ui, repo, opts, buffered=False):
1543 """show one changeset using template or regular display.
1539 """show one changeset using template or regular display.
1544
1540
1545 Display format will be the first non-empty hit of:
1541 Display format will be the first non-empty hit of:
1546 1. option 'template'
1542 1. option 'template'
1547 2. option 'style'
1543 2. option 'style'
1548 3. [ui] setting 'logtemplate'
1544 3. [ui] setting 'logtemplate'
1549 4. [ui] setting 'style'
1545 4. [ui] setting 'style'
1550 If all of these values are either the unset or the empty string,
1546 If all of these values are either the unset or the empty string,
1551 regular display via changeset_printer() is done.
1547 regular display via changeset_printer() is done.
1552 """
1548 """
1553 # options
1549 # options
1554 matchfn = None
1550 matchfn = None
1555 if opts.get('patch') or opts.get('stat'):
1551 if opts.get('patch') or opts.get('stat'):
1556 matchfn = scmutil.matchall(repo)
1552 matchfn = scmutil.matchall(repo)
1557
1553
1558 if opts.get('template') == 'json':
1554 if opts.get('template') == 'json':
1559 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1555 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1560
1556
1561 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1557 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1562
1558
1563 if not tmpl and not mapfile:
1559 if not tmpl and not mapfile:
1564 return changeset_printer(ui, repo, matchfn, opts, buffered)
1560 return changeset_printer(ui, repo, matchfn, opts, buffered)
1565
1561
1566 try:
1562 try:
1567 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1563 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1568 buffered)
1564 buffered)
1569 except SyntaxError as inst:
1565 except SyntaxError as inst:
1570 raise util.Abort(inst.args[0])
1566 raise util.Abort(inst.args[0])
1571 return t
1567 return t
1572
1568
1573 def showmarker(ui, marker):
1569 def showmarker(ui, marker):
1574 """utility function to display obsolescence marker in a readable way
1570 """utility function to display obsolescence marker in a readable way
1575
1571
1576 To be used by debug function."""
1572 To be used by debug function."""
1577 ui.write(hex(marker.precnode()))
1573 ui.write(hex(marker.precnode()))
1578 for repl in marker.succnodes():
1574 for repl in marker.succnodes():
1579 ui.write(' ')
1575 ui.write(' ')
1580 ui.write(hex(repl))
1576 ui.write(hex(repl))
1581 ui.write(' %X ' % marker.flags())
1577 ui.write(' %X ' % marker.flags())
1582 parents = marker.parentnodes()
1578 parents = marker.parentnodes()
1583 if parents is not None:
1579 if parents is not None:
1584 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1580 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1585 ui.write('(%s) ' % util.datestr(marker.date()))
1581 ui.write('(%s) ' % util.datestr(marker.date()))
1586 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1582 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1587 sorted(marker.metadata().items())
1583 sorted(marker.metadata().items())
1588 if t[0] != 'date')))
1584 if t[0] != 'date')))
1589 ui.write('\n')
1585 ui.write('\n')
1590
1586
1591 def finddate(ui, repo, date):
1587 def finddate(ui, repo, date):
1592 """Find the tipmost changeset that matches the given date spec"""
1588 """Find the tipmost changeset that matches the given date spec"""
1593
1589
1594 df = util.matchdate(date)
1590 df = util.matchdate(date)
1595 m = scmutil.matchall(repo)
1591 m = scmutil.matchall(repo)
1596 results = {}
1592 results = {}
1597
1593
1598 def prep(ctx, fns):
1594 def prep(ctx, fns):
1599 d = ctx.date()
1595 d = ctx.date()
1600 if df(d[0]):
1596 if df(d[0]):
1601 results[ctx.rev()] = d
1597 results[ctx.rev()] = d
1602
1598
1603 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1599 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1604 rev = ctx.rev()
1600 rev = ctx.rev()
1605 if rev in results:
1601 if rev in results:
1606 ui.status(_("found revision %s from %s\n") %
1602 ui.status(_("found revision %s from %s\n") %
1607 (rev, util.datestr(results[rev])))
1603 (rev, util.datestr(results[rev])))
1608 return str(rev)
1604 return str(rev)
1609
1605
1610 raise util.Abort(_("revision matching date not found"))
1606 raise util.Abort(_("revision matching date not found"))
1611
1607
1612 def increasingwindows(windowsize=8, sizelimit=512):
1608 def increasingwindows(windowsize=8, sizelimit=512):
1613 while True:
1609 while True:
1614 yield windowsize
1610 yield windowsize
1615 if windowsize < sizelimit:
1611 if windowsize < sizelimit:
1616 windowsize *= 2
1612 windowsize *= 2
1617
1613
1618 class FileWalkError(Exception):
1614 class FileWalkError(Exception):
1619 pass
1615 pass
1620
1616
1621 def walkfilerevs(repo, match, follow, revs, fncache):
1617 def walkfilerevs(repo, match, follow, revs, fncache):
1622 '''Walks the file history for the matched files.
1618 '''Walks the file history for the matched files.
1623
1619
1624 Returns the changeset revs that are involved in the file history.
1620 Returns the changeset revs that are involved in the file history.
1625
1621
1626 Throws FileWalkError if the file history can't be walked using
1622 Throws FileWalkError if the file history can't be walked using
1627 filelogs alone.
1623 filelogs alone.
1628 '''
1624 '''
1629 wanted = set()
1625 wanted = set()
1630 copies = []
1626 copies = []
1631 minrev, maxrev = min(revs), max(revs)
1627 minrev, maxrev = min(revs), max(revs)
1632 def filerevgen(filelog, last):
1628 def filerevgen(filelog, last):
1633 """
1629 """
1634 Only files, no patterns. Check the history of each file.
1630 Only files, no patterns. Check the history of each file.
1635
1631
1636 Examines filelog entries within minrev, maxrev linkrev range
1632 Examines filelog entries within minrev, maxrev linkrev range
1637 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1633 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1638 tuples in backwards order
1634 tuples in backwards order
1639 """
1635 """
1640 cl_count = len(repo)
1636 cl_count = len(repo)
1641 revs = []
1637 revs = []
1642 for j in xrange(0, last + 1):
1638 for j in xrange(0, last + 1):
1643 linkrev = filelog.linkrev(j)
1639 linkrev = filelog.linkrev(j)
1644 if linkrev < minrev:
1640 if linkrev < minrev:
1645 continue
1641 continue
1646 # only yield rev for which we have the changelog, it can
1642 # only yield rev for which we have the changelog, it can
1647 # happen while doing "hg log" during a pull or commit
1643 # happen while doing "hg log" during a pull or commit
1648 if linkrev >= cl_count:
1644 if linkrev >= cl_count:
1649 break
1645 break
1650
1646
1651 parentlinkrevs = []
1647 parentlinkrevs = []
1652 for p in filelog.parentrevs(j):
1648 for p in filelog.parentrevs(j):
1653 if p != nullrev:
1649 if p != nullrev:
1654 parentlinkrevs.append(filelog.linkrev(p))
1650 parentlinkrevs.append(filelog.linkrev(p))
1655 n = filelog.node(j)
1651 n = filelog.node(j)
1656 revs.append((linkrev, parentlinkrevs,
1652 revs.append((linkrev, parentlinkrevs,
1657 follow and filelog.renamed(n)))
1653 follow and filelog.renamed(n)))
1658
1654
1659 return reversed(revs)
1655 return reversed(revs)
1660 def iterfiles():
1656 def iterfiles():
1661 pctx = repo['.']
1657 pctx = repo['.']
1662 for filename in match.files():
1658 for filename in match.files():
1663 if follow:
1659 if follow:
1664 if filename not in pctx:
1660 if filename not in pctx:
1665 raise util.Abort(_('cannot follow file not in parent '
1661 raise util.Abort(_('cannot follow file not in parent '
1666 'revision: "%s"') % filename)
1662 'revision: "%s"') % filename)
1667 yield filename, pctx[filename].filenode()
1663 yield filename, pctx[filename].filenode()
1668 else:
1664 else:
1669 yield filename, None
1665 yield filename, None
1670 for filename_node in copies:
1666 for filename_node in copies:
1671 yield filename_node
1667 yield filename_node
1672
1668
1673 for file_, node in iterfiles():
1669 for file_, node in iterfiles():
1674 filelog = repo.file(file_)
1670 filelog = repo.file(file_)
1675 if not len(filelog):
1671 if not len(filelog):
1676 if node is None:
1672 if node is None:
1677 # A zero count may be a directory or deleted file, so
1673 # A zero count may be a directory or deleted file, so
1678 # try to find matching entries on the slow path.
1674 # try to find matching entries on the slow path.
1679 if follow:
1675 if follow:
1680 raise util.Abort(
1676 raise util.Abort(
1681 _('cannot follow nonexistent file: "%s"') % file_)
1677 _('cannot follow nonexistent file: "%s"') % file_)
1682 raise FileWalkError("Cannot walk via filelog")
1678 raise FileWalkError("Cannot walk via filelog")
1683 else:
1679 else:
1684 continue
1680 continue
1685
1681
1686 if node is None:
1682 if node is None:
1687 last = len(filelog) - 1
1683 last = len(filelog) - 1
1688 else:
1684 else:
1689 last = filelog.rev(node)
1685 last = filelog.rev(node)
1690
1686
1691 # keep track of all ancestors of the file
1687 # keep track of all ancestors of the file
1692 ancestors = set([filelog.linkrev(last)])
1688 ancestors = set([filelog.linkrev(last)])
1693
1689
1694 # iterate from latest to oldest revision
1690 # iterate from latest to oldest revision
1695 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1691 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1696 if not follow:
1692 if not follow:
1697 if rev > maxrev:
1693 if rev > maxrev:
1698 continue
1694 continue
1699 else:
1695 else:
1700 # Note that last might not be the first interesting
1696 # Note that last might not be the first interesting
1701 # rev to us:
1697 # rev to us:
1702 # if the file has been changed after maxrev, we'll
1698 # if the file has been changed after maxrev, we'll
1703 # have linkrev(last) > maxrev, and we still need
1699 # have linkrev(last) > maxrev, and we still need
1704 # to explore the file graph
1700 # to explore the file graph
1705 if rev not in ancestors:
1701 if rev not in ancestors:
1706 continue
1702 continue
1707 # XXX insert 1327 fix here
1703 # XXX insert 1327 fix here
1708 if flparentlinkrevs:
1704 if flparentlinkrevs:
1709 ancestors.update(flparentlinkrevs)
1705 ancestors.update(flparentlinkrevs)
1710
1706
1711 fncache.setdefault(rev, []).append(file_)
1707 fncache.setdefault(rev, []).append(file_)
1712 wanted.add(rev)
1708 wanted.add(rev)
1713 if copied:
1709 if copied:
1714 copies.append(copied)
1710 copies.append(copied)
1715
1711
1716 return wanted
1712 return wanted
1717
1713
1718 class _followfilter(object):
1714 class _followfilter(object):
1719 def __init__(self, repo, onlyfirst=False):
1715 def __init__(self, repo, onlyfirst=False):
1720 self.repo = repo
1716 self.repo = repo
1721 self.startrev = nullrev
1717 self.startrev = nullrev
1722 self.roots = set()
1718 self.roots = set()
1723 self.onlyfirst = onlyfirst
1719 self.onlyfirst = onlyfirst
1724
1720
1725 def match(self, rev):
1721 def match(self, rev):
1726 def realparents(rev):
1722 def realparents(rev):
1727 if self.onlyfirst:
1723 if self.onlyfirst:
1728 return self.repo.changelog.parentrevs(rev)[0:1]
1724 return self.repo.changelog.parentrevs(rev)[0:1]
1729 else:
1725 else:
1730 return filter(lambda x: x != nullrev,
1726 return filter(lambda x: x != nullrev,
1731 self.repo.changelog.parentrevs(rev))
1727 self.repo.changelog.parentrevs(rev))
1732
1728
1733 if self.startrev == nullrev:
1729 if self.startrev == nullrev:
1734 self.startrev = rev
1730 self.startrev = rev
1735 return True
1731 return True
1736
1732
1737 if rev > self.startrev:
1733 if rev > self.startrev:
1738 # forward: all descendants
1734 # forward: all descendants
1739 if not self.roots:
1735 if not self.roots:
1740 self.roots.add(self.startrev)
1736 self.roots.add(self.startrev)
1741 for parent in realparents(rev):
1737 for parent in realparents(rev):
1742 if parent in self.roots:
1738 if parent in self.roots:
1743 self.roots.add(rev)
1739 self.roots.add(rev)
1744 return True
1740 return True
1745 else:
1741 else:
1746 # backwards: all parents
1742 # backwards: all parents
1747 if not self.roots:
1743 if not self.roots:
1748 self.roots.update(realparents(self.startrev))
1744 self.roots.update(realparents(self.startrev))
1749 if rev in self.roots:
1745 if rev in self.roots:
1750 self.roots.remove(rev)
1746 self.roots.remove(rev)
1751 self.roots.update(realparents(rev))
1747 self.roots.update(realparents(rev))
1752 return True
1748 return True
1753
1749
1754 return False
1750 return False
1755
1751
1756 def walkchangerevs(repo, match, opts, prepare):
1752 def walkchangerevs(repo, match, opts, prepare):
1757 '''Iterate over files and the revs in which they changed.
1753 '''Iterate over files and the revs in which they changed.
1758
1754
1759 Callers most commonly need to iterate backwards over the history
1755 Callers most commonly need to iterate backwards over the history
1760 in which they are interested. Doing so has awful (quadratic-looking)
1756 in which they are interested. Doing so has awful (quadratic-looking)
1761 performance, so we use iterators in a "windowed" way.
1757 performance, so we use iterators in a "windowed" way.
1762
1758
1763 We walk a window of revisions in the desired order. Within the
1759 We walk a window of revisions in the desired order. Within the
1764 window, we first walk forwards to gather data, then in the desired
1760 window, we first walk forwards to gather data, then in the desired
1765 order (usually backwards) to display it.
1761 order (usually backwards) to display it.
1766
1762
1767 This function returns an iterator yielding contexts. Before
1763 This function returns an iterator yielding contexts. Before
1768 yielding each context, the iterator will first call the prepare
1764 yielding each context, the iterator will first call the prepare
1769 function on each context in the window in forward order.'''
1765 function on each context in the window in forward order.'''
1770
1766
1771 follow = opts.get('follow') or opts.get('follow_first')
1767 follow = opts.get('follow') or opts.get('follow_first')
1772 revs = _logrevs(repo, opts)
1768 revs = _logrevs(repo, opts)
1773 if not revs:
1769 if not revs:
1774 return []
1770 return []
1775 wanted = set()
1771 wanted = set()
1776 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1772 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1777 opts.get('removed'))
1773 opts.get('removed'))
1778 fncache = {}
1774 fncache = {}
1779 change = repo.changectx
1775 change = repo.changectx
1780
1776
1781 # First step is to fill wanted, the set of revisions that we want to yield.
1777 # First step is to fill wanted, the set of revisions that we want to yield.
1782 # When it does not induce extra cost, we also fill fncache for revisions in
1778 # When it does not induce extra cost, we also fill fncache for revisions in
1783 # wanted: a cache of filenames that were changed (ctx.files()) and that
1779 # wanted: a cache of filenames that were changed (ctx.files()) and that
1784 # match the file filtering conditions.
1780 # match the file filtering conditions.
1785
1781
1786 if match.always():
1782 if match.always():
1787 # No files, no patterns. Display all revs.
1783 # No files, no patterns. Display all revs.
1788 wanted = revs
1784 wanted = revs
1789 elif not slowpath:
1785 elif not slowpath:
1790 # We only have to read through the filelog to find wanted revisions
1786 # We only have to read through the filelog to find wanted revisions
1791
1787
1792 try:
1788 try:
1793 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1789 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1794 except FileWalkError:
1790 except FileWalkError:
1795 slowpath = True
1791 slowpath = True
1796
1792
1797 # We decided to fall back to the slowpath because at least one
1793 # We decided to fall back to the slowpath because at least one
1798 # of the paths was not a file. Check to see if at least one of them
1794 # of the paths was not a file. Check to see if at least one of them
1799 # existed in history, otherwise simply return
1795 # existed in history, otherwise simply return
1800 for path in match.files():
1796 for path in match.files():
1801 if path == '.' or path in repo.store:
1797 if path == '.' or path in repo.store:
1802 break
1798 break
1803 else:
1799 else:
1804 return []
1800 return []
1805
1801
1806 if slowpath:
1802 if slowpath:
1807 # We have to read the changelog to match filenames against
1803 # We have to read the changelog to match filenames against
1808 # changed files
1804 # changed files
1809
1805
1810 if follow:
1806 if follow:
1811 raise util.Abort(_('can only follow copies/renames for explicit '
1807 raise util.Abort(_('can only follow copies/renames for explicit '
1812 'filenames'))
1808 'filenames'))
1813
1809
1814 # The slow path checks files modified in every changeset.
1810 # The slow path checks files modified in every changeset.
1815 # This is really slow on large repos, so compute the set lazily.
1811 # This is really slow on large repos, so compute the set lazily.
1816 class lazywantedset(object):
1812 class lazywantedset(object):
1817 def __init__(self):
1813 def __init__(self):
1818 self.set = set()
1814 self.set = set()
1819 self.revs = set(revs)
1815 self.revs = set(revs)
1820
1816
1821 # No need to worry about locality here because it will be accessed
1817 # No need to worry about locality here because it will be accessed
1822 # in the same order as the increasing window below.
1818 # in the same order as the increasing window below.
1823 def __contains__(self, value):
1819 def __contains__(self, value):
1824 if value in self.set:
1820 if value in self.set:
1825 return True
1821 return True
1826 elif not value in self.revs:
1822 elif not value in self.revs:
1827 return False
1823 return False
1828 else:
1824 else:
1829 self.revs.discard(value)
1825 self.revs.discard(value)
1830 ctx = change(value)
1826 ctx = change(value)
1831 matches = filter(match, ctx.files())
1827 matches = filter(match, ctx.files())
1832 if matches:
1828 if matches:
1833 fncache[value] = matches
1829 fncache[value] = matches
1834 self.set.add(value)
1830 self.set.add(value)
1835 return True
1831 return True
1836 return False
1832 return False
1837
1833
1838 def discard(self, value):
1834 def discard(self, value):
1839 self.revs.discard(value)
1835 self.revs.discard(value)
1840 self.set.discard(value)
1836 self.set.discard(value)
1841
1837
1842 wanted = lazywantedset()
1838 wanted = lazywantedset()
1843
1839
1844 # it might be worthwhile to do this in the iterator if the rev range
1840 # it might be worthwhile to do this in the iterator if the rev range
1845 # is descending and the prune args are all within that range
1841 # is descending and the prune args are all within that range
1846 for rev in opts.get('prune', ()):
1842 for rev in opts.get('prune', ()):
1847 rev = repo[rev].rev()
1843 rev = repo[rev].rev()
1848 ff = _followfilter(repo)
1844 ff = _followfilter(repo)
1849 stop = min(revs[0], revs[-1])
1845 stop = min(revs[0], revs[-1])
1850 for x in xrange(rev, stop - 1, -1):
1846 for x in xrange(rev, stop - 1, -1):
1851 if ff.match(x):
1847 if ff.match(x):
1852 wanted = wanted - [x]
1848 wanted = wanted - [x]
1853
1849
1854 # Now that wanted is correctly initialized, we can iterate over the
1850 # Now that wanted is correctly initialized, we can iterate over the
1855 # revision range, yielding only revisions in wanted.
1851 # revision range, yielding only revisions in wanted.
1856 def iterate():
1852 def iterate():
1857 if follow and match.always():
1853 if follow and match.always():
1858 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1854 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1859 def want(rev):
1855 def want(rev):
1860 return ff.match(rev) and rev in wanted
1856 return ff.match(rev) and rev in wanted
1861 else:
1857 else:
1862 def want(rev):
1858 def want(rev):
1863 return rev in wanted
1859 return rev in wanted
1864
1860
1865 it = iter(revs)
1861 it = iter(revs)
1866 stopiteration = False
1862 stopiteration = False
1867 for windowsize in increasingwindows():
1863 for windowsize in increasingwindows():
1868 nrevs = []
1864 nrevs = []
1869 for i in xrange(windowsize):
1865 for i in xrange(windowsize):
1870 rev = next(it, None)
1866 rev = next(it, None)
1871 if rev is None:
1867 if rev is None:
1872 stopiteration = True
1868 stopiteration = True
1873 break
1869 break
1874 elif want(rev):
1870 elif want(rev):
1875 nrevs.append(rev)
1871 nrevs.append(rev)
1876 for rev in sorted(nrevs):
1872 for rev in sorted(nrevs):
1877 fns = fncache.get(rev)
1873 fns = fncache.get(rev)
1878 ctx = change(rev)
1874 ctx = change(rev)
1879 if not fns:
1875 if not fns:
1880 def fns_generator():
1876 def fns_generator():
1881 for f in ctx.files():
1877 for f in ctx.files():
1882 if match(f):
1878 if match(f):
1883 yield f
1879 yield f
1884 fns = fns_generator()
1880 fns = fns_generator()
1885 prepare(ctx, fns)
1881 prepare(ctx, fns)
1886 for rev in nrevs:
1882 for rev in nrevs:
1887 yield change(rev)
1883 yield change(rev)
1888
1884
1889 if stopiteration:
1885 if stopiteration:
1890 break
1886 break
1891
1887
1892 return iterate()
1888 return iterate()
1893
1889
1894 def _makefollowlogfilematcher(repo, files, followfirst):
1890 def _makefollowlogfilematcher(repo, files, followfirst):
1895 # When displaying a revision with --patch --follow FILE, we have
1891 # When displaying a revision with --patch --follow FILE, we have
1896 # to know which file of the revision must be diffed. With
1892 # to know which file of the revision must be diffed. With
1897 # --follow, we want the names of the ancestors of FILE in the
1893 # --follow, we want the names of the ancestors of FILE in the
1898 # revision, stored in "fcache". "fcache" is populated by
1894 # revision, stored in "fcache". "fcache" is populated by
1899 # reproducing the graph traversal already done by --follow revset
1895 # reproducing the graph traversal already done by --follow revset
1900 # and relating linkrevs to file names (which is not "correct" but
1896 # and relating linkrevs to file names (which is not "correct" but
1901 # good enough).
1897 # good enough).
1902 fcache = {}
1898 fcache = {}
1903 fcacheready = [False]
1899 fcacheready = [False]
1904 pctx = repo['.']
1900 pctx = repo['.']
1905
1901
1906 def populate():
1902 def populate():
1907 for fn in files:
1903 for fn in files:
1908 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1904 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1909 for c in i:
1905 for c in i:
1910 fcache.setdefault(c.linkrev(), set()).add(c.path())
1906 fcache.setdefault(c.linkrev(), set()).add(c.path())
1911
1907
1912 def filematcher(rev):
1908 def filematcher(rev):
1913 if not fcacheready[0]:
1909 if not fcacheready[0]:
1914 # Lazy initialization
1910 # Lazy initialization
1915 fcacheready[0] = True
1911 fcacheready[0] = True
1916 populate()
1912 populate()
1917 return scmutil.matchfiles(repo, fcache.get(rev, []))
1913 return scmutil.matchfiles(repo, fcache.get(rev, []))
1918
1914
1919 return filematcher
1915 return filematcher
1920
1916
1921 def _makenofollowlogfilematcher(repo, pats, opts):
1917 def _makenofollowlogfilematcher(repo, pats, opts):
1922 '''hook for extensions to override the filematcher for non-follow cases'''
1918 '''hook for extensions to override the filematcher for non-follow cases'''
1923 return None
1919 return None
1924
1920
1925 def _makelogrevset(repo, pats, opts, revs):
1921 def _makelogrevset(repo, pats, opts, revs):
1926 """Return (expr, filematcher) where expr is a revset string built
1922 """Return (expr, filematcher) where expr is a revset string built
1927 from log options and file patterns or None. If --stat or --patch
1923 from log options and file patterns or None. If --stat or --patch
1928 are not passed filematcher is None. Otherwise it is a callable
1924 are not passed filematcher is None. Otherwise it is a callable
1929 taking a revision number and returning a match objects filtering
1925 taking a revision number and returning a match objects filtering
1930 the files to be detailed when displaying the revision.
1926 the files to be detailed when displaying the revision.
1931 """
1927 """
1932 opt2revset = {
1928 opt2revset = {
1933 'no_merges': ('not merge()', None),
1929 'no_merges': ('not merge()', None),
1934 'only_merges': ('merge()', None),
1930 'only_merges': ('merge()', None),
1935 '_ancestors': ('ancestors(%(val)s)', None),
1931 '_ancestors': ('ancestors(%(val)s)', None),
1936 '_fancestors': ('_firstancestors(%(val)s)', None),
1932 '_fancestors': ('_firstancestors(%(val)s)', None),
1937 '_descendants': ('descendants(%(val)s)', None),
1933 '_descendants': ('descendants(%(val)s)', None),
1938 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1934 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1939 '_matchfiles': ('_matchfiles(%(val)s)', None),
1935 '_matchfiles': ('_matchfiles(%(val)s)', None),
1940 'date': ('date(%(val)r)', None),
1936 'date': ('date(%(val)r)', None),
1941 'branch': ('branch(%(val)r)', ' or '),
1937 'branch': ('branch(%(val)r)', ' or '),
1942 '_patslog': ('filelog(%(val)r)', ' or '),
1938 '_patslog': ('filelog(%(val)r)', ' or '),
1943 '_patsfollow': ('follow(%(val)r)', ' or '),
1939 '_patsfollow': ('follow(%(val)r)', ' or '),
1944 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1940 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1945 'keyword': ('keyword(%(val)r)', ' or '),
1941 'keyword': ('keyword(%(val)r)', ' or '),
1946 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1942 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1947 'user': ('user(%(val)r)', ' or '),
1943 'user': ('user(%(val)r)', ' or '),
1948 }
1944 }
1949
1945
1950 opts = dict(opts)
1946 opts = dict(opts)
1951 # follow or not follow?
1947 # follow or not follow?
1952 follow = opts.get('follow') or opts.get('follow_first')
1948 follow = opts.get('follow') or opts.get('follow_first')
1953 if opts.get('follow_first'):
1949 if opts.get('follow_first'):
1954 followfirst = 1
1950 followfirst = 1
1955 else:
1951 else:
1956 followfirst = 0
1952 followfirst = 0
1957 # --follow with FILE behavior depends on revs...
1953 # --follow with FILE behavior depends on revs...
1958 it = iter(revs)
1954 it = iter(revs)
1959 startrev = it.next()
1955 startrev = it.next()
1960 followdescendants = startrev < next(it, startrev)
1956 followdescendants = startrev < next(it, startrev)
1961
1957
1962 # branch and only_branch are really aliases and must be handled at
1958 # branch and only_branch are really aliases and must be handled at
1963 # the same time
1959 # the same time
1964 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1960 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1965 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1961 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1966 # pats/include/exclude are passed to match.match() directly in
1962 # pats/include/exclude are passed to match.match() directly in
1967 # _matchfiles() revset but walkchangerevs() builds its matcher with
1963 # _matchfiles() revset but walkchangerevs() builds its matcher with
1968 # scmutil.match(). The difference is input pats are globbed on
1964 # scmutil.match(). The difference is input pats are globbed on
1969 # platforms without shell expansion (windows).
1965 # platforms without shell expansion (windows).
1970 wctx = repo[None]
1966 wctx = repo[None]
1971 match, pats = scmutil.matchandpats(wctx, pats, opts)
1967 match, pats = scmutil.matchandpats(wctx, pats, opts)
1972 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1968 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1973 opts.get('removed'))
1969 opts.get('removed'))
1974 if not slowpath:
1970 if not slowpath:
1975 for f in match.files():
1971 for f in match.files():
1976 if follow and f not in wctx:
1972 if follow and f not in wctx:
1977 # If the file exists, it may be a directory, so let it
1973 # If the file exists, it may be a directory, so let it
1978 # take the slow path.
1974 # take the slow path.
1979 if os.path.exists(repo.wjoin(f)):
1975 if os.path.exists(repo.wjoin(f)):
1980 slowpath = True
1976 slowpath = True
1981 continue
1977 continue
1982 else:
1978 else:
1983 raise util.Abort(_('cannot follow file not in parent '
1979 raise util.Abort(_('cannot follow file not in parent '
1984 'revision: "%s"') % f)
1980 'revision: "%s"') % f)
1985 filelog = repo.file(f)
1981 filelog = repo.file(f)
1986 if not filelog:
1982 if not filelog:
1987 # A zero count may be a directory or deleted file, so
1983 # A zero count may be a directory or deleted file, so
1988 # try to find matching entries on the slow path.
1984 # try to find matching entries on the slow path.
1989 if follow:
1985 if follow:
1990 raise util.Abort(
1986 raise util.Abort(
1991 _('cannot follow nonexistent file: "%s"') % f)
1987 _('cannot follow nonexistent file: "%s"') % f)
1992 slowpath = True
1988 slowpath = True
1993
1989
1994 # We decided to fall back to the slowpath because at least one
1990 # We decided to fall back to the slowpath because at least one
1995 # of the paths was not a file. Check to see if at least one of them
1991 # of the paths was not a file. Check to see if at least one of them
1996 # existed in history - in that case, we'll continue down the
1992 # existed in history - in that case, we'll continue down the
1997 # slowpath; otherwise, we can turn off the slowpath
1993 # slowpath; otherwise, we can turn off the slowpath
1998 if slowpath:
1994 if slowpath:
1999 for path in match.files():
1995 for path in match.files():
2000 if path == '.' or path in repo.store:
1996 if path == '.' or path in repo.store:
2001 break
1997 break
2002 else:
1998 else:
2003 slowpath = False
1999 slowpath = False
2004
2000
2005 fpats = ('_patsfollow', '_patsfollowfirst')
2001 fpats = ('_patsfollow', '_patsfollowfirst')
2006 fnopats = (('_ancestors', '_fancestors'),
2002 fnopats = (('_ancestors', '_fancestors'),
2007 ('_descendants', '_fdescendants'))
2003 ('_descendants', '_fdescendants'))
2008 if slowpath:
2004 if slowpath:
2009 # See walkchangerevs() slow path.
2005 # See walkchangerevs() slow path.
2010 #
2006 #
2011 # pats/include/exclude cannot be represented as separate
2007 # pats/include/exclude cannot be represented as separate
2012 # revset expressions as their filtering logic applies at file
2008 # revset expressions as their filtering logic applies at file
2013 # level. For instance "-I a -X a" matches a revision touching
2009 # level. For instance "-I a -X a" matches a revision touching
2014 # "a" and "b" while "file(a) and not file(b)" does
2010 # "a" and "b" while "file(a) and not file(b)" does
2015 # not. Besides, filesets are evaluated against the working
2011 # not. Besides, filesets are evaluated against the working
2016 # directory.
2012 # directory.
2017 matchargs = ['r:', 'd:relpath']
2013 matchargs = ['r:', 'd:relpath']
2018 for p in pats:
2014 for p in pats:
2019 matchargs.append('p:' + p)
2015 matchargs.append('p:' + p)
2020 for p in opts.get('include', []):
2016 for p in opts.get('include', []):
2021 matchargs.append('i:' + p)
2017 matchargs.append('i:' + p)
2022 for p in opts.get('exclude', []):
2018 for p in opts.get('exclude', []):
2023 matchargs.append('x:' + p)
2019 matchargs.append('x:' + p)
2024 matchargs = ','.join(('%r' % p) for p in matchargs)
2020 matchargs = ','.join(('%r' % p) for p in matchargs)
2025 opts['_matchfiles'] = matchargs
2021 opts['_matchfiles'] = matchargs
2026 if follow:
2022 if follow:
2027 opts[fnopats[0][followfirst]] = '.'
2023 opts[fnopats[0][followfirst]] = '.'
2028 else:
2024 else:
2029 if follow:
2025 if follow:
2030 if pats:
2026 if pats:
2031 # follow() revset interprets its file argument as a
2027 # follow() revset interprets its file argument as a
2032 # manifest entry, so use match.files(), not pats.
2028 # manifest entry, so use match.files(), not pats.
2033 opts[fpats[followfirst]] = list(match.files())
2029 opts[fpats[followfirst]] = list(match.files())
2034 else:
2030 else:
2035 op = fnopats[followdescendants][followfirst]
2031 op = fnopats[followdescendants][followfirst]
2036 opts[op] = 'rev(%d)' % startrev
2032 opts[op] = 'rev(%d)' % startrev
2037 else:
2033 else:
2038 opts['_patslog'] = list(pats)
2034 opts['_patslog'] = list(pats)
2039
2035
2040 filematcher = None
2036 filematcher = None
2041 if opts.get('patch') or opts.get('stat'):
2037 if opts.get('patch') or opts.get('stat'):
2042 # When following files, track renames via a special matcher.
2038 # When following files, track renames via a special matcher.
2043 # If we're forced to take the slowpath it means we're following
2039 # If we're forced to take the slowpath it means we're following
2044 # at least one pattern/directory, so don't bother with rename tracking.
2040 # at least one pattern/directory, so don't bother with rename tracking.
2045 if follow and not match.always() and not slowpath:
2041 if follow and not match.always() and not slowpath:
2046 # _makefollowlogfilematcher expects its files argument to be
2042 # _makefollowlogfilematcher expects its files argument to be
2047 # relative to the repo root, so use match.files(), not pats.
2043 # relative to the repo root, so use match.files(), not pats.
2048 filematcher = _makefollowlogfilematcher(repo, match.files(),
2044 filematcher = _makefollowlogfilematcher(repo, match.files(),
2049 followfirst)
2045 followfirst)
2050 else:
2046 else:
2051 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2047 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2052 if filematcher is None:
2048 if filematcher is None:
2053 filematcher = lambda rev: match
2049 filematcher = lambda rev: match
2054
2050
2055 expr = []
2051 expr = []
2056 for op, val in sorted(opts.iteritems()):
2052 for op, val in sorted(opts.iteritems()):
2057 if not val:
2053 if not val:
2058 continue
2054 continue
2059 if op not in opt2revset:
2055 if op not in opt2revset:
2060 continue
2056 continue
2061 revop, andor = opt2revset[op]
2057 revop, andor = opt2revset[op]
2062 if '%(val)' not in revop:
2058 if '%(val)' not in revop:
2063 expr.append(revop)
2059 expr.append(revop)
2064 else:
2060 else:
2065 if not isinstance(val, list):
2061 if not isinstance(val, list):
2066 e = revop % {'val': val}
2062 e = revop % {'val': val}
2067 else:
2063 else:
2068 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2064 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2069 expr.append(e)
2065 expr.append(e)
2070
2066
2071 if expr:
2067 if expr:
2072 expr = '(' + ' and '.join(expr) + ')'
2068 expr = '(' + ' and '.join(expr) + ')'
2073 else:
2069 else:
2074 expr = None
2070 expr = None
2075 return expr, filematcher
2071 return expr, filematcher
2076
2072
2077 def _logrevs(repo, opts):
2073 def _logrevs(repo, opts):
2078 # Default --rev value depends on --follow but --follow behavior
2074 # Default --rev value depends on --follow but --follow behavior
2079 # depends on revisions resolved from --rev...
2075 # depends on revisions resolved from --rev...
2080 follow = opts.get('follow') or opts.get('follow_first')
2076 follow = opts.get('follow') or opts.get('follow_first')
2081 if opts.get('rev'):
2077 if opts.get('rev'):
2082 revs = scmutil.revrange(repo, opts['rev'])
2078 revs = scmutil.revrange(repo, opts['rev'])
2083 elif follow and repo.dirstate.p1() == nullid:
2079 elif follow and repo.dirstate.p1() == nullid:
2084 revs = revset.baseset()
2080 revs = revset.baseset()
2085 elif follow:
2081 elif follow:
2086 revs = repo.revs('reverse(:.)')
2082 revs = repo.revs('reverse(:.)')
2087 else:
2083 else:
2088 revs = revset.spanset(repo)
2084 revs = revset.spanset(repo)
2089 revs.reverse()
2085 revs.reverse()
2090 return revs
2086 return revs
2091
2087
2092 def getgraphlogrevs(repo, pats, opts):
2088 def getgraphlogrevs(repo, pats, opts):
2093 """Return (revs, expr, filematcher) where revs is an iterable of
2089 """Return (revs, expr, filematcher) where revs is an iterable of
2094 revision numbers, expr is a revset string built from log options
2090 revision numbers, expr is a revset string built from log options
2095 and file patterns or None, and used to filter 'revs'. If --stat or
2091 and file patterns or None, and used to filter 'revs'. If --stat or
2096 --patch are not passed filematcher is None. Otherwise it is a
2092 --patch are not passed filematcher is None. Otherwise it is a
2097 callable taking a revision number and returning a match objects
2093 callable taking a revision number and returning a match objects
2098 filtering the files to be detailed when displaying the revision.
2094 filtering the files to be detailed when displaying the revision.
2099 """
2095 """
2100 limit = loglimit(opts)
2096 limit = loglimit(opts)
2101 revs = _logrevs(repo, opts)
2097 revs = _logrevs(repo, opts)
2102 if not revs:
2098 if not revs:
2103 return revset.baseset(), None, None
2099 return revset.baseset(), None, None
2104 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2100 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2105 if opts.get('rev'):
2101 if opts.get('rev'):
2106 # User-specified revs might be unsorted, but don't sort before
2102 # User-specified revs might be unsorted, but don't sort before
2107 # _makelogrevset because it might depend on the order of revs
2103 # _makelogrevset because it might depend on the order of revs
2108 revs.sort(reverse=True)
2104 revs.sort(reverse=True)
2109 if expr:
2105 if expr:
2110 # Revset matchers often operate faster on revisions in changelog
2106 # Revset matchers often operate faster on revisions in changelog
2111 # order, because most filters deal with the changelog.
2107 # order, because most filters deal with the changelog.
2112 revs.reverse()
2108 revs.reverse()
2113 matcher = revset.match(repo.ui, expr)
2109 matcher = revset.match(repo.ui, expr)
2114 # Revset matches can reorder revisions. "A or B" typically returns
2110 # Revset matches can reorder revisions. "A or B" typically returns
2115 # returns the revision matching A then the revision matching B. Sort
2111 # returns the revision matching A then the revision matching B. Sort
2116 # again to fix that.
2112 # again to fix that.
2117 revs = matcher(repo, revs)
2113 revs = matcher(repo, revs)
2118 revs.sort(reverse=True)
2114 revs.sort(reverse=True)
2119 if limit is not None:
2115 if limit is not None:
2120 limitedrevs = []
2116 limitedrevs = []
2121 for idx, rev in enumerate(revs):
2117 for idx, rev in enumerate(revs):
2122 if idx >= limit:
2118 if idx >= limit:
2123 break
2119 break
2124 limitedrevs.append(rev)
2120 limitedrevs.append(rev)
2125 revs = revset.baseset(limitedrevs)
2121 revs = revset.baseset(limitedrevs)
2126
2122
2127 return revs, expr, filematcher
2123 return revs, expr, filematcher
2128
2124
2129 def getlogrevs(repo, pats, opts):
2125 def getlogrevs(repo, pats, opts):
2130 """Return (revs, expr, filematcher) where revs is an iterable of
2126 """Return (revs, expr, filematcher) where revs is an iterable of
2131 revision numbers, expr is a revset string built from log options
2127 revision numbers, expr is a revset string built from log options
2132 and file patterns or None, and used to filter 'revs'. If --stat or
2128 and file patterns or None, and used to filter 'revs'. If --stat or
2133 --patch are not passed filematcher is None. Otherwise it is a
2129 --patch are not passed filematcher is None. Otherwise it is a
2134 callable taking a revision number and returning a match objects
2130 callable taking a revision number and returning a match objects
2135 filtering the files to be detailed when displaying the revision.
2131 filtering the files to be detailed when displaying the revision.
2136 """
2132 """
2137 limit = loglimit(opts)
2133 limit = loglimit(opts)
2138 revs = _logrevs(repo, opts)
2134 revs = _logrevs(repo, opts)
2139 if not revs:
2135 if not revs:
2140 return revset.baseset([]), None, None
2136 return revset.baseset([]), None, None
2141 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2137 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2142 if expr:
2138 if expr:
2143 # Revset matchers often operate faster on revisions in changelog
2139 # Revset matchers often operate faster on revisions in changelog
2144 # order, because most filters deal with the changelog.
2140 # order, because most filters deal with the changelog.
2145 if not opts.get('rev'):
2141 if not opts.get('rev'):
2146 revs.reverse()
2142 revs.reverse()
2147 matcher = revset.match(repo.ui, expr)
2143 matcher = revset.match(repo.ui, expr)
2148 # Revset matches can reorder revisions. "A or B" typically returns
2144 # Revset matches can reorder revisions. "A or B" typically returns
2149 # returns the revision matching A then the revision matching B. Sort
2145 # returns the revision matching A then the revision matching B. Sort
2150 # again to fix that.
2146 # again to fix that.
2151 revs = matcher(repo, revs)
2147 revs = matcher(repo, revs)
2152 if not opts.get('rev'):
2148 if not opts.get('rev'):
2153 revs.sort(reverse=True)
2149 revs.sort(reverse=True)
2154 if limit is not None:
2150 if limit is not None:
2155 limitedrevs = []
2151 limitedrevs = []
2156 for idx, r in enumerate(revs):
2152 for idx, r in enumerate(revs):
2157 if limit <= idx:
2153 if limit <= idx:
2158 break
2154 break
2159 limitedrevs.append(r)
2155 limitedrevs.append(r)
2160 revs = revset.baseset(limitedrevs)
2156 revs = revset.baseset(limitedrevs)
2161
2157
2162 return revs, expr, filematcher
2158 return revs, expr, filematcher
2163
2159
2164 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2160 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2165 filematcher=None):
2161 filematcher=None):
2166 seen, state = [], graphmod.asciistate()
2162 seen, state = [], graphmod.asciistate()
2167 for rev, type, ctx, parents in dag:
2163 for rev, type, ctx, parents in dag:
2168 char = 'o'
2164 char = 'o'
2169 if ctx.node() in showparents:
2165 if ctx.node() in showparents:
2170 char = '@'
2166 char = '@'
2171 elif ctx.obsolete():
2167 elif ctx.obsolete():
2172 char = 'x'
2168 char = 'x'
2173 elif ctx.closesbranch():
2169 elif ctx.closesbranch():
2174 char = '_'
2170 char = '_'
2175 copies = None
2171 copies = None
2176 if getrenamed and ctx.rev():
2172 if getrenamed and ctx.rev():
2177 copies = []
2173 copies = []
2178 for fn in ctx.files():
2174 for fn in ctx.files():
2179 rename = getrenamed(fn, ctx.rev())
2175 rename = getrenamed(fn, ctx.rev())
2180 if rename:
2176 if rename:
2181 copies.append((fn, rename[0]))
2177 copies.append((fn, rename[0]))
2182 revmatchfn = None
2178 revmatchfn = None
2183 if filematcher is not None:
2179 if filematcher is not None:
2184 revmatchfn = filematcher(ctx.rev())
2180 revmatchfn = filematcher(ctx.rev())
2185 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2181 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2186 lines = displayer.hunk.pop(rev).split('\n')
2182 lines = displayer.hunk.pop(rev).split('\n')
2187 if not lines[-1]:
2183 if not lines[-1]:
2188 del lines[-1]
2184 del lines[-1]
2189 displayer.flush(ctx)
2185 displayer.flush(ctx)
2190 edges = edgefn(type, char, lines, seen, rev, parents)
2186 edges = edgefn(type, char, lines, seen, rev, parents)
2191 for type, char, lines, coldata in edges:
2187 for type, char, lines, coldata in edges:
2192 graphmod.ascii(ui, state, type, char, lines, coldata)
2188 graphmod.ascii(ui, state, type, char, lines, coldata)
2193 displayer.close()
2189 displayer.close()
2194
2190
2195 def graphlog(ui, repo, *pats, **opts):
2191 def graphlog(ui, repo, *pats, **opts):
2196 # Parameters are identical to log command ones
2192 # Parameters are identical to log command ones
2197 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2193 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2198 revdag = graphmod.dagwalker(repo, revs)
2194 revdag = graphmod.dagwalker(repo, revs)
2199
2195
2200 getrenamed = None
2196 getrenamed = None
2201 if opts.get('copies'):
2197 if opts.get('copies'):
2202 endrev = None
2198 endrev = None
2203 if opts.get('rev'):
2199 if opts.get('rev'):
2204 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2200 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2205 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2201 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2206 displayer = show_changeset(ui, repo, opts, buffered=True)
2202 displayer = show_changeset(ui, repo, opts, buffered=True)
2207 showparents = [ctx.node() for ctx in repo[None].parents()]
2203 showparents = [ctx.node() for ctx in repo[None].parents()]
2208 displaygraph(ui, revdag, displayer, showparents,
2204 displaygraph(ui, revdag, displayer, showparents,
2209 graphmod.asciiedges, getrenamed, filematcher)
2205 graphmod.asciiedges, getrenamed, filematcher)
2210
2206
2211 def checkunsupportedgraphflags(pats, opts):
2207 def checkunsupportedgraphflags(pats, opts):
2212 for op in ["newest_first"]:
2208 for op in ["newest_first"]:
2213 if op in opts and opts[op]:
2209 if op in opts and opts[op]:
2214 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2210 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2215 % op.replace("_", "-"))
2211 % op.replace("_", "-"))
2216
2212
2217 def graphrevs(repo, nodes, opts):
2213 def graphrevs(repo, nodes, opts):
2218 limit = loglimit(opts)
2214 limit = loglimit(opts)
2219 nodes.reverse()
2215 nodes.reverse()
2220 if limit is not None:
2216 if limit is not None:
2221 nodes = nodes[:limit]
2217 nodes = nodes[:limit]
2222 return graphmod.nodes(repo, nodes)
2218 return graphmod.nodes(repo, nodes)
2223
2219
2224 def add(ui, repo, match, prefix, explicitonly, **opts):
2220 def add(ui, repo, match, prefix, explicitonly, **opts):
2225 join = lambda f: os.path.join(prefix, f)
2221 join = lambda f: os.path.join(prefix, f)
2226 bad = []
2222 bad = []
2227
2223
2228 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2224 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2229 names = []
2225 names = []
2230 wctx = repo[None]
2226 wctx = repo[None]
2231 cca = None
2227 cca = None
2232 abort, warn = scmutil.checkportabilityalert(ui)
2228 abort, warn = scmutil.checkportabilityalert(ui)
2233 if abort or warn:
2229 if abort or warn:
2234 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2230 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2235
2231
2236 badmatch = matchmod.badmatch(match, badfn)
2232 badmatch = matchmod.badmatch(match, badfn)
2237 dirstate = repo.dirstate
2233 dirstate = repo.dirstate
2238 # We don't want to just call wctx.walk here, since it would return a lot of
2234 # We don't want to just call wctx.walk here, since it would return a lot of
2239 # clean files, which we aren't interested in and takes time.
2235 # clean files, which we aren't interested in and takes time.
2240 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2236 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2241 True, False, full=False)):
2237 True, False, full=False)):
2242 exact = match.exact(f)
2238 exact = match.exact(f)
2243 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2239 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2244 if cca:
2240 if cca:
2245 cca(f)
2241 cca(f)
2246 names.append(f)
2242 names.append(f)
2247 if ui.verbose or not exact:
2243 if ui.verbose or not exact:
2248 ui.status(_('adding %s\n') % match.rel(f))
2244 ui.status(_('adding %s\n') % match.rel(f))
2249
2245
2250 for subpath in sorted(wctx.substate):
2246 for subpath in sorted(wctx.substate):
2251 sub = wctx.sub(subpath)
2247 sub = wctx.sub(subpath)
2252 try:
2248 try:
2253 submatch = matchmod.narrowmatcher(subpath, match)
2249 submatch = matchmod.narrowmatcher(subpath, match)
2254 if opts.get('subrepos'):
2250 if opts.get('subrepos'):
2255 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2251 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2256 else:
2252 else:
2257 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2253 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2258 except error.LookupError:
2254 except error.LookupError:
2259 ui.status(_("skipping missing subrepository: %s\n")
2255 ui.status(_("skipping missing subrepository: %s\n")
2260 % join(subpath))
2256 % join(subpath))
2261
2257
2262 if not opts.get('dry_run'):
2258 if not opts.get('dry_run'):
2263 rejected = wctx.add(names, prefix)
2259 rejected = wctx.add(names, prefix)
2264 bad.extend(f for f in rejected if f in match.files())
2260 bad.extend(f for f in rejected if f in match.files())
2265 return bad
2261 return bad
2266
2262
2267 def forget(ui, repo, match, prefix, explicitonly):
2263 def forget(ui, repo, match, prefix, explicitonly):
2268 join = lambda f: os.path.join(prefix, f)
2264 join = lambda f: os.path.join(prefix, f)
2269 bad = []
2265 bad = []
2270 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2266 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2271 wctx = repo[None]
2267 wctx = repo[None]
2272 forgot = []
2268 forgot = []
2273
2269
2274 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2270 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2275 forget = sorted(s[0] + s[1] + s[3] + s[6])
2271 forget = sorted(s[0] + s[1] + s[3] + s[6])
2276 if explicitonly:
2272 if explicitonly:
2277 forget = [f for f in forget if match.exact(f)]
2273 forget = [f for f in forget if match.exact(f)]
2278
2274
2279 for subpath in sorted(wctx.substate):
2275 for subpath in sorted(wctx.substate):
2280 sub = wctx.sub(subpath)
2276 sub = wctx.sub(subpath)
2281 try:
2277 try:
2282 submatch = matchmod.narrowmatcher(subpath, match)
2278 submatch = matchmod.narrowmatcher(subpath, match)
2283 subbad, subforgot = sub.forget(submatch, prefix)
2279 subbad, subforgot = sub.forget(submatch, prefix)
2284 bad.extend([subpath + '/' + f for f in subbad])
2280 bad.extend([subpath + '/' + f for f in subbad])
2285 forgot.extend([subpath + '/' + f for f in subforgot])
2281 forgot.extend([subpath + '/' + f for f in subforgot])
2286 except error.LookupError:
2282 except error.LookupError:
2287 ui.status(_("skipping missing subrepository: %s\n")
2283 ui.status(_("skipping missing subrepository: %s\n")
2288 % join(subpath))
2284 % join(subpath))
2289
2285
2290 if not explicitonly:
2286 if not explicitonly:
2291 for f in match.files():
2287 for f in match.files():
2292 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2288 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2293 if f not in forgot:
2289 if f not in forgot:
2294 if repo.wvfs.exists(f):
2290 if repo.wvfs.exists(f):
2295 # Don't complain if the exact case match wasn't given.
2291 # Don't complain if the exact case match wasn't given.
2296 # But don't do this until after checking 'forgot', so
2292 # But don't do this until after checking 'forgot', so
2297 # that subrepo files aren't normalized, and this op is
2293 # that subrepo files aren't normalized, and this op is
2298 # purely from data cached by the status walk above.
2294 # purely from data cached by the status walk above.
2299 if repo.dirstate.normalize(f) in repo.dirstate:
2295 if repo.dirstate.normalize(f) in repo.dirstate:
2300 continue
2296 continue
2301 ui.warn(_('not removing %s: '
2297 ui.warn(_('not removing %s: '
2302 'file is already untracked\n')
2298 'file is already untracked\n')
2303 % match.rel(f))
2299 % match.rel(f))
2304 bad.append(f)
2300 bad.append(f)
2305
2301
2306 for f in forget:
2302 for f in forget:
2307 if ui.verbose or not match.exact(f):
2303 if ui.verbose or not match.exact(f):
2308 ui.status(_('removing %s\n') % match.rel(f))
2304 ui.status(_('removing %s\n') % match.rel(f))
2309
2305
2310 rejected = wctx.forget(forget, prefix)
2306 rejected = wctx.forget(forget, prefix)
2311 bad.extend(f for f in rejected if f in match.files())
2307 bad.extend(f for f in rejected if f in match.files())
2312 forgot.extend(f for f in forget if f not in rejected)
2308 forgot.extend(f for f in forget if f not in rejected)
2313 return bad, forgot
2309 return bad, forgot
2314
2310
2315 def files(ui, ctx, m, fm, fmt, subrepos):
2311 def files(ui, ctx, m, fm, fmt, subrepos):
2316 rev = ctx.rev()
2312 rev = ctx.rev()
2317 ret = 1
2313 ret = 1
2318 ds = ctx.repo().dirstate
2314 ds = ctx.repo().dirstate
2319
2315
2320 for f in ctx.matches(m):
2316 for f in ctx.matches(m):
2321 if rev is None and ds[f] == 'r':
2317 if rev is None and ds[f] == 'r':
2322 continue
2318 continue
2323 fm.startitem()
2319 fm.startitem()
2324 if ui.verbose:
2320 if ui.verbose:
2325 fc = ctx[f]
2321 fc = ctx[f]
2326 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2322 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2327 fm.data(abspath=f)
2323 fm.data(abspath=f)
2328 fm.write('path', fmt, m.rel(f))
2324 fm.write('path', fmt, m.rel(f))
2329 ret = 0
2325 ret = 0
2330
2326
2331 for subpath in sorted(ctx.substate):
2327 for subpath in sorted(ctx.substate):
2332 def matchessubrepo(subpath):
2328 def matchessubrepo(subpath):
2333 return (m.always() or m.exact(subpath)
2329 return (m.always() or m.exact(subpath)
2334 or any(f.startswith(subpath + '/') for f in m.files()))
2330 or any(f.startswith(subpath + '/') for f in m.files()))
2335
2331
2336 if subrepos or matchessubrepo(subpath):
2332 if subrepos or matchessubrepo(subpath):
2337 sub = ctx.sub(subpath)
2333 sub = ctx.sub(subpath)
2338 try:
2334 try:
2339 submatch = matchmod.narrowmatcher(subpath, m)
2335 submatch = matchmod.narrowmatcher(subpath, m)
2340 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2336 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2341 ret = 0
2337 ret = 0
2342 except error.LookupError:
2338 except error.LookupError:
2343 ui.status(_("skipping missing subrepository: %s\n")
2339 ui.status(_("skipping missing subrepository: %s\n")
2344 % m.abs(subpath))
2340 % m.abs(subpath))
2345
2341
2346 return ret
2342 return ret
2347
2343
2348 def remove(ui, repo, m, prefix, after, force, subrepos):
2344 def remove(ui, repo, m, prefix, after, force, subrepos):
2349 join = lambda f: os.path.join(prefix, f)
2345 join = lambda f: os.path.join(prefix, f)
2350 ret = 0
2346 ret = 0
2351 s = repo.status(match=m, clean=True)
2347 s = repo.status(match=m, clean=True)
2352 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2348 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2353
2349
2354 wctx = repo[None]
2350 wctx = repo[None]
2355
2351
2356 for subpath in sorted(wctx.substate):
2352 for subpath in sorted(wctx.substate):
2357 def matchessubrepo(matcher, subpath):
2353 def matchessubrepo(matcher, subpath):
2358 if matcher.exact(subpath):
2354 if matcher.exact(subpath):
2359 return True
2355 return True
2360 for f in matcher.files():
2356 for f in matcher.files():
2361 if f.startswith(subpath):
2357 if f.startswith(subpath):
2362 return True
2358 return True
2363 return False
2359 return False
2364
2360
2365 if subrepos or matchessubrepo(m, subpath):
2361 if subrepos or matchessubrepo(m, subpath):
2366 sub = wctx.sub(subpath)
2362 sub = wctx.sub(subpath)
2367 try:
2363 try:
2368 submatch = matchmod.narrowmatcher(subpath, m)
2364 submatch = matchmod.narrowmatcher(subpath, m)
2369 if sub.removefiles(submatch, prefix, after, force, subrepos):
2365 if sub.removefiles(submatch, prefix, after, force, subrepos):
2370 ret = 1
2366 ret = 1
2371 except error.LookupError:
2367 except error.LookupError:
2372 ui.status(_("skipping missing subrepository: %s\n")
2368 ui.status(_("skipping missing subrepository: %s\n")
2373 % join(subpath))
2369 % join(subpath))
2374
2370
2375 # warn about failure to delete explicit files/dirs
2371 # warn about failure to delete explicit files/dirs
2376 deleteddirs = util.dirs(deleted)
2372 deleteddirs = util.dirs(deleted)
2377 for f in m.files():
2373 for f in m.files():
2378 def insubrepo():
2374 def insubrepo():
2379 for subpath in wctx.substate:
2375 for subpath in wctx.substate:
2380 if f.startswith(subpath):
2376 if f.startswith(subpath):
2381 return True
2377 return True
2382 return False
2378 return False
2383
2379
2384 isdir = f in deleteddirs or wctx.hasdir(f)
2380 isdir = f in deleteddirs or wctx.hasdir(f)
2385 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2381 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2386 continue
2382 continue
2387
2383
2388 if repo.wvfs.exists(f):
2384 if repo.wvfs.exists(f):
2389 if repo.wvfs.isdir(f):
2385 if repo.wvfs.isdir(f):
2390 ui.warn(_('not removing %s: no tracked files\n')
2386 ui.warn(_('not removing %s: no tracked files\n')
2391 % m.rel(f))
2387 % m.rel(f))
2392 else:
2388 else:
2393 ui.warn(_('not removing %s: file is untracked\n')
2389 ui.warn(_('not removing %s: file is untracked\n')
2394 % m.rel(f))
2390 % m.rel(f))
2395 # missing files will generate a warning elsewhere
2391 # missing files will generate a warning elsewhere
2396 ret = 1
2392 ret = 1
2397
2393
2398 if force:
2394 if force:
2399 list = modified + deleted + clean + added
2395 list = modified + deleted + clean + added
2400 elif after:
2396 elif after:
2401 list = deleted
2397 list = deleted
2402 for f in modified + added + clean:
2398 for f in modified + added + clean:
2403 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2399 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2404 ret = 1
2400 ret = 1
2405 else:
2401 else:
2406 list = deleted + clean
2402 list = deleted + clean
2407 for f in modified:
2403 for f in modified:
2408 ui.warn(_('not removing %s: file is modified (use -f'
2404 ui.warn(_('not removing %s: file is modified (use -f'
2409 ' to force removal)\n') % m.rel(f))
2405 ' to force removal)\n') % m.rel(f))
2410 ret = 1
2406 ret = 1
2411 for f in added:
2407 for f in added:
2412 ui.warn(_('not removing %s: file has been marked for add'
2408 ui.warn(_('not removing %s: file has been marked for add'
2413 ' (use forget to undo)\n') % m.rel(f))
2409 ' (use forget to undo)\n') % m.rel(f))
2414 ret = 1
2410 ret = 1
2415
2411
2416 for f in sorted(list):
2412 for f in sorted(list):
2417 if ui.verbose or not m.exact(f):
2413 if ui.verbose or not m.exact(f):
2418 ui.status(_('removing %s\n') % m.rel(f))
2414 ui.status(_('removing %s\n') % m.rel(f))
2419
2415
2420 wlock = repo.wlock()
2416 wlock = repo.wlock()
2421 try:
2417 try:
2422 if not after:
2418 if not after:
2423 for f in list:
2419 for f in list:
2424 if f in added:
2420 if f in added:
2425 continue # we never unlink added files on remove
2421 continue # we never unlink added files on remove
2426 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2422 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2427 repo[None].forget(list)
2423 repo[None].forget(list)
2428 finally:
2424 finally:
2429 wlock.release()
2425 wlock.release()
2430
2426
2431 return ret
2427 return ret
2432
2428
2433 def cat(ui, repo, ctx, matcher, prefix, **opts):
2429 def cat(ui, repo, ctx, matcher, prefix, **opts):
2434 err = 1
2430 err = 1
2435
2431
2436 def write(path):
2432 def write(path):
2437 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2433 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2438 pathname=os.path.join(prefix, path))
2434 pathname=os.path.join(prefix, path))
2439 data = ctx[path].data()
2435 data = ctx[path].data()
2440 if opts.get('decode'):
2436 if opts.get('decode'):
2441 data = repo.wwritedata(path, data)
2437 data = repo.wwritedata(path, data)
2442 fp.write(data)
2438 fp.write(data)
2443 fp.close()
2439 fp.close()
2444
2440
2445 # Automation often uses hg cat on single files, so special case it
2441 # Automation often uses hg cat on single files, so special case it
2446 # for performance to avoid the cost of parsing the manifest.
2442 # for performance to avoid the cost of parsing the manifest.
2447 if len(matcher.files()) == 1 and not matcher.anypats():
2443 if len(matcher.files()) == 1 and not matcher.anypats():
2448 file = matcher.files()[0]
2444 file = matcher.files()[0]
2449 mf = repo.manifest
2445 mf = repo.manifest
2450 mfnode = ctx.manifestnode()
2446 mfnode = ctx.manifestnode()
2451 if mfnode and mf.find(mfnode, file)[0]:
2447 if mfnode and mf.find(mfnode, file)[0]:
2452 write(file)
2448 write(file)
2453 return 0
2449 return 0
2454
2450
2455 # Don't warn about "missing" files that are really in subrepos
2451 # Don't warn about "missing" files that are really in subrepos
2456 def badfn(path, msg):
2452 def badfn(path, msg):
2457 for subpath in ctx.substate:
2453 for subpath in ctx.substate:
2458 if path.startswith(subpath):
2454 if path.startswith(subpath):
2459 return
2455 return
2460 matcher.bad(path, msg)
2456 matcher.bad(path, msg)
2461
2457
2462 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2458 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2463 write(abs)
2459 write(abs)
2464 err = 0
2460 err = 0
2465
2461
2466 for subpath in sorted(ctx.substate):
2462 for subpath in sorted(ctx.substate):
2467 sub = ctx.sub(subpath)
2463 sub = ctx.sub(subpath)
2468 try:
2464 try:
2469 submatch = matchmod.narrowmatcher(subpath, matcher)
2465 submatch = matchmod.narrowmatcher(subpath, matcher)
2470
2466
2471 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2467 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2472 **opts):
2468 **opts):
2473 err = 0
2469 err = 0
2474 except error.RepoLookupError:
2470 except error.RepoLookupError:
2475 ui.status(_("skipping missing subrepository: %s\n")
2471 ui.status(_("skipping missing subrepository: %s\n")
2476 % os.path.join(prefix, subpath))
2472 % os.path.join(prefix, subpath))
2477
2473
2478 return err
2474 return err
2479
2475
2480 def commit(ui, repo, commitfunc, pats, opts):
2476 def commit(ui, repo, commitfunc, pats, opts):
2481 '''commit the specified files or all outstanding changes'''
2477 '''commit the specified files or all outstanding changes'''
2482 date = opts.get('date')
2478 date = opts.get('date')
2483 if date:
2479 if date:
2484 opts['date'] = util.parsedate(date)
2480 opts['date'] = util.parsedate(date)
2485 message = logmessage(ui, opts)
2481 message = logmessage(ui, opts)
2486 matcher = scmutil.match(repo[None], pats, opts)
2482 matcher = scmutil.match(repo[None], pats, opts)
2487
2483
2488 # extract addremove carefully -- this function can be called from a command
2484 # extract addremove carefully -- this function can be called from a command
2489 # that doesn't support addremove
2485 # that doesn't support addremove
2490 if opts.get('addremove'):
2486 if opts.get('addremove'):
2491 if scmutil.addremove(repo, matcher, "", opts) != 0:
2487 if scmutil.addremove(repo, matcher, "", opts) != 0:
2492 raise util.Abort(
2488 raise util.Abort(
2493 _("failed to mark all new/missing files as added/removed"))
2489 _("failed to mark all new/missing files as added/removed"))
2494
2490
2495 return commitfunc(ui, repo, message, matcher, opts)
2491 return commitfunc(ui, repo, message, matcher, opts)
2496
2492
2497 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2493 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2498 # avoid cycle context -> subrepo -> cmdutil
2494 # avoid cycle context -> subrepo -> cmdutil
2499 import context
2495 import context
2500
2496
2501 # amend will reuse the existing user if not specified, but the obsolete
2497 # amend will reuse the existing user if not specified, but the obsolete
2502 # marker creation requires that the current user's name is specified.
2498 # marker creation requires that the current user's name is specified.
2503 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2499 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2504 ui.username() # raise exception if username not set
2500 ui.username() # raise exception if username not set
2505
2501
2506 ui.note(_('amending changeset %s\n') % old)
2502 ui.note(_('amending changeset %s\n') % old)
2507 base = old.p1()
2503 base = old.p1()
2508 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2504 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2509
2505
2510 wlock = lock = newid = None
2506 wlock = lock = newid = None
2511 try:
2507 try:
2512 wlock = repo.wlock()
2508 wlock = repo.wlock()
2513 lock = repo.lock()
2509 lock = repo.lock()
2514 tr = repo.transaction('amend')
2510 tr = repo.transaction('amend')
2515 try:
2511 try:
2516 # See if we got a message from -m or -l, if not, open the editor
2512 # See if we got a message from -m or -l, if not, open the editor
2517 # with the message of the changeset to amend
2513 # with the message of the changeset to amend
2518 message = logmessage(ui, opts)
2514 message = logmessage(ui, opts)
2519 # ensure logfile does not conflict with later enforcement of the
2515 # ensure logfile does not conflict with later enforcement of the
2520 # message. potential logfile content has been processed by
2516 # message. potential logfile content has been processed by
2521 # `logmessage` anyway.
2517 # `logmessage` anyway.
2522 opts.pop('logfile')
2518 opts.pop('logfile')
2523 # First, do a regular commit to record all changes in the working
2519 # First, do a regular commit to record all changes in the working
2524 # directory (if there are any)
2520 # directory (if there are any)
2525 ui.callhooks = False
2521 ui.callhooks = False
2526 activebookmark = repo._activebookmark
2522 activebookmark = repo._activebookmark
2527 try:
2523 try:
2528 repo._activebookmark = None
2524 repo._activebookmark = None
2529 opts['message'] = 'temporary amend commit for %s' % old
2525 opts['message'] = 'temporary amend commit for %s' % old
2530 node = commit(ui, repo, commitfunc, pats, opts)
2526 node = commit(ui, repo, commitfunc, pats, opts)
2531 finally:
2527 finally:
2532 repo._activebookmark = activebookmark
2528 repo._activebookmark = activebookmark
2533 ui.callhooks = True
2529 ui.callhooks = True
2534 ctx = repo[node]
2530 ctx = repo[node]
2535
2531
2536 # Participating changesets:
2532 # Participating changesets:
2537 #
2533 #
2538 # node/ctx o - new (intermediate) commit that contains changes
2534 # node/ctx o - new (intermediate) commit that contains changes
2539 # | from working dir to go into amending commit
2535 # | from working dir to go into amending commit
2540 # | (or a workingctx if there were no changes)
2536 # | (or a workingctx if there were no changes)
2541 # |
2537 # |
2542 # old o - changeset to amend
2538 # old o - changeset to amend
2543 # |
2539 # |
2544 # base o - parent of amending changeset
2540 # base o - parent of amending changeset
2545
2541
2546 # Update extra dict from amended commit (e.g. to preserve graft
2542 # Update extra dict from amended commit (e.g. to preserve graft
2547 # source)
2543 # source)
2548 extra.update(old.extra())
2544 extra.update(old.extra())
2549
2545
2550 # Also update it from the intermediate commit or from the wctx
2546 # Also update it from the intermediate commit or from the wctx
2551 extra.update(ctx.extra())
2547 extra.update(ctx.extra())
2552
2548
2553 if len(old.parents()) > 1:
2549 if len(old.parents()) > 1:
2554 # ctx.files() isn't reliable for merges, so fall back to the
2550 # ctx.files() isn't reliable for merges, so fall back to the
2555 # slower repo.status() method
2551 # slower repo.status() method
2556 files = set([fn for st in repo.status(base, old)[:3]
2552 files = set([fn for st in repo.status(base, old)[:3]
2557 for fn in st])
2553 for fn in st])
2558 else:
2554 else:
2559 files = set(old.files())
2555 files = set(old.files())
2560
2556
2561 # Second, we use either the commit we just did, or if there were no
2557 # Second, we use either the commit we just did, or if there were no
2562 # changes the parent of the working directory as the version of the
2558 # changes the parent of the working directory as the version of the
2563 # files in the final amend commit
2559 # files in the final amend commit
2564 if node:
2560 if node:
2565 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2561 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2566
2562
2567 user = ctx.user()
2563 user = ctx.user()
2568 date = ctx.date()
2564 date = ctx.date()
2569 # Recompute copies (avoid recording a -> b -> a)
2565 # Recompute copies (avoid recording a -> b -> a)
2570 copied = copies.pathcopies(base, ctx)
2566 copied = copies.pathcopies(base, ctx)
2571 if old.p2:
2567 if old.p2:
2572 copied.update(copies.pathcopies(old.p2(), ctx))
2568 copied.update(copies.pathcopies(old.p2(), ctx))
2573
2569
2574 # Prune files which were reverted by the updates: if old
2570 # Prune files which were reverted by the updates: if old
2575 # introduced file X and our intermediate commit, node,
2571 # introduced file X and our intermediate commit, node,
2576 # renamed that file, then those two files are the same and
2572 # renamed that file, then those two files are the same and
2577 # we can discard X from our list of files. Likewise if X
2573 # we can discard X from our list of files. Likewise if X
2578 # was deleted, it's no longer relevant
2574 # was deleted, it's no longer relevant
2579 files.update(ctx.files())
2575 files.update(ctx.files())
2580
2576
2581 def samefile(f):
2577 def samefile(f):
2582 if f in ctx.manifest():
2578 if f in ctx.manifest():
2583 a = ctx.filectx(f)
2579 a = ctx.filectx(f)
2584 if f in base.manifest():
2580 if f in base.manifest():
2585 b = base.filectx(f)
2581 b = base.filectx(f)
2586 return (not a.cmp(b)
2582 return (not a.cmp(b)
2587 and a.flags() == b.flags())
2583 and a.flags() == b.flags())
2588 else:
2584 else:
2589 return False
2585 return False
2590 else:
2586 else:
2591 return f not in base.manifest()
2587 return f not in base.manifest()
2592 files = [f for f in files if not samefile(f)]
2588 files = [f for f in files if not samefile(f)]
2593
2589
2594 def filectxfn(repo, ctx_, path):
2590 def filectxfn(repo, ctx_, path):
2595 try:
2591 try:
2596 fctx = ctx[path]
2592 fctx = ctx[path]
2597 flags = fctx.flags()
2593 flags = fctx.flags()
2598 mctx = context.memfilectx(repo,
2594 mctx = context.memfilectx(repo,
2599 fctx.path(), fctx.data(),
2595 fctx.path(), fctx.data(),
2600 islink='l' in flags,
2596 islink='l' in flags,
2601 isexec='x' in flags,
2597 isexec='x' in flags,
2602 copied=copied.get(path))
2598 copied=copied.get(path))
2603 return mctx
2599 return mctx
2604 except KeyError:
2600 except KeyError:
2605 return None
2601 return None
2606 else:
2602 else:
2607 ui.note(_('copying changeset %s to %s\n') % (old, base))
2603 ui.note(_('copying changeset %s to %s\n') % (old, base))
2608
2604
2609 # Use version of files as in the old cset
2605 # Use version of files as in the old cset
2610 def filectxfn(repo, ctx_, path):
2606 def filectxfn(repo, ctx_, path):
2611 try:
2607 try:
2612 return old.filectx(path)
2608 return old.filectx(path)
2613 except KeyError:
2609 except KeyError:
2614 return None
2610 return None
2615
2611
2616 user = opts.get('user') or old.user()
2612 user = opts.get('user') or old.user()
2617 date = opts.get('date') or old.date()
2613 date = opts.get('date') or old.date()
2618 editform = mergeeditform(old, 'commit.amend')
2614 editform = mergeeditform(old, 'commit.amend')
2619 editor = getcommiteditor(editform=editform, **opts)
2615 editor = getcommiteditor(editform=editform, **opts)
2620 if not message:
2616 if not message:
2621 editor = getcommiteditor(edit=True, editform=editform)
2617 editor = getcommiteditor(edit=True, editform=editform)
2622 message = old.description()
2618 message = old.description()
2623
2619
2624 pureextra = extra.copy()
2620 pureextra = extra.copy()
2625 extra['amend_source'] = old.hex()
2621 extra['amend_source'] = old.hex()
2626
2622
2627 new = context.memctx(repo,
2623 new = context.memctx(repo,
2628 parents=[base.node(), old.p2().node()],
2624 parents=[base.node(), old.p2().node()],
2629 text=message,
2625 text=message,
2630 files=files,
2626 files=files,
2631 filectxfn=filectxfn,
2627 filectxfn=filectxfn,
2632 user=user,
2628 user=user,
2633 date=date,
2629 date=date,
2634 extra=extra,
2630 extra=extra,
2635 editor=editor)
2631 editor=editor)
2636
2632
2637 newdesc = changelog.stripdesc(new.description())
2633 newdesc = changelog.stripdesc(new.description())
2638 if ((not node)
2634 if ((not node)
2639 and newdesc == old.description()
2635 and newdesc == old.description()
2640 and user == old.user()
2636 and user == old.user()
2641 and date == old.date()
2637 and date == old.date()
2642 and pureextra == old.extra()):
2638 and pureextra == old.extra()):
2643 # nothing changed. continuing here would create a new node
2639 # nothing changed. continuing here would create a new node
2644 # anyway because of the amend_source noise.
2640 # anyway because of the amend_source noise.
2645 #
2641 #
2646 # This not what we expect from amend.
2642 # This not what we expect from amend.
2647 return old.node()
2643 return old.node()
2648
2644
2649 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2645 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2650 try:
2646 try:
2651 if opts.get('secret'):
2647 if opts.get('secret'):
2652 commitphase = 'secret'
2648 commitphase = 'secret'
2653 else:
2649 else:
2654 commitphase = old.phase()
2650 commitphase = old.phase()
2655 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2651 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2656 newid = repo.commitctx(new)
2652 newid = repo.commitctx(new)
2657 finally:
2653 finally:
2658 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2654 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2659 if newid != old.node():
2655 if newid != old.node():
2660 # Reroute the working copy parent to the new changeset
2656 # Reroute the working copy parent to the new changeset
2661 repo.setparents(newid, nullid)
2657 repo.setparents(newid, nullid)
2662
2658
2663 # Move bookmarks from old parent to amend commit
2659 # Move bookmarks from old parent to amend commit
2664 bms = repo.nodebookmarks(old.node())
2660 bms = repo.nodebookmarks(old.node())
2665 if bms:
2661 if bms:
2666 marks = repo._bookmarks
2662 marks = repo._bookmarks
2667 for bm in bms:
2663 for bm in bms:
2668 ui.debug('moving bookmarks %r from %s to %s\n' %
2664 ui.debug('moving bookmarks %r from %s to %s\n' %
2669 (marks, old.hex(), hex(newid)))
2665 (marks, old.hex(), hex(newid)))
2670 marks[bm] = newid
2666 marks[bm] = newid
2671 marks.recordchange(tr)
2667 marks.recordchange(tr)
2672 #commit the whole amend process
2668 #commit the whole amend process
2673 if createmarkers:
2669 if createmarkers:
2674 # mark the new changeset as successor of the rewritten one
2670 # mark the new changeset as successor of the rewritten one
2675 new = repo[newid]
2671 new = repo[newid]
2676 obs = [(old, (new,))]
2672 obs = [(old, (new,))]
2677 if node:
2673 if node:
2678 obs.append((ctx, ()))
2674 obs.append((ctx, ()))
2679
2675
2680 obsolete.createmarkers(repo, obs)
2676 obsolete.createmarkers(repo, obs)
2681 tr.close()
2677 tr.close()
2682 finally:
2678 finally:
2683 tr.release()
2679 tr.release()
2684 if not createmarkers and newid != old.node():
2680 if not createmarkers and newid != old.node():
2685 # Strip the intermediate commit (if there was one) and the amended
2681 # Strip the intermediate commit (if there was one) and the amended
2686 # commit
2682 # commit
2687 if node:
2683 if node:
2688 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2684 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2689 ui.note(_('stripping amended changeset %s\n') % old)
2685 ui.note(_('stripping amended changeset %s\n') % old)
2690 repair.strip(ui, repo, old.node(), topic='amend-backup')
2686 repair.strip(ui, repo, old.node(), topic='amend-backup')
2691 finally:
2687 finally:
2692 lockmod.release(lock, wlock)
2688 lockmod.release(lock, wlock)
2693 return newid
2689 return newid
2694
2690
2695 def commiteditor(repo, ctx, subs, editform=''):
2691 def commiteditor(repo, ctx, subs, editform=''):
2696 if ctx.description():
2692 if ctx.description():
2697 return ctx.description()
2693 return ctx.description()
2698 return commitforceeditor(repo, ctx, subs, editform=editform)
2694 return commitforceeditor(repo, ctx, subs, editform=editform)
2699
2695
2700 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2696 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2701 editform=''):
2697 editform=''):
2702 if not extramsg:
2698 if not extramsg:
2703 extramsg = _("Leave message empty to abort commit.")
2699 extramsg = _("Leave message empty to abort commit.")
2704
2700
2705 forms = [e for e in editform.split('.') if e]
2701 forms = [e for e in editform.split('.') if e]
2706 forms.insert(0, 'changeset')
2702 forms.insert(0, 'changeset')
2707 while forms:
2703 while forms:
2708 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2704 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2709 if tmpl:
2705 if tmpl:
2710 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2706 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2711 break
2707 break
2712 forms.pop()
2708 forms.pop()
2713 else:
2709 else:
2714 committext = buildcommittext(repo, ctx, subs, extramsg)
2710 committext = buildcommittext(repo, ctx, subs, extramsg)
2715
2711
2716 # run editor in the repository root
2712 # run editor in the repository root
2717 olddir = os.getcwd()
2713 olddir = os.getcwd()
2718 os.chdir(repo.root)
2714 os.chdir(repo.root)
2719 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2715 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2720 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2716 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2721 os.chdir(olddir)
2717 os.chdir(olddir)
2722
2718
2723 if finishdesc:
2719 if finishdesc:
2724 text = finishdesc(text)
2720 text = finishdesc(text)
2725 if not text.strip():
2721 if not text.strip():
2726 raise util.Abort(_("empty commit message"))
2722 raise util.Abort(_("empty commit message"))
2727
2723
2728 return text
2724 return text
2729
2725
2730 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2726 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2731 ui = repo.ui
2727 ui = repo.ui
2732 tmpl, mapfile = gettemplate(ui, tmpl, None)
2728 tmpl, mapfile = gettemplate(ui, tmpl, None)
2733
2729
2734 try:
2730 try:
2735 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2731 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2736 except SyntaxError as inst:
2732 except SyntaxError as inst:
2737 raise util.Abort(inst.args[0])
2733 raise util.Abort(inst.args[0])
2738
2734
2739 for k, v in repo.ui.configitems('committemplate'):
2735 for k, v in repo.ui.configitems('committemplate'):
2740 if k != 'changeset':
2736 if k != 'changeset':
2741 t.t.cache[k] = v
2737 t.t.cache[k] = v
2742
2738
2743 if not extramsg:
2739 if not extramsg:
2744 extramsg = '' # ensure that extramsg is string
2740 extramsg = '' # ensure that extramsg is string
2745
2741
2746 ui.pushbuffer()
2742 ui.pushbuffer()
2747 t.show(ctx, extramsg=extramsg)
2743 t.show(ctx, extramsg=extramsg)
2748 return ui.popbuffer()
2744 return ui.popbuffer()
2749
2745
2750 def hgprefix(msg):
2746 def hgprefix(msg):
2751 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2747 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2752
2748
2753 def buildcommittext(repo, ctx, subs, extramsg):
2749 def buildcommittext(repo, ctx, subs, extramsg):
2754 edittext = []
2750 edittext = []
2755 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2751 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2756 if ctx.description():
2752 if ctx.description():
2757 edittext.append(ctx.description())
2753 edittext.append(ctx.description())
2758 edittext.append("")
2754 edittext.append("")
2759 edittext.append("") # Empty line between message and comments.
2755 edittext.append("") # Empty line between message and comments.
2760 edittext.append(hgprefix(_("Enter commit message."
2756 edittext.append(hgprefix(_("Enter commit message."
2761 " Lines beginning with 'HG:' are removed.")))
2757 " Lines beginning with 'HG:' are removed.")))
2762 edittext.append(hgprefix(extramsg))
2758 edittext.append(hgprefix(extramsg))
2763 edittext.append("HG: --")
2759 edittext.append("HG: --")
2764 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2760 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2765 if ctx.p2():
2761 if ctx.p2():
2766 edittext.append(hgprefix(_("branch merge")))
2762 edittext.append(hgprefix(_("branch merge")))
2767 if ctx.branch():
2763 if ctx.branch():
2768 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2764 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2769 if bookmarks.isactivewdirparent(repo):
2765 if bookmarks.isactivewdirparent(repo):
2770 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2766 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2771 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2767 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2772 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2768 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2773 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2769 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2774 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2770 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2775 if not added and not modified and not removed:
2771 if not added and not modified and not removed:
2776 edittext.append(hgprefix(_("no files changed")))
2772 edittext.append(hgprefix(_("no files changed")))
2777 edittext.append("")
2773 edittext.append("")
2778
2774
2779 return "\n".join(edittext)
2775 return "\n".join(edittext)
2780
2776
2781 def commitstatus(repo, node, branch, bheads=None, opts=None):
2777 def commitstatus(repo, node, branch, bheads=None, opts=None):
2782 if opts is None:
2778 if opts is None:
2783 opts = {}
2779 opts = {}
2784 ctx = repo[node]
2780 ctx = repo[node]
2785 parents = ctx.parents()
2781 parents = ctx.parents()
2786
2782
2787 if (not opts.get('amend') and bheads and node not in bheads and not
2783 if (not opts.get('amend') and bheads and node not in bheads and not
2788 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2784 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2789 repo.ui.status(_('created new head\n'))
2785 repo.ui.status(_('created new head\n'))
2790 # The message is not printed for initial roots. For the other
2786 # The message is not printed for initial roots. For the other
2791 # changesets, it is printed in the following situations:
2787 # changesets, it is printed in the following situations:
2792 #
2788 #
2793 # Par column: for the 2 parents with ...
2789 # Par column: for the 2 parents with ...
2794 # N: null or no parent
2790 # N: null or no parent
2795 # B: parent is on another named branch
2791 # B: parent is on another named branch
2796 # C: parent is a regular non head changeset
2792 # C: parent is a regular non head changeset
2797 # H: parent was a branch head of the current branch
2793 # H: parent was a branch head of the current branch
2798 # Msg column: whether we print "created new head" message
2794 # Msg column: whether we print "created new head" message
2799 # In the following, it is assumed that there already exists some
2795 # In the following, it is assumed that there already exists some
2800 # initial branch heads of the current branch, otherwise nothing is
2796 # initial branch heads of the current branch, otherwise nothing is
2801 # printed anyway.
2797 # printed anyway.
2802 #
2798 #
2803 # Par Msg Comment
2799 # Par Msg Comment
2804 # N N y additional topo root
2800 # N N y additional topo root
2805 #
2801 #
2806 # B N y additional branch root
2802 # B N y additional branch root
2807 # C N y additional topo head
2803 # C N y additional topo head
2808 # H N n usual case
2804 # H N n usual case
2809 #
2805 #
2810 # B B y weird additional branch root
2806 # B B y weird additional branch root
2811 # C B y branch merge
2807 # C B y branch merge
2812 # H B n merge with named branch
2808 # H B n merge with named branch
2813 #
2809 #
2814 # C C y additional head from merge
2810 # C C y additional head from merge
2815 # C H n merge with a head
2811 # C H n merge with a head
2816 #
2812 #
2817 # H H n head merge: head count decreases
2813 # H H n head merge: head count decreases
2818
2814
2819 if not opts.get('close_branch'):
2815 if not opts.get('close_branch'):
2820 for r in parents:
2816 for r in parents:
2821 if r.closesbranch() and r.branch() == branch:
2817 if r.closesbranch() and r.branch() == branch:
2822 repo.ui.status(_('reopening closed branch head %d\n') % r)
2818 repo.ui.status(_('reopening closed branch head %d\n') % r)
2823
2819
2824 if repo.ui.debugflag:
2820 if repo.ui.debugflag:
2825 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2821 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2826 elif repo.ui.verbose:
2822 elif repo.ui.verbose:
2827 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2823 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2828
2824
2829 def revert(ui, repo, ctx, parents, *pats, **opts):
2825 def revert(ui, repo, ctx, parents, *pats, **opts):
2830 parent, p2 = parents
2826 parent, p2 = parents
2831 node = ctx.node()
2827 node = ctx.node()
2832
2828
2833 mf = ctx.manifest()
2829 mf = ctx.manifest()
2834 if node == p2:
2830 if node == p2:
2835 parent = p2
2831 parent = p2
2836 if node == parent:
2832 if node == parent:
2837 pmf = mf
2833 pmf = mf
2838 else:
2834 else:
2839 pmf = None
2835 pmf = None
2840
2836
2841 # need all matching names in dirstate and manifest of target rev,
2837 # need all matching names in dirstate and manifest of target rev,
2842 # so have to walk both. do not print errors if files exist in one
2838 # so have to walk both. do not print errors if files exist in one
2843 # but not other. in both cases, filesets should be evaluated against
2839 # but not other. in both cases, filesets should be evaluated against
2844 # workingctx to get consistent result (issue4497). this means 'set:**'
2840 # workingctx to get consistent result (issue4497). this means 'set:**'
2845 # cannot be used to select missing files from target rev.
2841 # cannot be used to select missing files from target rev.
2846
2842
2847 # `names` is a mapping for all elements in working copy and target revision
2843 # `names` is a mapping for all elements in working copy and target revision
2848 # The mapping is in the form:
2844 # The mapping is in the form:
2849 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2845 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2850 names = {}
2846 names = {}
2851
2847
2852 wlock = repo.wlock()
2848 wlock = repo.wlock()
2853 try:
2849 try:
2854 ## filling of the `names` mapping
2850 ## filling of the `names` mapping
2855 # walk dirstate to fill `names`
2851 # walk dirstate to fill `names`
2856
2852
2857 interactive = opts.get('interactive', False)
2853 interactive = opts.get('interactive', False)
2858 wctx = repo[None]
2854 wctx = repo[None]
2859 m = scmutil.match(wctx, pats, opts)
2855 m = scmutil.match(wctx, pats, opts)
2860
2856
2861 # we'll need this later
2857 # we'll need this later
2862 targetsubs = sorted(s for s in wctx.substate if m(s))
2858 targetsubs = sorted(s for s in wctx.substate if m(s))
2863
2859
2864 if not m.always():
2860 if not m.always():
2865 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2861 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2866 names[abs] = m.rel(abs), m.exact(abs)
2862 names[abs] = m.rel(abs), m.exact(abs)
2867
2863
2868 # walk target manifest to fill `names`
2864 # walk target manifest to fill `names`
2869
2865
2870 def badfn(path, msg):
2866 def badfn(path, msg):
2871 if path in names:
2867 if path in names:
2872 return
2868 return
2873 if path in ctx.substate:
2869 if path in ctx.substate:
2874 return
2870 return
2875 path_ = path + '/'
2871 path_ = path + '/'
2876 for f in names:
2872 for f in names:
2877 if f.startswith(path_):
2873 if f.startswith(path_):
2878 return
2874 return
2879 ui.warn("%s: %s\n" % (m.rel(path), msg))
2875 ui.warn("%s: %s\n" % (m.rel(path), msg))
2880
2876
2881 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2877 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2882 if abs not in names:
2878 if abs not in names:
2883 names[abs] = m.rel(abs), m.exact(abs)
2879 names[abs] = m.rel(abs), m.exact(abs)
2884
2880
2885 # Find status of all file in `names`.
2881 # Find status of all file in `names`.
2886 m = scmutil.matchfiles(repo, names)
2882 m = scmutil.matchfiles(repo, names)
2887
2883
2888 changes = repo.status(node1=node, match=m,
2884 changes = repo.status(node1=node, match=m,
2889 unknown=True, ignored=True, clean=True)
2885 unknown=True, ignored=True, clean=True)
2890 else:
2886 else:
2891 changes = repo.status(node1=node, match=m)
2887 changes = repo.status(node1=node, match=m)
2892 for kind in changes:
2888 for kind in changes:
2893 for abs in kind:
2889 for abs in kind:
2894 names[abs] = m.rel(abs), m.exact(abs)
2890 names[abs] = m.rel(abs), m.exact(abs)
2895
2891
2896 m = scmutil.matchfiles(repo, names)
2892 m = scmutil.matchfiles(repo, names)
2897
2893
2898 modified = set(changes.modified)
2894 modified = set(changes.modified)
2899 added = set(changes.added)
2895 added = set(changes.added)
2900 removed = set(changes.removed)
2896 removed = set(changes.removed)
2901 _deleted = set(changes.deleted)
2897 _deleted = set(changes.deleted)
2902 unknown = set(changes.unknown)
2898 unknown = set(changes.unknown)
2903 unknown.update(changes.ignored)
2899 unknown.update(changes.ignored)
2904 clean = set(changes.clean)
2900 clean = set(changes.clean)
2905 modadded = set()
2901 modadded = set()
2906
2902
2907 # split between files known in target manifest and the others
2903 # split between files known in target manifest and the others
2908 smf = set(mf)
2904 smf = set(mf)
2909
2905
2910 # determine the exact nature of the deleted changesets
2906 # determine the exact nature of the deleted changesets
2911 deladded = _deleted - smf
2907 deladded = _deleted - smf
2912 deleted = _deleted - deladded
2908 deleted = _deleted - deladded
2913
2909
2914 # We need to account for the state of the file in the dirstate,
2910 # We need to account for the state of the file in the dirstate,
2915 # even when we revert against something else than parent. This will
2911 # even when we revert against something else than parent. This will
2916 # slightly alter the behavior of revert (doing back up or not, delete
2912 # slightly alter the behavior of revert (doing back up or not, delete
2917 # or just forget etc).
2913 # or just forget etc).
2918 if parent == node:
2914 if parent == node:
2919 dsmodified = modified
2915 dsmodified = modified
2920 dsadded = added
2916 dsadded = added
2921 dsremoved = removed
2917 dsremoved = removed
2922 # store all local modifications, useful later for rename detection
2918 # store all local modifications, useful later for rename detection
2923 localchanges = dsmodified | dsadded
2919 localchanges = dsmodified | dsadded
2924 modified, added, removed = set(), set(), set()
2920 modified, added, removed = set(), set(), set()
2925 else:
2921 else:
2926 changes = repo.status(node1=parent, match=m)
2922 changes = repo.status(node1=parent, match=m)
2927 dsmodified = set(changes.modified)
2923 dsmodified = set(changes.modified)
2928 dsadded = set(changes.added)
2924 dsadded = set(changes.added)
2929 dsremoved = set(changes.removed)
2925 dsremoved = set(changes.removed)
2930 # store all local modifications, useful later for rename detection
2926 # store all local modifications, useful later for rename detection
2931 localchanges = dsmodified | dsadded
2927 localchanges = dsmodified | dsadded
2932
2928
2933 # only take into account for removes between wc and target
2929 # only take into account for removes between wc and target
2934 clean |= dsremoved - removed
2930 clean |= dsremoved - removed
2935 dsremoved &= removed
2931 dsremoved &= removed
2936 # distinct between dirstate remove and other
2932 # distinct between dirstate remove and other
2937 removed -= dsremoved
2933 removed -= dsremoved
2938
2934
2939 modadded = added & dsmodified
2935 modadded = added & dsmodified
2940 added -= modadded
2936 added -= modadded
2941
2937
2942 # tell newly modified apart.
2938 # tell newly modified apart.
2943 dsmodified &= modified
2939 dsmodified &= modified
2944 dsmodified |= modified & dsadded # dirstate added may needs backup
2940 dsmodified |= modified & dsadded # dirstate added may needs backup
2945 modified -= dsmodified
2941 modified -= dsmodified
2946
2942
2947 # We need to wait for some post-processing to update this set
2943 # We need to wait for some post-processing to update this set
2948 # before making the distinction. The dirstate will be used for
2944 # before making the distinction. The dirstate will be used for
2949 # that purpose.
2945 # that purpose.
2950 dsadded = added
2946 dsadded = added
2951
2947
2952 # in case of merge, files that are actually added can be reported as
2948 # in case of merge, files that are actually added can be reported as
2953 # modified, we need to post process the result
2949 # modified, we need to post process the result
2954 if p2 != nullid:
2950 if p2 != nullid:
2955 if pmf is None:
2951 if pmf is None:
2956 # only need parent manifest in the merge case,
2952 # only need parent manifest in the merge case,
2957 # so do not read by default
2953 # so do not read by default
2958 pmf = repo[parent].manifest()
2954 pmf = repo[parent].manifest()
2959 mergeadd = dsmodified - set(pmf)
2955 mergeadd = dsmodified - set(pmf)
2960 dsadded |= mergeadd
2956 dsadded |= mergeadd
2961 dsmodified -= mergeadd
2957 dsmodified -= mergeadd
2962
2958
2963 # if f is a rename, update `names` to also revert the source
2959 # if f is a rename, update `names` to also revert the source
2964 cwd = repo.getcwd()
2960 cwd = repo.getcwd()
2965 for f in localchanges:
2961 for f in localchanges:
2966 src = repo.dirstate.copied(f)
2962 src = repo.dirstate.copied(f)
2967 # XXX should we check for rename down to target node?
2963 # XXX should we check for rename down to target node?
2968 if src and src not in names and repo.dirstate[src] == 'r':
2964 if src and src not in names and repo.dirstate[src] == 'r':
2969 dsremoved.add(src)
2965 dsremoved.add(src)
2970 names[src] = (repo.pathto(src, cwd), True)
2966 names[src] = (repo.pathto(src, cwd), True)
2971
2967
2972 # distinguish between file to forget and the other
2968 # distinguish between file to forget and the other
2973 added = set()
2969 added = set()
2974 for abs in dsadded:
2970 for abs in dsadded:
2975 if repo.dirstate[abs] != 'a':
2971 if repo.dirstate[abs] != 'a':
2976 added.add(abs)
2972 added.add(abs)
2977 dsadded -= added
2973 dsadded -= added
2978
2974
2979 for abs in deladded:
2975 for abs in deladded:
2980 if repo.dirstate[abs] == 'a':
2976 if repo.dirstate[abs] == 'a':
2981 dsadded.add(abs)
2977 dsadded.add(abs)
2982 deladded -= dsadded
2978 deladded -= dsadded
2983
2979
2984 # For files marked as removed, we check if an unknown file is present at
2980 # For files marked as removed, we check if an unknown file is present at
2985 # the same path. If a such file exists it may need to be backed up.
2981 # the same path. If a such file exists it may need to be backed up.
2986 # Making the distinction at this stage helps have simpler backup
2982 # Making the distinction at this stage helps have simpler backup
2987 # logic.
2983 # logic.
2988 removunk = set()
2984 removunk = set()
2989 for abs in removed:
2985 for abs in removed:
2990 target = repo.wjoin(abs)
2986 target = repo.wjoin(abs)
2991 if os.path.lexists(target):
2987 if os.path.lexists(target):
2992 removunk.add(abs)
2988 removunk.add(abs)
2993 removed -= removunk
2989 removed -= removunk
2994
2990
2995 dsremovunk = set()
2991 dsremovunk = set()
2996 for abs in dsremoved:
2992 for abs in dsremoved:
2997 target = repo.wjoin(abs)
2993 target = repo.wjoin(abs)
2998 if os.path.lexists(target):
2994 if os.path.lexists(target):
2999 dsremovunk.add(abs)
2995 dsremovunk.add(abs)
3000 dsremoved -= dsremovunk
2996 dsremoved -= dsremovunk
3001
2997
3002 # action to be actually performed by revert
2998 # action to be actually performed by revert
3003 # (<list of file>, message>) tuple
2999 # (<list of file>, message>) tuple
3004 actions = {'revert': ([], _('reverting %s\n')),
3000 actions = {'revert': ([], _('reverting %s\n')),
3005 'add': ([], _('adding %s\n')),
3001 'add': ([], _('adding %s\n')),
3006 'remove': ([], _('removing %s\n')),
3002 'remove': ([], _('removing %s\n')),
3007 'drop': ([], _('removing %s\n')),
3003 'drop': ([], _('removing %s\n')),
3008 'forget': ([], _('forgetting %s\n')),
3004 'forget': ([], _('forgetting %s\n')),
3009 'undelete': ([], _('undeleting %s\n')),
3005 'undelete': ([], _('undeleting %s\n')),
3010 'noop': (None, _('no changes needed to %s\n')),
3006 'noop': (None, _('no changes needed to %s\n')),
3011 'unknown': (None, _('file not managed: %s\n')),
3007 'unknown': (None, _('file not managed: %s\n')),
3012 }
3008 }
3013
3009
3014 # "constant" that convey the backup strategy.
3010 # "constant" that convey the backup strategy.
3015 # All set to `discard` if `no-backup` is set do avoid checking
3011 # All set to `discard` if `no-backup` is set do avoid checking
3016 # no_backup lower in the code.
3012 # no_backup lower in the code.
3017 # These values are ordered for comparison purposes
3013 # These values are ordered for comparison purposes
3018 backup = 2 # unconditionally do backup
3014 backup = 2 # unconditionally do backup
3019 check = 1 # check if the existing file differs from target
3015 check = 1 # check if the existing file differs from target
3020 discard = 0 # never do backup
3016 discard = 0 # never do backup
3021 if opts.get('no_backup'):
3017 if opts.get('no_backup'):
3022 backup = check = discard
3018 backup = check = discard
3023
3019
3024 backupanddel = actions['remove']
3020 backupanddel = actions['remove']
3025 if not opts.get('no_backup'):
3021 if not opts.get('no_backup'):
3026 backupanddel = actions['drop']
3022 backupanddel = actions['drop']
3027
3023
3028 disptable = (
3024 disptable = (
3029 # dispatch table:
3025 # dispatch table:
3030 # file state
3026 # file state
3031 # action
3027 # action
3032 # make backup
3028 # make backup
3033
3029
3034 ## Sets that results that will change file on disk
3030 ## Sets that results that will change file on disk
3035 # Modified compared to target, no local change
3031 # Modified compared to target, no local change
3036 (modified, actions['revert'], discard),
3032 (modified, actions['revert'], discard),
3037 # Modified compared to target, but local file is deleted
3033 # Modified compared to target, but local file is deleted
3038 (deleted, actions['revert'], discard),
3034 (deleted, actions['revert'], discard),
3039 # Modified compared to target, local change
3035 # Modified compared to target, local change
3040 (dsmodified, actions['revert'], backup),
3036 (dsmodified, actions['revert'], backup),
3041 # Added since target
3037 # Added since target
3042 (added, actions['remove'], discard),
3038 (added, actions['remove'], discard),
3043 # Added in working directory
3039 # Added in working directory
3044 (dsadded, actions['forget'], discard),
3040 (dsadded, actions['forget'], discard),
3045 # Added since target, have local modification
3041 # Added since target, have local modification
3046 (modadded, backupanddel, backup),
3042 (modadded, backupanddel, backup),
3047 # Added since target but file is missing in working directory
3043 # Added since target but file is missing in working directory
3048 (deladded, actions['drop'], discard),
3044 (deladded, actions['drop'], discard),
3049 # Removed since target, before working copy parent
3045 # Removed since target, before working copy parent
3050 (removed, actions['add'], discard),
3046 (removed, actions['add'], discard),
3051 # Same as `removed` but an unknown file exists at the same path
3047 # Same as `removed` but an unknown file exists at the same path
3052 (removunk, actions['add'], check),
3048 (removunk, actions['add'], check),
3053 # Removed since targe, marked as such in working copy parent
3049 # Removed since targe, marked as such in working copy parent
3054 (dsremoved, actions['undelete'], discard),
3050 (dsremoved, actions['undelete'], discard),
3055 # Same as `dsremoved` but an unknown file exists at the same path
3051 # Same as `dsremoved` but an unknown file exists at the same path
3056 (dsremovunk, actions['undelete'], check),
3052 (dsremovunk, actions['undelete'], check),
3057 ## the following sets does not result in any file changes
3053 ## the following sets does not result in any file changes
3058 # File with no modification
3054 # File with no modification
3059 (clean, actions['noop'], discard),
3055 (clean, actions['noop'], discard),
3060 # Existing file, not tracked anywhere
3056 # Existing file, not tracked anywhere
3061 (unknown, actions['unknown'], discard),
3057 (unknown, actions['unknown'], discard),
3062 )
3058 )
3063
3059
3064 for abs, (rel, exact) in sorted(names.items()):
3060 for abs, (rel, exact) in sorted(names.items()):
3065 # target file to be touch on disk (relative to cwd)
3061 # target file to be touch on disk (relative to cwd)
3066 target = repo.wjoin(abs)
3062 target = repo.wjoin(abs)
3067 # search the entry in the dispatch table.
3063 # search the entry in the dispatch table.
3068 # if the file is in any of these sets, it was touched in the working
3064 # if the file is in any of these sets, it was touched in the working
3069 # directory parent and we are sure it needs to be reverted.
3065 # directory parent and we are sure it needs to be reverted.
3070 for table, (xlist, msg), dobackup in disptable:
3066 for table, (xlist, msg), dobackup in disptable:
3071 if abs not in table:
3067 if abs not in table:
3072 continue
3068 continue
3073 if xlist is not None:
3069 if xlist is not None:
3074 xlist.append(abs)
3070 xlist.append(abs)
3075 if dobackup and (backup <= dobackup
3071 if dobackup and (backup <= dobackup
3076 or wctx[abs].cmp(ctx[abs])):
3072 or wctx[abs].cmp(ctx[abs])):
3077 bakname = "%s.orig" % rel
3073 bakname = "%s.orig" % rel
3078 ui.note(_('saving current version of %s as %s\n') %
3074 ui.note(_('saving current version of %s as %s\n') %
3079 (rel, bakname))
3075 (rel, bakname))
3080 if not opts.get('dry_run'):
3076 if not opts.get('dry_run'):
3081 if interactive:
3077 if interactive:
3082 util.copyfile(target, bakname)
3078 util.copyfile(target, bakname)
3083 else:
3079 else:
3084 util.rename(target, bakname)
3080 util.rename(target, bakname)
3085 if ui.verbose or not exact:
3081 if ui.verbose or not exact:
3086 if not isinstance(msg, basestring):
3082 if not isinstance(msg, basestring):
3087 msg = msg(abs)
3083 msg = msg(abs)
3088 ui.status(msg % rel)
3084 ui.status(msg % rel)
3089 elif exact:
3085 elif exact:
3090 ui.warn(msg % rel)
3086 ui.warn(msg % rel)
3091 break
3087 break
3092
3088
3093 if not opts.get('dry_run'):
3089 if not opts.get('dry_run'):
3094 needdata = ('revert', 'add', 'undelete')
3090 needdata = ('revert', 'add', 'undelete')
3095 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3091 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3096 _performrevert(repo, parents, ctx, actions, interactive)
3092 _performrevert(repo, parents, ctx, actions, interactive)
3097
3093
3098 if targetsubs:
3094 if targetsubs:
3099 # Revert the subrepos on the revert list
3095 # Revert the subrepos on the revert list
3100 for sub in targetsubs:
3096 for sub in targetsubs:
3101 try:
3097 try:
3102 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3098 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3103 except KeyError:
3099 except KeyError:
3104 raise util.Abort("subrepository '%s' does not exist in %s!"
3100 raise util.Abort("subrepository '%s' does not exist in %s!"
3105 % (sub, short(ctx.node())))
3101 % (sub, short(ctx.node())))
3106 finally:
3102 finally:
3107 wlock.release()
3103 wlock.release()
3108
3104
3109 def _revertprefetch(repo, ctx, *files):
3105 def _revertprefetch(repo, ctx, *files):
3110 """Let extension changing the storage layer prefetch content"""
3106 """Let extension changing the storage layer prefetch content"""
3111 pass
3107 pass
3112
3108
3113 def _performrevert(repo, parents, ctx, actions, interactive=False):
3109 def _performrevert(repo, parents, ctx, actions, interactive=False):
3114 """function that actually perform all the actions computed for revert
3110 """function that actually perform all the actions computed for revert
3115
3111
3116 This is an independent function to let extension to plug in and react to
3112 This is an independent function to let extension to plug in and react to
3117 the imminent revert.
3113 the imminent revert.
3118
3114
3119 Make sure you have the working directory locked when calling this function.
3115 Make sure you have the working directory locked when calling this function.
3120 """
3116 """
3121 parent, p2 = parents
3117 parent, p2 = parents
3122 node = ctx.node()
3118 node = ctx.node()
3123 def checkout(f):
3119 def checkout(f):
3124 fc = ctx[f]
3120 fc = ctx[f]
3125 repo.wwrite(f, fc.data(), fc.flags())
3121 repo.wwrite(f, fc.data(), fc.flags())
3126
3122
3127 audit_path = pathutil.pathauditor(repo.root)
3123 audit_path = pathutil.pathauditor(repo.root)
3128 for f in actions['forget'][0]:
3124 for f in actions['forget'][0]:
3129 repo.dirstate.drop(f)
3125 repo.dirstate.drop(f)
3130 for f in actions['remove'][0]:
3126 for f in actions['remove'][0]:
3131 audit_path(f)
3127 audit_path(f)
3132 try:
3128 try:
3133 util.unlinkpath(repo.wjoin(f))
3129 util.unlinkpath(repo.wjoin(f))
3134 except OSError:
3130 except OSError:
3135 pass
3131 pass
3136 repo.dirstate.remove(f)
3132 repo.dirstate.remove(f)
3137 for f in actions['drop'][0]:
3133 for f in actions['drop'][0]:
3138 audit_path(f)
3134 audit_path(f)
3139 repo.dirstate.remove(f)
3135 repo.dirstate.remove(f)
3140
3136
3141 normal = None
3137 normal = None
3142 if node == parent:
3138 if node == parent:
3143 # We're reverting to our parent. If possible, we'd like status
3139 # We're reverting to our parent. If possible, we'd like status
3144 # to report the file as clean. We have to use normallookup for
3140 # to report the file as clean. We have to use normallookup for
3145 # merges to avoid losing information about merged/dirty files.
3141 # merges to avoid losing information about merged/dirty files.
3146 if p2 != nullid:
3142 if p2 != nullid:
3147 normal = repo.dirstate.normallookup
3143 normal = repo.dirstate.normallookup
3148 else:
3144 else:
3149 normal = repo.dirstate.normal
3145 normal = repo.dirstate.normal
3150
3146
3151 newlyaddedandmodifiedfiles = set()
3147 newlyaddedandmodifiedfiles = set()
3152 if interactive:
3148 if interactive:
3153 # Prompt the user for changes to revert
3149 # Prompt the user for changes to revert
3154 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3150 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3155 m = scmutil.match(ctx, torevert, {})
3151 m = scmutil.match(ctx, torevert, {})
3156 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3152 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3157 diffopts.nodates = True
3153 diffopts.nodates = True
3158 diffopts.git = True
3154 diffopts.git = True
3159 reversehunks = repo.ui.configbool('experimental',
3155 reversehunks = repo.ui.configbool('experimental',
3160 'revertalternateinteractivemode',
3156 'revertalternateinteractivemode',
3161 True)
3157 True)
3162 if reversehunks:
3158 if reversehunks:
3163 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3159 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3164 else:
3160 else:
3165 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3161 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3166 originalchunks = patch.parsepatch(diff)
3162 originalchunks = patch.parsepatch(diff)
3167
3163
3168 try:
3164 try:
3169
3165
3170 chunks = recordfilter(repo.ui, originalchunks)
3166 chunks = recordfilter(repo.ui, originalchunks)
3171 if reversehunks:
3167 if reversehunks:
3172 chunks = patch.reversehunks(chunks)
3168 chunks = patch.reversehunks(chunks)
3173
3169
3174 except patch.PatchError as err:
3170 except patch.PatchError as err:
3175 raise util.Abort(_('error parsing patch: %s') % err)
3171 raise util.Abort(_('error parsing patch: %s') % err)
3176
3172
3177 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3173 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3178 # Apply changes
3174 # Apply changes
3179 fp = cStringIO.StringIO()
3175 fp = cStringIO.StringIO()
3180 for c in chunks:
3176 for c in chunks:
3181 c.write(fp)
3177 c.write(fp)
3182 dopatch = fp.tell()
3178 dopatch = fp.tell()
3183 fp.seek(0)
3179 fp.seek(0)
3184 if dopatch:
3180 if dopatch:
3185 try:
3181 try:
3186 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3182 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3187 except patch.PatchError as err:
3183 except patch.PatchError as err:
3188 raise util.Abort(str(err))
3184 raise util.Abort(str(err))
3189 del fp
3185 del fp
3190 else:
3186 else:
3191 for f in actions['revert'][0]:
3187 for f in actions['revert'][0]:
3192 checkout(f)
3188 checkout(f)
3193 if normal:
3189 if normal:
3194 normal(f)
3190 normal(f)
3195
3191
3196 for f in actions['add'][0]:
3192 for f in actions['add'][0]:
3197 # Don't checkout modified files, they are already created by the diff
3193 # Don't checkout modified files, they are already created by the diff
3198 if f not in newlyaddedandmodifiedfiles:
3194 if f not in newlyaddedandmodifiedfiles:
3199 checkout(f)
3195 checkout(f)
3200 repo.dirstate.add(f)
3196 repo.dirstate.add(f)
3201
3197
3202 normal = repo.dirstate.normallookup
3198 normal = repo.dirstate.normallookup
3203 if node == parent and p2 == nullid:
3199 if node == parent and p2 == nullid:
3204 normal = repo.dirstate.normal
3200 normal = repo.dirstate.normal
3205 for f in actions['undelete'][0]:
3201 for f in actions['undelete'][0]:
3206 checkout(f)
3202 checkout(f)
3207 normal(f)
3203 normal(f)
3208
3204
3209 copied = copies.pathcopies(repo[parent], ctx)
3205 copied = copies.pathcopies(repo[parent], ctx)
3210
3206
3211 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3207 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3212 if f in copied:
3208 if f in copied:
3213 repo.dirstate.copy(copied[f], f)
3209 repo.dirstate.copy(copied[f], f)
3214
3210
3215 def command(table):
3211 def command(table):
3216 """Returns a function object to be used as a decorator for making commands.
3212 """Returns a function object to be used as a decorator for making commands.
3217
3213
3218 This function receives a command table as its argument. The table should
3214 This function receives a command table as its argument. The table should
3219 be a dict.
3215 be a dict.
3220
3216
3221 The returned function can be used as a decorator for adding commands
3217 The returned function can be used as a decorator for adding commands
3222 to that command table. This function accepts multiple arguments to define
3218 to that command table. This function accepts multiple arguments to define
3223 a command.
3219 a command.
3224
3220
3225 The first argument is the command name.
3221 The first argument is the command name.
3226
3222
3227 The options argument is an iterable of tuples defining command arguments.
3223 The options argument is an iterable of tuples defining command arguments.
3228 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3224 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3229
3225
3230 The synopsis argument defines a short, one line summary of how to use the
3226 The synopsis argument defines a short, one line summary of how to use the
3231 command. This shows up in the help output.
3227 command. This shows up in the help output.
3232
3228
3233 The norepo argument defines whether the command does not require a
3229 The norepo argument defines whether the command does not require a
3234 local repository. Most commands operate against a repository, thus the
3230 local repository. Most commands operate against a repository, thus the
3235 default is False.
3231 default is False.
3236
3232
3237 The optionalrepo argument defines whether the command optionally requires
3233 The optionalrepo argument defines whether the command optionally requires
3238 a local repository.
3234 a local repository.
3239
3235
3240 The inferrepo argument defines whether to try to find a repository from the
3236 The inferrepo argument defines whether to try to find a repository from the
3241 command line arguments. If True, arguments will be examined for potential
3237 command line arguments. If True, arguments will be examined for potential
3242 repository locations. See ``findrepo()``. If a repository is found, it
3238 repository locations. See ``findrepo()``. If a repository is found, it
3243 will be used.
3239 will be used.
3244 """
3240 """
3245 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3241 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3246 inferrepo=False):
3242 inferrepo=False):
3247 def decorator(func):
3243 def decorator(func):
3248 if synopsis:
3244 if synopsis:
3249 table[name] = func, list(options), synopsis
3245 table[name] = func, list(options), synopsis
3250 else:
3246 else:
3251 table[name] = func, list(options)
3247 table[name] = func, list(options)
3252
3248
3253 if norepo:
3249 if norepo:
3254 # Avoid import cycle.
3250 # Avoid import cycle.
3255 import commands
3251 import commands
3256 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3252 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3257
3253
3258 if optionalrepo:
3254 if optionalrepo:
3259 import commands
3255 import commands
3260 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3256 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3261
3257
3262 if inferrepo:
3258 if inferrepo:
3263 import commands
3259 import commands
3264 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3260 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3265
3261
3266 return func
3262 return func
3267 return decorator
3263 return decorator
3268
3264
3269 return cmd
3265 return cmd
3270
3266
3271 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3267 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3272 # commands.outgoing. "missing" is "missing" of the result of
3268 # commands.outgoing. "missing" is "missing" of the result of
3273 # "findcommonoutgoing()"
3269 # "findcommonoutgoing()"
3274 outgoinghooks = util.hooks()
3270 outgoinghooks = util.hooks()
3275
3271
3276 # a list of (ui, repo) functions called by commands.summary
3272 # a list of (ui, repo) functions called by commands.summary
3277 summaryhooks = util.hooks()
3273 summaryhooks = util.hooks()
3278
3274
3279 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3275 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3280 #
3276 #
3281 # functions should return tuple of booleans below, if 'changes' is None:
3277 # functions should return tuple of booleans below, if 'changes' is None:
3282 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3278 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3283 #
3279 #
3284 # otherwise, 'changes' is a tuple of tuples below:
3280 # otherwise, 'changes' is a tuple of tuples below:
3285 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3281 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3286 # - (desturl, destbranch, destpeer, outgoing)
3282 # - (desturl, destbranch, destpeer, outgoing)
3287 summaryremotehooks = util.hooks()
3283 summaryremotehooks = util.hooks()
3288
3284
3289 # A list of state files kept by multistep operations like graft.
3285 # A list of state files kept by multistep operations like graft.
3290 # Since graft cannot be aborted, it is considered 'clearable' by update.
3286 # Since graft cannot be aborted, it is considered 'clearable' by update.
3291 # note: bisect is intentionally excluded
3287 # note: bisect is intentionally excluded
3292 # (state file, clearable, allowcommit, error, hint)
3288 # (state file, clearable, allowcommit, error, hint)
3293 unfinishedstates = [
3289 unfinishedstates = [
3294 ('graftstate', True, False, _('graft in progress'),
3290 ('graftstate', True, False, _('graft in progress'),
3295 _("use 'hg graft --continue' or 'hg update' to abort")),
3291 _("use 'hg graft --continue' or 'hg update' to abort")),
3296 ('updatestate', True, False, _('last update was interrupted'),
3292 ('updatestate', True, False, _('last update was interrupted'),
3297 _("use 'hg update' to get a consistent checkout"))
3293 _("use 'hg update' to get a consistent checkout"))
3298 ]
3294 ]
3299
3295
3300 def checkunfinished(repo, commit=False):
3296 def checkunfinished(repo, commit=False):
3301 '''Look for an unfinished multistep operation, like graft, and abort
3297 '''Look for an unfinished multistep operation, like graft, and abort
3302 if found. It's probably good to check this right before
3298 if found. It's probably good to check this right before
3303 bailifchanged().
3299 bailifchanged().
3304 '''
3300 '''
3305 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3301 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3306 if commit and allowcommit:
3302 if commit and allowcommit:
3307 continue
3303 continue
3308 if repo.vfs.exists(f):
3304 if repo.vfs.exists(f):
3309 raise util.Abort(msg, hint=hint)
3305 raise util.Abort(msg, hint=hint)
3310
3306
3311 def clearunfinished(repo):
3307 def clearunfinished(repo):
3312 '''Check for unfinished operations (as above), and clear the ones
3308 '''Check for unfinished operations (as above), and clear the ones
3313 that are clearable.
3309 that are clearable.
3314 '''
3310 '''
3315 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3311 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3316 if not clearable and repo.vfs.exists(f):
3312 if not clearable and repo.vfs.exists(f):
3317 raise util.Abort(msg, hint=hint)
3313 raise util.Abort(msg, hint=hint)
3318 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3314 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3319 if clearable and repo.vfs.exists(f):
3315 if clearable and repo.vfs.exists(f):
3320 util.unlink(repo.join(f))
3316 util.unlink(repo.join(f))
3321
3317
3322 class dirstateguard(object):
3318 class dirstateguard(object):
3323 '''Restore dirstate at unexpected failure.
3319 '''Restore dirstate at unexpected failure.
3324
3320
3325 At the construction, this class does:
3321 At the construction, this class does:
3326
3322
3327 - write current ``repo.dirstate`` out, and
3323 - write current ``repo.dirstate`` out, and
3328 - save ``.hg/dirstate`` into the backup file
3324 - save ``.hg/dirstate`` into the backup file
3329
3325
3330 This restores ``.hg/dirstate`` from backup file, if ``release()``
3326 This restores ``.hg/dirstate`` from backup file, if ``release()``
3331 is invoked before ``close()``.
3327 is invoked before ``close()``.
3332
3328
3333 This just removes the backup file at ``close()`` before ``release()``.
3329 This just removes the backup file at ``close()`` before ``release()``.
3334 '''
3330 '''
3335
3331
3336 def __init__(self, repo, name):
3332 def __init__(self, repo, name):
3337 repo.dirstate.write()
3333 repo.dirstate.write()
3338 self._repo = repo
3334 self._repo = repo
3339 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3335 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3340 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3336 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3341 self._active = True
3337 self._active = True
3342 self._closed = False
3338 self._closed = False
3343
3339
3344 def __del__(self):
3340 def __del__(self):
3345 if self._active: # still active
3341 if self._active: # still active
3346 # this may occur, even if this class is used correctly:
3342 # this may occur, even if this class is used correctly:
3347 # for example, releasing other resources like transaction
3343 # for example, releasing other resources like transaction
3348 # may raise exception before ``dirstateguard.release`` in
3344 # may raise exception before ``dirstateguard.release`` in
3349 # ``release(tr, ....)``.
3345 # ``release(tr, ....)``.
3350 self._abort()
3346 self._abort()
3351
3347
3352 def close(self):
3348 def close(self):
3353 if not self._active: # already inactivated
3349 if not self._active: # already inactivated
3354 msg = (_("can't close already inactivated backup: %s")
3350 msg = (_("can't close already inactivated backup: %s")
3355 % self._filename)
3351 % self._filename)
3356 raise util.Abort(msg)
3352 raise util.Abort(msg)
3357
3353
3358 self._repo.vfs.unlink(self._filename)
3354 self._repo.vfs.unlink(self._filename)
3359 self._active = False
3355 self._active = False
3360 self._closed = True
3356 self._closed = True
3361
3357
3362 def _abort(self):
3358 def _abort(self):
3363 # this "invalidate()" prevents "wlock.release()" from writing
3359 # this "invalidate()" prevents "wlock.release()" from writing
3364 # changes of dirstate out after restoring to original status
3360 # changes of dirstate out after restoring to original status
3365 self._repo.dirstate.invalidate()
3361 self._repo.dirstate.invalidate()
3366
3362
3367 self._repo.vfs.rename(self._filename, 'dirstate')
3363 self._repo.vfs.rename(self._filename, 'dirstate')
3368 self._active = False
3364 self._active = False
3369
3365
3370 def release(self):
3366 def release(self):
3371 if not self._closed:
3367 if not self._closed:
3372 if not self._active: # already inactivated
3368 if not self._active: # already inactivated
3373 msg = (_("can't release already inactivated backup: %s")
3369 msg = (_("can't release already inactivated backup: %s")
3374 % self._filename)
3370 % self._filename)
3375 raise util.Abort(msg)
3371 raise util.Abort(msg)
3376 self._abort()
3372 self._abort()
3377
3373
3378 _bundlecompspecs = {'none': None,
3374 _bundlecompspecs = {'none': None,
3379 'bzip2': 'BZ',
3375 'bzip2': 'BZ',
3380 'gzip': 'GZ',
3376 'gzip': 'GZ',
3381 }
3377 }
3382
3378
3383 _bundleversionspecs = {'v1': '01',
3379 _bundleversionspecs = {'v1': '01',
3384 'v2': '02',
3380 'v2': '02',
3385 'bundle2': '02', #legacy
3381 'bundle2': '02', #legacy
3386 }
3382 }
3387
3383
3388 def parsebundletype(repo, spec):
3384 def parsebundletype(repo, spec):
3389 """return the internal bundle type to use from a user input
3385 """return the internal bundle type to use from a user input
3390
3386
3391 This is parsing user specified bundle type as accepted in:
3387 This is parsing user specified bundle type as accepted in:
3392
3388
3393 'hg bundle --type TYPE'.
3389 'hg bundle --type TYPE'.
3394
3390
3395 It accept format in the form [compression][-version]|[version]
3391 It accept format in the form [compression][-version]|[version]
3396
3392
3397 Consensus about extensions of the format for various bundle2 feature
3393 Consensus about extensions of the format for various bundle2 feature
3398 is to prefix any feature with "+". eg "+treemanifest" or "gzip+phases"
3394 is to prefix any feature with "+". eg "+treemanifest" or "gzip+phases"
3399 """
3395 """
3400 comp, version = None, None
3396 comp, version = None, None
3401
3397
3402 if '-' in spec:
3398 if '-' in spec:
3403 comp, version = spec.split('-', 1)
3399 comp, version = spec.split('-', 1)
3404 elif spec in _bundlecompspecs:
3400 elif spec in _bundlecompspecs:
3405 comp = spec
3401 comp = spec
3406 elif spec in _bundleversionspecs:
3402 elif spec in _bundleversionspecs:
3407 version = spec
3403 version = spec
3408 else:
3404 else:
3409 raise util.Abort(_('unknown bundle type specified with --type'))
3405 raise util.Abort(_('unknown bundle type specified with --type'))
3410
3406
3411 if comp is None:
3407 if comp is None:
3412 comp = 'BZ'
3408 comp = 'BZ'
3413 else:
3409 else:
3414 try:
3410 try:
3415 comp = _bundlecompspecs[comp]
3411 comp = _bundlecompspecs[comp]
3416 except KeyError:
3412 except KeyError:
3417 raise util.Abort(_('unknown bundle type specified with --type'))
3413 raise util.Abort(_('unknown bundle type specified with --type'))
3418
3414
3419 if version is None:
3415 if version is None:
3420 version = '01'
3416 version = '01'
3421 if 'generaldelta' in repo.requirements:
3417 if 'generaldelta' in repo.requirements:
3422 version = '02'
3418 version = '02'
3423 else:
3419 else:
3424 try:
3420 try:
3425 version = _bundleversionspecs[version]
3421 version = _bundleversionspecs[version]
3426 except KeyError:
3422 except KeyError:
3427 raise util.Abort(_('unknown bundle type specified with --type'))
3423 raise util.Abort(_('unknown bundle type specified with --type'))
3428
3424
3429 return version, comp
3425 return version, comp
General Comments 0
You need to be logged in to leave comments. Login now