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