##// END OF EJS Templates
changeset_printer: use node.wdirrev to calculate meaningful parentrevs...
Yuya Nishihara -
r25739:3dabc9b7 default
parent child Browse files
Show More
@@ -1,3326 +1,3326 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile, cStringIO, shutil
10 import os, sys, errno, re, tempfile, cStringIO, shutil
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import encoding
16 import encoding
17 import formatter
17 import formatter
18 import crecord as crecordmod
18 import crecord as crecordmod
19 import lock as lockmod
19 import lock as lockmod
20
20
21 def ishunk(x):
21 def ishunk(x):
22 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
22 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
23 return isinstance(x, hunkclasses)
23 return isinstance(x, hunkclasses)
24
24
25 def newandmodified(chunks, originalchunks):
25 def newandmodified(chunks, originalchunks):
26 newlyaddedandmodifiedfiles = set()
26 newlyaddedandmodifiedfiles = set()
27 for chunk in chunks:
27 for chunk in chunks:
28 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
28 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
29 originalchunks:
29 originalchunks:
30 newlyaddedandmodifiedfiles.add(chunk.header.filename())
30 newlyaddedandmodifiedfiles.add(chunk.header.filename())
31 return newlyaddedandmodifiedfiles
31 return newlyaddedandmodifiedfiles
32
32
33 def parsealiases(cmd):
33 def parsealiases(cmd):
34 return cmd.lstrip("^").split("|")
34 return cmd.lstrip("^").split("|")
35
35
36 def setupwrapcolorwrite(ui):
36 def setupwrapcolorwrite(ui):
37 # wrap ui.write so diff output can be labeled/colorized
37 # wrap ui.write so diff output can be labeled/colorized
38 def wrapwrite(orig, *args, **kw):
38 def wrapwrite(orig, *args, **kw):
39 label = kw.pop('label', '')
39 label = kw.pop('label', '')
40 for chunk, l in patch.difflabel(lambda: args):
40 for chunk, l in patch.difflabel(lambda: args):
41 orig(chunk, label=label + l)
41 orig(chunk, label=label + l)
42
42
43 oldwrite = ui.write
43 oldwrite = ui.write
44 def wrap(*args, **kwargs):
44 def wrap(*args, **kwargs):
45 return wrapwrite(oldwrite, *args, **kwargs)
45 return wrapwrite(oldwrite, *args, **kwargs)
46 setattr(ui, 'write', wrap)
46 setattr(ui, 'write', wrap)
47 return oldwrite
47 return oldwrite
48
48
49 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
49 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
50 if usecurses:
50 if usecurses:
51 if testfile:
51 if testfile:
52 recordfn = crecordmod.testdecorator(testfile,
52 recordfn = crecordmod.testdecorator(testfile,
53 crecordmod.testchunkselector)
53 crecordmod.testchunkselector)
54 else:
54 else:
55 recordfn = crecordmod.chunkselector
55 recordfn = crecordmod.chunkselector
56
56
57 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
57 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
58
58
59 else:
59 else:
60 return patch.filterpatch(ui, originalhunks, operation)
60 return patch.filterpatch(ui, originalhunks, operation)
61
61
62 def recordfilter(ui, originalhunks, operation=None):
62 def recordfilter(ui, originalhunks, operation=None):
63 """ Prompts the user to filter the originalhunks and return a list of
63 """ Prompts the user to filter the originalhunks and return a list of
64 selected hunks.
64 selected hunks.
65 *operation* is used for ui purposes to indicate the user
65 *operation* is used for ui purposes to indicate the user
66 what kind of filtering they are doing: reverting, commiting, shelving, etc.
66 what kind of filtering they are doing: reverting, commiting, shelving, etc.
67 *operation* has to be a translated string.
67 *operation* has to be a translated string.
68 """
68 """
69 usecurses = ui.configbool('experimental', 'crecord', False)
69 usecurses = ui.configbool('experimental', 'crecord', False)
70 testfile = ui.config('experimental', 'crecordtest', None)
70 testfile = ui.config('experimental', 'crecordtest', None)
71 oldwrite = setupwrapcolorwrite(ui)
71 oldwrite = setupwrapcolorwrite(ui)
72 try:
72 try:
73 newchunks = filterchunks(ui, originalhunks, usecurses, testfile,
73 newchunks = filterchunks(ui, originalhunks, usecurses, testfile,
74 operation)
74 operation)
75 finally:
75 finally:
76 ui.write = oldwrite
76 ui.write = oldwrite
77 return newchunks
77 return newchunks
78
78
79 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
79 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
80 filterfn, *pats, **opts):
80 filterfn, *pats, **opts):
81 import merge as mergemod
81 import merge as mergemod
82
82
83 if not ui.interactive():
83 if not ui.interactive():
84 raise util.Abort(_('running non-interactively, use %s instead') %
84 raise util.Abort(_('running non-interactively, use %s instead') %
85 cmdsuggest)
85 cmdsuggest)
86
86
87 # make sure username is set before going interactive
87 # make sure username is set before going interactive
88 if not opts.get('user'):
88 if not opts.get('user'):
89 ui.username() # raise exception, username not provided
89 ui.username() # raise exception, username not provided
90
90
91 def recordfunc(ui, repo, message, match, opts):
91 def recordfunc(ui, repo, message, match, opts):
92 """This is generic record driver.
92 """This is generic record driver.
93
93
94 Its job is to interactively filter local changes, and
94 Its job is to interactively filter local changes, and
95 accordingly prepare working directory into a state in which the
95 accordingly prepare working directory into a state in which the
96 job can be delegated to a non-interactive commit command such as
96 job can be delegated to a non-interactive commit command such as
97 'commit' or 'qrefresh'.
97 'commit' or 'qrefresh'.
98
98
99 After the actual job is done by non-interactive command, the
99 After the actual job is done by non-interactive command, the
100 working directory is restored to its original state.
100 working directory is restored to its original state.
101
101
102 In the end we'll record interesting changes, and everything else
102 In the end we'll record interesting changes, and everything else
103 will be left in place, so the user can continue working.
103 will be left in place, so the user can continue working.
104 """
104 """
105
105
106 checkunfinished(repo, commit=True)
106 checkunfinished(repo, commit=True)
107 merge = len(repo[None].parents()) > 1
107 merge = len(repo[None].parents()) > 1
108 if merge:
108 if merge:
109 raise util.Abort(_('cannot partially commit a merge '
109 raise util.Abort(_('cannot partially commit a merge '
110 '(use "hg commit" instead)'))
110 '(use "hg commit" instead)'))
111
111
112 status = repo.status(match=match)
112 status = repo.status(match=match)
113 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
113 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
114 diffopts.nodates = True
114 diffopts.nodates = True
115 diffopts.git = True
115 diffopts.git = True
116 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
116 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
117 originalchunks = patch.parsepatch(originaldiff)
117 originalchunks = patch.parsepatch(originaldiff)
118
118
119 # 1. filter patch, so we have intending-to apply subset of it
119 # 1. filter patch, so we have intending-to apply subset of it
120 try:
120 try:
121 chunks = filterfn(ui, originalchunks)
121 chunks = filterfn(ui, originalchunks)
122 except patch.PatchError as err:
122 except patch.PatchError as err:
123 raise util.Abort(_('error parsing patch: %s') % err)
123 raise util.Abort(_('error parsing patch: %s') % err)
124
124
125 # We need to keep a backup of files that have been newly added and
125 # We need to keep a backup of files that have been newly added and
126 # modified during the recording process because there is a previous
126 # modified during the recording process because there is a previous
127 # version without the edit in the workdir
127 # version without the edit in the workdir
128 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
128 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
129 contenders = set()
129 contenders = set()
130 for h in chunks:
130 for h in chunks:
131 try:
131 try:
132 contenders.update(set(h.files()))
132 contenders.update(set(h.files()))
133 except AttributeError:
133 except AttributeError:
134 pass
134 pass
135
135
136 changed = status.modified + status.added + status.removed
136 changed = status.modified + status.added + status.removed
137 newfiles = [f for f in changed if f in contenders]
137 newfiles = [f for f in changed if f in contenders]
138 if not newfiles:
138 if not newfiles:
139 ui.status(_('no changes to record\n'))
139 ui.status(_('no changes to record\n'))
140 return 0
140 return 0
141
141
142 modified = set(status.modified)
142 modified = set(status.modified)
143
143
144 # 2. backup changed files, so we can restore them in the end
144 # 2. backup changed files, so we can restore them in the end
145
145
146 if backupall:
146 if backupall:
147 tobackup = changed
147 tobackup = changed
148 else:
148 else:
149 tobackup = [f for f in newfiles if f in modified or f in \
149 tobackup = [f for f in newfiles if f in modified or f in \
150 newlyaddedandmodifiedfiles]
150 newlyaddedandmodifiedfiles]
151 backups = {}
151 backups = {}
152 if tobackup:
152 if tobackup:
153 backupdir = repo.join('record-backups')
153 backupdir = repo.join('record-backups')
154 try:
154 try:
155 os.mkdir(backupdir)
155 os.mkdir(backupdir)
156 except OSError as err:
156 except OSError as err:
157 if err.errno != errno.EEXIST:
157 if err.errno != errno.EEXIST:
158 raise
158 raise
159 try:
159 try:
160 # backup continues
160 # backup continues
161 for f in tobackup:
161 for f in tobackup:
162 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
162 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
163 dir=backupdir)
163 dir=backupdir)
164 os.close(fd)
164 os.close(fd)
165 ui.debug('backup %r as %r\n' % (f, tmpname))
165 ui.debug('backup %r as %r\n' % (f, tmpname))
166 util.copyfile(repo.wjoin(f), tmpname)
166 util.copyfile(repo.wjoin(f), tmpname)
167 shutil.copystat(repo.wjoin(f), tmpname)
167 shutil.copystat(repo.wjoin(f), tmpname)
168 backups[f] = tmpname
168 backups[f] = tmpname
169
169
170 fp = cStringIO.StringIO()
170 fp = cStringIO.StringIO()
171 for c in chunks:
171 for c in chunks:
172 fname = c.filename()
172 fname = c.filename()
173 if fname in backups:
173 if fname in backups:
174 c.write(fp)
174 c.write(fp)
175 dopatch = fp.tell()
175 dopatch = fp.tell()
176 fp.seek(0)
176 fp.seek(0)
177
177
178 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
178 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
179 # 3a. apply filtered patch to clean repo (clean)
179 # 3a. apply filtered patch to clean repo (clean)
180 if backups:
180 if backups:
181 # Equivalent to hg.revert
181 # Equivalent to hg.revert
182 choices = lambda key: key in backups
182 choices = lambda key: key in backups
183 mergemod.update(repo, repo.dirstate.p1(),
183 mergemod.update(repo, repo.dirstate.p1(),
184 False, True, choices)
184 False, True, choices)
185
185
186 # 3b. (apply)
186 # 3b. (apply)
187 if dopatch:
187 if dopatch:
188 try:
188 try:
189 ui.debug('applying patch\n')
189 ui.debug('applying patch\n')
190 ui.debug(fp.getvalue())
190 ui.debug(fp.getvalue())
191 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
191 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
192 except patch.PatchError as err:
192 except patch.PatchError as err:
193 raise util.Abort(str(err))
193 raise util.Abort(str(err))
194 del fp
194 del fp
195
195
196 # 4. We prepared working directory according to filtered
196 # 4. We prepared working directory according to filtered
197 # patch. Now is the time to delegate the job to
197 # patch. Now is the time to delegate the job to
198 # commit/qrefresh or the like!
198 # commit/qrefresh or the like!
199
199
200 # Make all of the pathnames absolute.
200 # Make all of the pathnames absolute.
201 newfiles = [repo.wjoin(nf) for nf in newfiles]
201 newfiles = [repo.wjoin(nf) for nf in newfiles]
202 return commitfunc(ui, repo, *newfiles, **opts)
202 return commitfunc(ui, repo, *newfiles, **opts)
203 finally:
203 finally:
204 # 5. finally restore backed-up files
204 # 5. finally restore backed-up files
205 try:
205 try:
206 for realname, tmpname in backups.iteritems():
206 for realname, tmpname in backups.iteritems():
207 ui.debug('restoring %r to %r\n' % (tmpname, realname))
207 ui.debug('restoring %r to %r\n' % (tmpname, realname))
208 util.copyfile(tmpname, repo.wjoin(realname))
208 util.copyfile(tmpname, repo.wjoin(realname))
209 # Our calls to copystat() here and above are a
209 # Our calls to copystat() here and above are a
210 # hack to trick any editors that have f open that
210 # hack to trick any editors that have f open that
211 # we haven't modified them.
211 # we haven't modified them.
212 #
212 #
213 # Also note that this racy as an editor could
213 # Also note that this racy as an editor could
214 # notice the file's mtime before we've finished
214 # notice the file's mtime before we've finished
215 # writing it.
215 # writing it.
216 shutil.copystat(tmpname, repo.wjoin(realname))
216 shutil.copystat(tmpname, repo.wjoin(realname))
217 os.unlink(tmpname)
217 os.unlink(tmpname)
218 if tobackup:
218 if tobackup:
219 os.rmdir(backupdir)
219 os.rmdir(backupdir)
220 except OSError:
220 except OSError:
221 pass
221 pass
222
222
223 return commit(ui, repo, recordfunc, pats, opts)
223 return commit(ui, repo, recordfunc, pats, opts)
224
224
225 def findpossible(cmd, table, strict=False):
225 def findpossible(cmd, table, strict=False):
226 """
226 """
227 Return cmd -> (aliases, command table entry)
227 Return cmd -> (aliases, command table entry)
228 for each matching command.
228 for each matching command.
229 Return debug commands (or their aliases) only if no normal command matches.
229 Return debug commands (or their aliases) only if no normal command matches.
230 """
230 """
231 choice = {}
231 choice = {}
232 debugchoice = {}
232 debugchoice = {}
233
233
234 if cmd in table:
234 if cmd in table:
235 # short-circuit exact matches, "log" alias beats "^log|history"
235 # short-circuit exact matches, "log" alias beats "^log|history"
236 keys = [cmd]
236 keys = [cmd]
237 else:
237 else:
238 keys = table.keys()
238 keys = table.keys()
239
239
240 allcmds = []
240 allcmds = []
241 for e in keys:
241 for e in keys:
242 aliases = parsealiases(e)
242 aliases = parsealiases(e)
243 allcmds.extend(aliases)
243 allcmds.extend(aliases)
244 found = None
244 found = None
245 if cmd in aliases:
245 if cmd in aliases:
246 found = cmd
246 found = cmd
247 elif not strict:
247 elif not strict:
248 for a in aliases:
248 for a in aliases:
249 if a.startswith(cmd):
249 if a.startswith(cmd):
250 found = a
250 found = a
251 break
251 break
252 if found is not None:
252 if found is not None:
253 if aliases[0].startswith("debug") or found.startswith("debug"):
253 if aliases[0].startswith("debug") or found.startswith("debug"):
254 debugchoice[found] = (aliases, table[e])
254 debugchoice[found] = (aliases, table[e])
255 else:
255 else:
256 choice[found] = (aliases, table[e])
256 choice[found] = (aliases, table[e])
257
257
258 if not choice and debugchoice:
258 if not choice and debugchoice:
259 choice = debugchoice
259 choice = debugchoice
260
260
261 return choice, allcmds
261 return choice, allcmds
262
262
263 def findcmd(cmd, table, strict=True):
263 def findcmd(cmd, table, strict=True):
264 """Return (aliases, command table entry) for command string."""
264 """Return (aliases, command table entry) for command string."""
265 choice, allcmds = findpossible(cmd, table, strict)
265 choice, allcmds = findpossible(cmd, table, strict)
266
266
267 if cmd in choice:
267 if cmd in choice:
268 return choice[cmd]
268 return choice[cmd]
269
269
270 if len(choice) > 1:
270 if len(choice) > 1:
271 clist = choice.keys()
271 clist = choice.keys()
272 clist.sort()
272 clist.sort()
273 raise error.AmbiguousCommand(cmd, clist)
273 raise error.AmbiguousCommand(cmd, clist)
274
274
275 if choice:
275 if choice:
276 return choice.values()[0]
276 return choice.values()[0]
277
277
278 raise error.UnknownCommand(cmd, allcmds)
278 raise error.UnknownCommand(cmd, allcmds)
279
279
280 def findrepo(p):
280 def findrepo(p):
281 while not os.path.isdir(os.path.join(p, ".hg")):
281 while not os.path.isdir(os.path.join(p, ".hg")):
282 oldp, p = p, os.path.dirname(p)
282 oldp, p = p, os.path.dirname(p)
283 if p == oldp:
283 if p == oldp:
284 return None
284 return None
285
285
286 return p
286 return p
287
287
288 def bailifchanged(repo, merge=True):
288 def bailifchanged(repo, merge=True):
289 if merge and repo.dirstate.p2() != nullid:
289 if merge and repo.dirstate.p2() != nullid:
290 raise util.Abort(_('outstanding uncommitted merge'))
290 raise util.Abort(_('outstanding uncommitted merge'))
291 modified, added, removed, deleted = repo.status()[:4]
291 modified, added, removed, deleted = repo.status()[:4]
292 if modified or added or removed or deleted:
292 if modified or added or removed or deleted:
293 raise util.Abort(_('uncommitted changes'))
293 raise util.Abort(_('uncommitted changes'))
294 ctx = repo[None]
294 ctx = repo[None]
295 for s in sorted(ctx.substate):
295 for s in sorted(ctx.substate):
296 ctx.sub(s).bailifchanged()
296 ctx.sub(s).bailifchanged()
297
297
298 def logmessage(ui, opts):
298 def logmessage(ui, opts):
299 """ get the log message according to -m and -l option """
299 """ get the log message according to -m and -l option """
300 message = opts.get('message')
300 message = opts.get('message')
301 logfile = opts.get('logfile')
301 logfile = opts.get('logfile')
302
302
303 if message and logfile:
303 if message and logfile:
304 raise util.Abort(_('options --message and --logfile are mutually '
304 raise util.Abort(_('options --message and --logfile are mutually '
305 'exclusive'))
305 'exclusive'))
306 if not message and logfile:
306 if not message and logfile:
307 try:
307 try:
308 if logfile == '-':
308 if logfile == '-':
309 message = ui.fin.read()
309 message = ui.fin.read()
310 else:
310 else:
311 message = '\n'.join(util.readfile(logfile).splitlines())
311 message = '\n'.join(util.readfile(logfile).splitlines())
312 except IOError as inst:
312 except IOError as inst:
313 raise util.Abort(_("can't read commit message '%s': %s") %
313 raise util.Abort(_("can't read commit message '%s': %s") %
314 (logfile, inst.strerror))
314 (logfile, inst.strerror))
315 return message
315 return message
316
316
317 def mergeeditform(ctxorbool, baseformname):
317 def mergeeditform(ctxorbool, baseformname):
318 """return appropriate editform name (referencing a committemplate)
318 """return appropriate editform name (referencing a committemplate)
319
319
320 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
320 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
321 merging is committed.
321 merging is committed.
322
322
323 This returns baseformname with '.merge' appended if it is a merge,
323 This returns baseformname with '.merge' appended if it is a merge,
324 otherwise '.normal' is appended.
324 otherwise '.normal' is appended.
325 """
325 """
326 if isinstance(ctxorbool, bool):
326 if isinstance(ctxorbool, bool):
327 if ctxorbool:
327 if ctxorbool:
328 return baseformname + ".merge"
328 return baseformname + ".merge"
329 elif 1 < len(ctxorbool.parents()):
329 elif 1 < len(ctxorbool.parents()):
330 return baseformname + ".merge"
330 return baseformname + ".merge"
331
331
332 return baseformname + ".normal"
332 return baseformname + ".normal"
333
333
334 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
334 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
335 editform='', **opts):
335 editform='', **opts):
336 """get appropriate commit message editor according to '--edit' option
336 """get appropriate commit message editor according to '--edit' option
337
337
338 'finishdesc' is a function to be called with edited commit message
338 'finishdesc' is a function to be called with edited commit message
339 (= 'description' of the new changeset) just after editing, but
339 (= 'description' of the new changeset) just after editing, but
340 before checking empty-ness. It should return actual text to be
340 before checking empty-ness. It should return actual text to be
341 stored into history. This allows to change description before
341 stored into history. This allows to change description before
342 storing.
342 storing.
343
343
344 'extramsg' is a extra message to be shown in the editor instead of
344 'extramsg' is a extra message to be shown in the editor instead of
345 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
345 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
346 is automatically added.
346 is automatically added.
347
347
348 'editform' is a dot-separated list of names, to distinguish
348 'editform' is a dot-separated list of names, to distinguish
349 the purpose of commit text editing.
349 the purpose of commit text editing.
350
350
351 'getcommiteditor' returns 'commitforceeditor' regardless of
351 'getcommiteditor' returns 'commitforceeditor' regardless of
352 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
352 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
353 they are specific for usage in MQ.
353 they are specific for usage in MQ.
354 """
354 """
355 if edit or finishdesc or extramsg:
355 if edit or finishdesc or extramsg:
356 return lambda r, c, s: commitforceeditor(r, c, s,
356 return lambda r, c, s: commitforceeditor(r, c, s,
357 finishdesc=finishdesc,
357 finishdesc=finishdesc,
358 extramsg=extramsg,
358 extramsg=extramsg,
359 editform=editform)
359 editform=editform)
360 elif editform:
360 elif editform:
361 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
361 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
362 else:
362 else:
363 return commiteditor
363 return commiteditor
364
364
365 def loglimit(opts):
365 def loglimit(opts):
366 """get the log limit according to option -l/--limit"""
366 """get the log limit according to option -l/--limit"""
367 limit = opts.get('limit')
367 limit = opts.get('limit')
368 if limit:
368 if limit:
369 try:
369 try:
370 limit = int(limit)
370 limit = int(limit)
371 except ValueError:
371 except ValueError:
372 raise util.Abort(_('limit must be a positive integer'))
372 raise util.Abort(_('limit must be a positive integer'))
373 if limit <= 0:
373 if limit <= 0:
374 raise util.Abort(_('limit must be positive'))
374 raise util.Abort(_('limit must be positive'))
375 else:
375 else:
376 limit = None
376 limit = None
377 return limit
377 return limit
378
378
379 def makefilename(repo, pat, node, desc=None,
379 def makefilename(repo, pat, node, desc=None,
380 total=None, seqno=None, revwidth=None, pathname=None):
380 total=None, seqno=None, revwidth=None, pathname=None):
381 node_expander = {
381 node_expander = {
382 'H': lambda: hex(node),
382 'H': lambda: hex(node),
383 'R': lambda: str(repo.changelog.rev(node)),
383 'R': lambda: str(repo.changelog.rev(node)),
384 'h': lambda: short(node),
384 'h': lambda: short(node),
385 'm': lambda: re.sub('[^\w]', '_', str(desc))
385 'm': lambda: re.sub('[^\w]', '_', str(desc))
386 }
386 }
387 expander = {
387 expander = {
388 '%': lambda: '%',
388 '%': lambda: '%',
389 'b': lambda: os.path.basename(repo.root),
389 'b': lambda: os.path.basename(repo.root),
390 }
390 }
391
391
392 try:
392 try:
393 if node:
393 if node:
394 expander.update(node_expander)
394 expander.update(node_expander)
395 if node:
395 if node:
396 expander['r'] = (lambda:
396 expander['r'] = (lambda:
397 str(repo.changelog.rev(node)).zfill(revwidth or 0))
397 str(repo.changelog.rev(node)).zfill(revwidth or 0))
398 if total is not None:
398 if total is not None:
399 expander['N'] = lambda: str(total)
399 expander['N'] = lambda: str(total)
400 if seqno is not None:
400 if seqno is not None:
401 expander['n'] = lambda: str(seqno)
401 expander['n'] = lambda: str(seqno)
402 if total is not None and seqno is not None:
402 if total is not None and seqno is not None:
403 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
403 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
404 if pathname is not None:
404 if pathname is not None:
405 expander['s'] = lambda: os.path.basename(pathname)
405 expander['s'] = lambda: os.path.basename(pathname)
406 expander['d'] = lambda: os.path.dirname(pathname) or '.'
406 expander['d'] = lambda: os.path.dirname(pathname) or '.'
407 expander['p'] = lambda: pathname
407 expander['p'] = lambda: pathname
408
408
409 newname = []
409 newname = []
410 patlen = len(pat)
410 patlen = len(pat)
411 i = 0
411 i = 0
412 while i < patlen:
412 while i < patlen:
413 c = pat[i]
413 c = pat[i]
414 if c == '%':
414 if c == '%':
415 i += 1
415 i += 1
416 c = pat[i]
416 c = pat[i]
417 c = expander[c]()
417 c = expander[c]()
418 newname.append(c)
418 newname.append(c)
419 i += 1
419 i += 1
420 return ''.join(newname)
420 return ''.join(newname)
421 except KeyError as inst:
421 except KeyError as inst:
422 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
422 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
423 inst.args[0])
423 inst.args[0])
424
424
425 def makefileobj(repo, pat, node=None, desc=None, total=None,
425 def makefileobj(repo, pat, node=None, desc=None, total=None,
426 seqno=None, revwidth=None, mode='wb', modemap=None,
426 seqno=None, revwidth=None, mode='wb', modemap=None,
427 pathname=None):
427 pathname=None):
428
428
429 writable = mode not in ('r', 'rb')
429 writable = mode not in ('r', 'rb')
430
430
431 if not pat or pat == '-':
431 if not pat or pat == '-':
432 if writable:
432 if writable:
433 fp = repo.ui.fout
433 fp = repo.ui.fout
434 else:
434 else:
435 fp = repo.ui.fin
435 fp = repo.ui.fin
436 if util.safehasattr(fp, 'fileno'):
436 if util.safehasattr(fp, 'fileno'):
437 return os.fdopen(os.dup(fp.fileno()), mode)
437 return os.fdopen(os.dup(fp.fileno()), mode)
438 else:
438 else:
439 # if this fp can't be duped properly, return
439 # if this fp can't be duped properly, return
440 # a dummy object that can be closed
440 # a dummy object that can be closed
441 class wrappedfileobj(object):
441 class wrappedfileobj(object):
442 noop = lambda x: None
442 noop = lambda x: None
443 def __init__(self, f):
443 def __init__(self, f):
444 self.f = f
444 self.f = f
445 def __getattr__(self, attr):
445 def __getattr__(self, attr):
446 if attr == 'close':
446 if attr == 'close':
447 return self.noop
447 return self.noop
448 else:
448 else:
449 return getattr(self.f, attr)
449 return getattr(self.f, attr)
450
450
451 return wrappedfileobj(fp)
451 return wrappedfileobj(fp)
452 if util.safehasattr(pat, 'write') and writable:
452 if util.safehasattr(pat, 'write') and writable:
453 return pat
453 return pat
454 if util.safehasattr(pat, 'read') and 'r' in mode:
454 if util.safehasattr(pat, 'read') and 'r' in mode:
455 return pat
455 return pat
456 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
456 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
457 if modemap is not None:
457 if modemap is not None:
458 mode = modemap.get(fn, mode)
458 mode = modemap.get(fn, mode)
459 if mode == 'wb':
459 if mode == 'wb':
460 modemap[fn] = 'ab'
460 modemap[fn] = 'ab'
461 return open(fn, mode)
461 return open(fn, mode)
462
462
463 def openrevlog(repo, cmd, file_, opts):
463 def openrevlog(repo, cmd, file_, opts):
464 """opens the changelog, manifest, a filelog or a given revlog"""
464 """opens the changelog, manifest, a filelog or a given revlog"""
465 cl = opts['changelog']
465 cl = opts['changelog']
466 mf = opts['manifest']
466 mf = opts['manifest']
467 dir = opts['dir']
467 dir = opts['dir']
468 msg = None
468 msg = None
469 if cl and mf:
469 if cl and mf:
470 msg = _('cannot specify --changelog and --manifest at the same time')
470 msg = _('cannot specify --changelog and --manifest at the same time')
471 elif cl and dir:
471 elif cl and dir:
472 msg = _('cannot specify --changelog and --dir at the same time')
472 msg = _('cannot specify --changelog and --dir at the same time')
473 elif cl or mf:
473 elif cl or mf:
474 if file_:
474 if file_:
475 msg = _('cannot specify filename with --changelog or --manifest')
475 msg = _('cannot specify filename with --changelog or --manifest')
476 elif not repo:
476 elif not repo:
477 msg = _('cannot specify --changelog or --manifest or --dir '
477 msg = _('cannot specify --changelog or --manifest or --dir '
478 'without a repository')
478 'without a repository')
479 if msg:
479 if msg:
480 raise util.Abort(msg)
480 raise util.Abort(msg)
481
481
482 r = None
482 r = None
483 if repo:
483 if repo:
484 if cl:
484 if cl:
485 r = repo.unfiltered().changelog
485 r = repo.unfiltered().changelog
486 elif dir:
486 elif dir:
487 if 'treemanifest' not in repo.requirements:
487 if 'treemanifest' not in repo.requirements:
488 raise util.Abort(_("--dir can only be used on repos with "
488 raise util.Abort(_("--dir can only be used on repos with "
489 "treemanifest enabled"))
489 "treemanifest enabled"))
490 dirlog = repo.dirlog(file_)
490 dirlog = repo.dirlog(file_)
491 if len(dirlog):
491 if len(dirlog):
492 r = dirlog
492 r = dirlog
493 elif mf:
493 elif mf:
494 r = repo.manifest
494 r = repo.manifest
495 elif file_:
495 elif file_:
496 filelog = repo.file(file_)
496 filelog = repo.file(file_)
497 if len(filelog):
497 if len(filelog):
498 r = filelog
498 r = filelog
499 if not r:
499 if not r:
500 if not file_:
500 if not file_:
501 raise error.CommandError(cmd, _('invalid arguments'))
501 raise error.CommandError(cmd, _('invalid arguments'))
502 if not os.path.isfile(file_):
502 if not os.path.isfile(file_):
503 raise util.Abort(_("revlog '%s' not found") % file_)
503 raise util.Abort(_("revlog '%s' not found") % file_)
504 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
504 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
505 file_[:-2] + ".i")
505 file_[:-2] + ".i")
506 return r
506 return r
507
507
508 def copy(ui, repo, pats, opts, rename=False):
508 def copy(ui, repo, pats, opts, rename=False):
509 # called with the repo lock held
509 # called with the repo lock held
510 #
510 #
511 # hgsep => pathname that uses "/" to separate directories
511 # hgsep => pathname that uses "/" to separate directories
512 # ossep => pathname that uses os.sep to separate directories
512 # ossep => pathname that uses os.sep to separate directories
513 cwd = repo.getcwd()
513 cwd = repo.getcwd()
514 targets = {}
514 targets = {}
515 after = opts.get("after")
515 after = opts.get("after")
516 dryrun = opts.get("dry_run")
516 dryrun = opts.get("dry_run")
517 wctx = repo[None]
517 wctx = repo[None]
518
518
519 def walkpat(pat):
519 def walkpat(pat):
520 srcs = []
520 srcs = []
521 if after:
521 if after:
522 badstates = '?'
522 badstates = '?'
523 else:
523 else:
524 badstates = '?r'
524 badstates = '?r'
525 m = scmutil.match(repo[None], [pat], opts, globbed=True)
525 m = scmutil.match(repo[None], [pat], opts, globbed=True)
526 for abs in repo.walk(m):
526 for abs in repo.walk(m):
527 state = repo.dirstate[abs]
527 state = repo.dirstate[abs]
528 rel = m.rel(abs)
528 rel = m.rel(abs)
529 exact = m.exact(abs)
529 exact = m.exact(abs)
530 if state in badstates:
530 if state in badstates:
531 if exact and state == '?':
531 if exact and state == '?':
532 ui.warn(_('%s: not copying - file is not managed\n') % rel)
532 ui.warn(_('%s: not copying - file is not managed\n') % rel)
533 if exact and state == 'r':
533 if exact and state == 'r':
534 ui.warn(_('%s: not copying - file has been marked for'
534 ui.warn(_('%s: not copying - file has been marked for'
535 ' remove\n') % rel)
535 ' remove\n') % rel)
536 continue
536 continue
537 # abs: hgsep
537 # abs: hgsep
538 # rel: ossep
538 # rel: ossep
539 srcs.append((abs, rel, exact))
539 srcs.append((abs, rel, exact))
540 return srcs
540 return srcs
541
541
542 # abssrc: hgsep
542 # abssrc: hgsep
543 # relsrc: ossep
543 # relsrc: ossep
544 # otarget: ossep
544 # otarget: ossep
545 def copyfile(abssrc, relsrc, otarget, exact):
545 def copyfile(abssrc, relsrc, otarget, exact):
546 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
546 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
547 if '/' in abstarget:
547 if '/' in abstarget:
548 # We cannot normalize abstarget itself, this would prevent
548 # We cannot normalize abstarget itself, this would prevent
549 # case only renames, like a => A.
549 # case only renames, like a => A.
550 abspath, absname = abstarget.rsplit('/', 1)
550 abspath, absname = abstarget.rsplit('/', 1)
551 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
551 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
552 reltarget = repo.pathto(abstarget, cwd)
552 reltarget = repo.pathto(abstarget, cwd)
553 target = repo.wjoin(abstarget)
553 target = repo.wjoin(abstarget)
554 src = repo.wjoin(abssrc)
554 src = repo.wjoin(abssrc)
555 state = repo.dirstate[abstarget]
555 state = repo.dirstate[abstarget]
556
556
557 scmutil.checkportable(ui, abstarget)
557 scmutil.checkportable(ui, abstarget)
558
558
559 # check for collisions
559 # check for collisions
560 prevsrc = targets.get(abstarget)
560 prevsrc = targets.get(abstarget)
561 if prevsrc is not None:
561 if prevsrc is not None:
562 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
562 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
563 (reltarget, repo.pathto(abssrc, cwd),
563 (reltarget, repo.pathto(abssrc, cwd),
564 repo.pathto(prevsrc, cwd)))
564 repo.pathto(prevsrc, cwd)))
565 return
565 return
566
566
567 # check for overwrites
567 # check for overwrites
568 exists = os.path.lexists(target)
568 exists = os.path.lexists(target)
569 samefile = False
569 samefile = False
570 if exists and abssrc != abstarget:
570 if exists and abssrc != abstarget:
571 if (repo.dirstate.normalize(abssrc) ==
571 if (repo.dirstate.normalize(abssrc) ==
572 repo.dirstate.normalize(abstarget)):
572 repo.dirstate.normalize(abstarget)):
573 if not rename:
573 if not rename:
574 ui.warn(_("%s: can't copy - same file\n") % reltarget)
574 ui.warn(_("%s: can't copy - same file\n") % reltarget)
575 return
575 return
576 exists = False
576 exists = False
577 samefile = True
577 samefile = True
578
578
579 if not after and exists or after and state in 'mn':
579 if not after and exists or after and state in 'mn':
580 if not opts['force']:
580 if not opts['force']:
581 ui.warn(_('%s: not overwriting - file exists\n') %
581 ui.warn(_('%s: not overwriting - file exists\n') %
582 reltarget)
582 reltarget)
583 return
583 return
584
584
585 if after:
585 if after:
586 if not exists:
586 if not exists:
587 if rename:
587 if rename:
588 ui.warn(_('%s: not recording move - %s does not exist\n') %
588 ui.warn(_('%s: not recording move - %s does not exist\n') %
589 (relsrc, reltarget))
589 (relsrc, reltarget))
590 else:
590 else:
591 ui.warn(_('%s: not recording copy - %s does not exist\n') %
591 ui.warn(_('%s: not recording copy - %s does not exist\n') %
592 (relsrc, reltarget))
592 (relsrc, reltarget))
593 return
593 return
594 elif not dryrun:
594 elif not dryrun:
595 try:
595 try:
596 if exists:
596 if exists:
597 os.unlink(target)
597 os.unlink(target)
598 targetdir = os.path.dirname(target) or '.'
598 targetdir = os.path.dirname(target) or '.'
599 if not os.path.isdir(targetdir):
599 if not os.path.isdir(targetdir):
600 os.makedirs(targetdir)
600 os.makedirs(targetdir)
601 if samefile:
601 if samefile:
602 tmp = target + "~hgrename"
602 tmp = target + "~hgrename"
603 os.rename(src, tmp)
603 os.rename(src, tmp)
604 os.rename(tmp, target)
604 os.rename(tmp, target)
605 else:
605 else:
606 util.copyfile(src, target)
606 util.copyfile(src, target)
607 srcexists = True
607 srcexists = True
608 except IOError as inst:
608 except IOError as inst:
609 if inst.errno == errno.ENOENT:
609 if inst.errno == errno.ENOENT:
610 ui.warn(_('%s: deleted in working directory\n') % relsrc)
610 ui.warn(_('%s: deleted in working directory\n') % relsrc)
611 srcexists = False
611 srcexists = False
612 else:
612 else:
613 ui.warn(_('%s: cannot copy - %s\n') %
613 ui.warn(_('%s: cannot copy - %s\n') %
614 (relsrc, inst.strerror))
614 (relsrc, inst.strerror))
615 return True # report a failure
615 return True # report a failure
616
616
617 if ui.verbose or not exact:
617 if ui.verbose or not exact:
618 if rename:
618 if rename:
619 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
619 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
620 else:
620 else:
621 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
621 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
622
622
623 targets[abstarget] = abssrc
623 targets[abstarget] = abssrc
624
624
625 # fix up dirstate
625 # fix up dirstate
626 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
626 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
627 dryrun=dryrun, cwd=cwd)
627 dryrun=dryrun, cwd=cwd)
628 if rename and not dryrun:
628 if rename and not dryrun:
629 if not after and srcexists and not samefile:
629 if not after and srcexists and not samefile:
630 util.unlinkpath(repo.wjoin(abssrc))
630 util.unlinkpath(repo.wjoin(abssrc))
631 wctx.forget([abssrc])
631 wctx.forget([abssrc])
632
632
633 # pat: ossep
633 # pat: ossep
634 # dest ossep
634 # dest ossep
635 # srcs: list of (hgsep, hgsep, ossep, bool)
635 # srcs: list of (hgsep, hgsep, ossep, bool)
636 # return: function that takes hgsep and returns ossep
636 # return: function that takes hgsep and returns ossep
637 def targetpathfn(pat, dest, srcs):
637 def targetpathfn(pat, dest, srcs):
638 if os.path.isdir(pat):
638 if os.path.isdir(pat):
639 abspfx = pathutil.canonpath(repo.root, cwd, pat)
639 abspfx = pathutil.canonpath(repo.root, cwd, pat)
640 abspfx = util.localpath(abspfx)
640 abspfx = util.localpath(abspfx)
641 if destdirexists:
641 if destdirexists:
642 striplen = len(os.path.split(abspfx)[0])
642 striplen = len(os.path.split(abspfx)[0])
643 else:
643 else:
644 striplen = len(abspfx)
644 striplen = len(abspfx)
645 if striplen:
645 if striplen:
646 striplen += len(os.sep)
646 striplen += len(os.sep)
647 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
647 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
648 elif destdirexists:
648 elif destdirexists:
649 res = lambda p: os.path.join(dest,
649 res = lambda p: os.path.join(dest,
650 os.path.basename(util.localpath(p)))
650 os.path.basename(util.localpath(p)))
651 else:
651 else:
652 res = lambda p: dest
652 res = lambda p: dest
653 return res
653 return res
654
654
655 # pat: ossep
655 # pat: ossep
656 # dest ossep
656 # dest ossep
657 # srcs: list of (hgsep, hgsep, ossep, bool)
657 # srcs: list of (hgsep, hgsep, ossep, bool)
658 # return: function that takes hgsep and returns ossep
658 # return: function that takes hgsep and returns ossep
659 def targetpathafterfn(pat, dest, srcs):
659 def targetpathafterfn(pat, dest, srcs):
660 if matchmod.patkind(pat):
660 if matchmod.patkind(pat):
661 # a mercurial pattern
661 # a mercurial pattern
662 res = lambda p: os.path.join(dest,
662 res = lambda p: os.path.join(dest,
663 os.path.basename(util.localpath(p)))
663 os.path.basename(util.localpath(p)))
664 else:
664 else:
665 abspfx = pathutil.canonpath(repo.root, cwd, pat)
665 abspfx = pathutil.canonpath(repo.root, cwd, pat)
666 if len(abspfx) < len(srcs[0][0]):
666 if len(abspfx) < len(srcs[0][0]):
667 # A directory. Either the target path contains the last
667 # A directory. Either the target path contains the last
668 # component of the source path or it does not.
668 # component of the source path or it does not.
669 def evalpath(striplen):
669 def evalpath(striplen):
670 score = 0
670 score = 0
671 for s in srcs:
671 for s in srcs:
672 t = os.path.join(dest, util.localpath(s[0])[striplen:])
672 t = os.path.join(dest, util.localpath(s[0])[striplen:])
673 if os.path.lexists(t):
673 if os.path.lexists(t):
674 score += 1
674 score += 1
675 return score
675 return score
676
676
677 abspfx = util.localpath(abspfx)
677 abspfx = util.localpath(abspfx)
678 striplen = len(abspfx)
678 striplen = len(abspfx)
679 if striplen:
679 if striplen:
680 striplen += len(os.sep)
680 striplen += len(os.sep)
681 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
681 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
682 score = evalpath(striplen)
682 score = evalpath(striplen)
683 striplen1 = len(os.path.split(abspfx)[0])
683 striplen1 = len(os.path.split(abspfx)[0])
684 if striplen1:
684 if striplen1:
685 striplen1 += len(os.sep)
685 striplen1 += len(os.sep)
686 if evalpath(striplen1) > score:
686 if evalpath(striplen1) > score:
687 striplen = striplen1
687 striplen = striplen1
688 res = lambda p: os.path.join(dest,
688 res = lambda p: os.path.join(dest,
689 util.localpath(p)[striplen:])
689 util.localpath(p)[striplen:])
690 else:
690 else:
691 # a file
691 # a file
692 if destdirexists:
692 if destdirexists:
693 res = lambda p: os.path.join(dest,
693 res = lambda p: os.path.join(dest,
694 os.path.basename(util.localpath(p)))
694 os.path.basename(util.localpath(p)))
695 else:
695 else:
696 res = lambda p: dest
696 res = lambda p: dest
697 return res
697 return res
698
698
699 pats = scmutil.expandpats(pats)
699 pats = scmutil.expandpats(pats)
700 if not pats:
700 if not pats:
701 raise util.Abort(_('no source or destination specified'))
701 raise util.Abort(_('no source or destination specified'))
702 if len(pats) == 1:
702 if len(pats) == 1:
703 raise util.Abort(_('no destination specified'))
703 raise util.Abort(_('no destination specified'))
704 dest = pats.pop()
704 dest = pats.pop()
705 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
705 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
706 if not destdirexists:
706 if not destdirexists:
707 if len(pats) > 1 or matchmod.patkind(pats[0]):
707 if len(pats) > 1 or matchmod.patkind(pats[0]):
708 raise util.Abort(_('with multiple sources, destination must be an '
708 raise util.Abort(_('with multiple sources, destination must be an '
709 'existing directory'))
709 'existing directory'))
710 if util.endswithsep(dest):
710 if util.endswithsep(dest):
711 raise util.Abort(_('destination %s is not a directory') % dest)
711 raise util.Abort(_('destination %s is not a directory') % dest)
712
712
713 tfn = targetpathfn
713 tfn = targetpathfn
714 if after:
714 if after:
715 tfn = targetpathafterfn
715 tfn = targetpathafterfn
716 copylist = []
716 copylist = []
717 for pat in pats:
717 for pat in pats:
718 srcs = walkpat(pat)
718 srcs = walkpat(pat)
719 if not srcs:
719 if not srcs:
720 continue
720 continue
721 copylist.append((tfn(pat, dest, srcs), srcs))
721 copylist.append((tfn(pat, dest, srcs), srcs))
722 if not copylist:
722 if not copylist:
723 raise util.Abort(_('no files to copy'))
723 raise util.Abort(_('no files to copy'))
724
724
725 errors = 0
725 errors = 0
726 for targetpath, srcs in copylist:
726 for targetpath, srcs in copylist:
727 for abssrc, relsrc, exact in srcs:
727 for abssrc, relsrc, exact in srcs:
728 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
728 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
729 errors += 1
729 errors += 1
730
730
731 if errors:
731 if errors:
732 ui.warn(_('(consider using --after)\n'))
732 ui.warn(_('(consider using --after)\n'))
733
733
734 return errors != 0
734 return errors != 0
735
735
736 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
736 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
737 runargs=None, appendpid=False):
737 runargs=None, appendpid=False):
738 '''Run a command as a service.'''
738 '''Run a command as a service.'''
739
739
740 def writepid(pid):
740 def writepid(pid):
741 if opts['pid_file']:
741 if opts['pid_file']:
742 if appendpid:
742 if appendpid:
743 mode = 'a'
743 mode = 'a'
744 else:
744 else:
745 mode = 'w'
745 mode = 'w'
746 fp = open(opts['pid_file'], mode)
746 fp = open(opts['pid_file'], mode)
747 fp.write(str(pid) + '\n')
747 fp.write(str(pid) + '\n')
748 fp.close()
748 fp.close()
749
749
750 if opts['daemon'] and not opts['daemon_pipefds']:
750 if opts['daemon'] and not opts['daemon_pipefds']:
751 # Signal child process startup with file removal
751 # Signal child process startup with file removal
752 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
752 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
753 os.close(lockfd)
753 os.close(lockfd)
754 try:
754 try:
755 if not runargs:
755 if not runargs:
756 runargs = util.hgcmd() + sys.argv[1:]
756 runargs = util.hgcmd() + sys.argv[1:]
757 runargs.append('--daemon-pipefds=%s' % lockpath)
757 runargs.append('--daemon-pipefds=%s' % lockpath)
758 # Don't pass --cwd to the child process, because we've already
758 # Don't pass --cwd to the child process, because we've already
759 # changed directory.
759 # changed directory.
760 for i in xrange(1, len(runargs)):
760 for i in xrange(1, len(runargs)):
761 if runargs[i].startswith('--cwd='):
761 if runargs[i].startswith('--cwd='):
762 del runargs[i]
762 del runargs[i]
763 break
763 break
764 elif runargs[i].startswith('--cwd'):
764 elif runargs[i].startswith('--cwd'):
765 del runargs[i:i + 2]
765 del runargs[i:i + 2]
766 break
766 break
767 def condfn():
767 def condfn():
768 return not os.path.exists(lockpath)
768 return not os.path.exists(lockpath)
769 pid = util.rundetached(runargs, condfn)
769 pid = util.rundetached(runargs, condfn)
770 if pid < 0:
770 if pid < 0:
771 raise util.Abort(_('child process failed to start'))
771 raise util.Abort(_('child process failed to start'))
772 writepid(pid)
772 writepid(pid)
773 finally:
773 finally:
774 try:
774 try:
775 os.unlink(lockpath)
775 os.unlink(lockpath)
776 except OSError as e:
776 except OSError as e:
777 if e.errno != errno.ENOENT:
777 if e.errno != errno.ENOENT:
778 raise
778 raise
779 if parentfn:
779 if parentfn:
780 return parentfn(pid)
780 return parentfn(pid)
781 else:
781 else:
782 return
782 return
783
783
784 if initfn:
784 if initfn:
785 initfn()
785 initfn()
786
786
787 if not opts['daemon']:
787 if not opts['daemon']:
788 writepid(os.getpid())
788 writepid(os.getpid())
789
789
790 if opts['daemon_pipefds']:
790 if opts['daemon_pipefds']:
791 lockpath = opts['daemon_pipefds']
791 lockpath = opts['daemon_pipefds']
792 try:
792 try:
793 os.setsid()
793 os.setsid()
794 except AttributeError:
794 except AttributeError:
795 pass
795 pass
796 os.unlink(lockpath)
796 os.unlink(lockpath)
797 util.hidewindow()
797 util.hidewindow()
798 sys.stdout.flush()
798 sys.stdout.flush()
799 sys.stderr.flush()
799 sys.stderr.flush()
800
800
801 nullfd = os.open(os.devnull, os.O_RDWR)
801 nullfd = os.open(os.devnull, os.O_RDWR)
802 logfilefd = nullfd
802 logfilefd = nullfd
803 if logfile:
803 if logfile:
804 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
804 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
805 os.dup2(nullfd, 0)
805 os.dup2(nullfd, 0)
806 os.dup2(logfilefd, 1)
806 os.dup2(logfilefd, 1)
807 os.dup2(logfilefd, 2)
807 os.dup2(logfilefd, 2)
808 if nullfd not in (0, 1, 2):
808 if nullfd not in (0, 1, 2):
809 os.close(nullfd)
809 os.close(nullfd)
810 if logfile and logfilefd not in (0, 1, 2):
810 if logfile and logfilefd not in (0, 1, 2):
811 os.close(logfilefd)
811 os.close(logfilefd)
812
812
813 if runfn:
813 if runfn:
814 return runfn()
814 return runfn()
815
815
816 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
816 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
817 """Utility function used by commands.import to import a single patch
817 """Utility function used by commands.import to import a single patch
818
818
819 This function is explicitly defined here to help the evolve extension to
819 This function is explicitly defined here to help the evolve extension to
820 wrap this part of the import logic.
820 wrap this part of the import logic.
821
821
822 The API is currently a bit ugly because it a simple code translation from
822 The API is currently a bit ugly because it a simple code translation from
823 the import command. Feel free to make it better.
823 the import command. Feel free to make it better.
824
824
825 :hunk: a patch (as a binary string)
825 :hunk: a patch (as a binary string)
826 :parents: nodes that will be parent of the created commit
826 :parents: nodes that will be parent of the created commit
827 :opts: the full dict of option passed to the import command
827 :opts: the full dict of option passed to the import command
828 :msgs: list to save commit message to.
828 :msgs: list to save commit message to.
829 (used in case we need to save it when failing)
829 (used in case we need to save it when failing)
830 :updatefunc: a function that update a repo to a given node
830 :updatefunc: a function that update a repo to a given node
831 updatefunc(<repo>, <node>)
831 updatefunc(<repo>, <node>)
832 """
832 """
833 tmpname, message, user, date, branch, nodeid, p1, p2 = \
833 tmpname, message, user, date, branch, nodeid, p1, p2 = \
834 patch.extract(ui, hunk)
834 patch.extract(ui, hunk)
835
835
836 update = not opts.get('bypass')
836 update = not opts.get('bypass')
837 strip = opts["strip"]
837 strip = opts["strip"]
838 prefix = opts["prefix"]
838 prefix = opts["prefix"]
839 sim = float(opts.get('similarity') or 0)
839 sim = float(opts.get('similarity') or 0)
840 if not tmpname:
840 if not tmpname:
841 return (None, None, False)
841 return (None, None, False)
842 msg = _('applied to working directory')
842 msg = _('applied to working directory')
843
843
844 rejects = False
844 rejects = False
845 dsguard = None
845 dsguard = None
846
846
847 try:
847 try:
848 cmdline_message = logmessage(ui, opts)
848 cmdline_message = logmessage(ui, opts)
849 if cmdline_message:
849 if cmdline_message:
850 # pickup the cmdline msg
850 # pickup the cmdline msg
851 message = cmdline_message
851 message = cmdline_message
852 elif message:
852 elif message:
853 # pickup the patch msg
853 # pickup the patch msg
854 message = message.strip()
854 message = message.strip()
855 else:
855 else:
856 # launch the editor
856 # launch the editor
857 message = None
857 message = None
858 ui.debug('message:\n%s\n' % message)
858 ui.debug('message:\n%s\n' % message)
859
859
860 if len(parents) == 1:
860 if len(parents) == 1:
861 parents.append(repo[nullid])
861 parents.append(repo[nullid])
862 if opts.get('exact'):
862 if opts.get('exact'):
863 if not nodeid or not p1:
863 if not nodeid or not p1:
864 raise util.Abort(_('not a Mercurial patch'))
864 raise util.Abort(_('not a Mercurial patch'))
865 p1 = repo[p1]
865 p1 = repo[p1]
866 p2 = repo[p2 or nullid]
866 p2 = repo[p2 or nullid]
867 elif p2:
867 elif p2:
868 try:
868 try:
869 p1 = repo[p1]
869 p1 = repo[p1]
870 p2 = repo[p2]
870 p2 = repo[p2]
871 # Without any options, consider p2 only if the
871 # Without any options, consider p2 only if the
872 # patch is being applied on top of the recorded
872 # patch is being applied on top of the recorded
873 # first parent.
873 # first parent.
874 if p1 != parents[0]:
874 if p1 != parents[0]:
875 p1 = parents[0]
875 p1 = parents[0]
876 p2 = repo[nullid]
876 p2 = repo[nullid]
877 except error.RepoError:
877 except error.RepoError:
878 p1, p2 = parents
878 p1, p2 = parents
879 if p2.node() == nullid:
879 if p2.node() == nullid:
880 ui.warn(_("warning: import the patch as a normal revision\n"
880 ui.warn(_("warning: import the patch as a normal revision\n"
881 "(use --exact to import the patch as a merge)\n"))
881 "(use --exact to import the patch as a merge)\n"))
882 else:
882 else:
883 p1, p2 = parents
883 p1, p2 = parents
884
884
885 n = None
885 n = None
886 if update:
886 if update:
887 dsguard = dirstateguard(repo, 'tryimportone')
887 dsguard = dirstateguard(repo, 'tryimportone')
888 if p1 != parents[0]:
888 if p1 != parents[0]:
889 updatefunc(repo, p1.node())
889 updatefunc(repo, p1.node())
890 if p2 != parents[1]:
890 if p2 != parents[1]:
891 repo.setparents(p1.node(), p2.node())
891 repo.setparents(p1.node(), p2.node())
892
892
893 if opts.get('exact') or opts.get('import_branch'):
893 if opts.get('exact') or opts.get('import_branch'):
894 repo.dirstate.setbranch(branch or 'default')
894 repo.dirstate.setbranch(branch or 'default')
895
895
896 partial = opts.get('partial', False)
896 partial = opts.get('partial', False)
897 files = set()
897 files = set()
898 try:
898 try:
899 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
899 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
900 files=files, eolmode=None, similarity=sim / 100.0)
900 files=files, eolmode=None, similarity=sim / 100.0)
901 except patch.PatchError as e:
901 except patch.PatchError as e:
902 if not partial:
902 if not partial:
903 raise util.Abort(str(e))
903 raise util.Abort(str(e))
904 if partial:
904 if partial:
905 rejects = True
905 rejects = True
906
906
907 files = list(files)
907 files = list(files)
908 if opts.get('no_commit'):
908 if opts.get('no_commit'):
909 if message:
909 if message:
910 msgs.append(message)
910 msgs.append(message)
911 else:
911 else:
912 if opts.get('exact') or p2:
912 if opts.get('exact') or p2:
913 # If you got here, you either use --force and know what
913 # If you got here, you either use --force and know what
914 # you are doing or used --exact or a merge patch while
914 # you are doing or used --exact or a merge patch while
915 # being updated to its first parent.
915 # being updated to its first parent.
916 m = None
916 m = None
917 else:
917 else:
918 m = scmutil.matchfiles(repo, files or [])
918 m = scmutil.matchfiles(repo, files or [])
919 editform = mergeeditform(repo[None], 'import.normal')
919 editform = mergeeditform(repo[None], 'import.normal')
920 if opts.get('exact'):
920 if opts.get('exact'):
921 editor = None
921 editor = None
922 else:
922 else:
923 editor = getcommiteditor(editform=editform, **opts)
923 editor = getcommiteditor(editform=editform, **opts)
924 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
924 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
925 try:
925 try:
926 if partial:
926 if partial:
927 repo.ui.setconfig('ui', 'allowemptycommit', True)
927 repo.ui.setconfig('ui', 'allowemptycommit', True)
928 n = repo.commit(message, opts.get('user') or user,
928 n = repo.commit(message, opts.get('user') or user,
929 opts.get('date') or date, match=m,
929 opts.get('date') or date, match=m,
930 editor=editor)
930 editor=editor)
931 finally:
931 finally:
932 repo.ui.restoreconfig(allowemptyback)
932 repo.ui.restoreconfig(allowemptyback)
933 dsguard.close()
933 dsguard.close()
934 else:
934 else:
935 if opts.get('exact') or opts.get('import_branch'):
935 if opts.get('exact') or opts.get('import_branch'):
936 branch = branch or 'default'
936 branch = branch or 'default'
937 else:
937 else:
938 branch = p1.branch()
938 branch = p1.branch()
939 store = patch.filestore()
939 store = patch.filestore()
940 try:
940 try:
941 files = set()
941 files = set()
942 try:
942 try:
943 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
943 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
944 files, eolmode=None)
944 files, eolmode=None)
945 except patch.PatchError as e:
945 except patch.PatchError as e:
946 raise util.Abort(str(e))
946 raise util.Abort(str(e))
947 if opts.get('exact'):
947 if opts.get('exact'):
948 editor = None
948 editor = None
949 else:
949 else:
950 editor = getcommiteditor(editform='import.bypass')
950 editor = getcommiteditor(editform='import.bypass')
951 memctx = context.makememctx(repo, (p1.node(), p2.node()),
951 memctx = context.makememctx(repo, (p1.node(), p2.node()),
952 message,
952 message,
953 opts.get('user') or user,
953 opts.get('user') or user,
954 opts.get('date') or date,
954 opts.get('date') or date,
955 branch, files, store,
955 branch, files, store,
956 editor=editor)
956 editor=editor)
957 n = memctx.commit()
957 n = memctx.commit()
958 finally:
958 finally:
959 store.close()
959 store.close()
960 if opts.get('exact') and opts.get('no_commit'):
960 if opts.get('exact') and opts.get('no_commit'):
961 # --exact with --no-commit is still useful in that it does merge
961 # --exact with --no-commit is still useful in that it does merge
962 # and branch bits
962 # and branch bits
963 ui.warn(_("warning: can't check exact import with --no-commit\n"))
963 ui.warn(_("warning: can't check exact import with --no-commit\n"))
964 elif opts.get('exact') and hex(n) != nodeid:
964 elif opts.get('exact') and hex(n) != nodeid:
965 raise util.Abort(_('patch is damaged or loses information'))
965 raise util.Abort(_('patch is damaged or loses information'))
966 if n:
966 if n:
967 # i18n: refers to a short changeset id
967 # i18n: refers to a short changeset id
968 msg = _('created %s') % short(n)
968 msg = _('created %s') % short(n)
969 return (msg, n, rejects)
969 return (msg, n, rejects)
970 finally:
970 finally:
971 lockmod.release(dsguard)
971 lockmod.release(dsguard)
972 os.unlink(tmpname)
972 os.unlink(tmpname)
973
973
974 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
974 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
975 opts=None):
975 opts=None):
976 '''export changesets as hg patches.'''
976 '''export changesets as hg patches.'''
977
977
978 total = len(revs)
978 total = len(revs)
979 revwidth = max([len(str(rev)) for rev in revs])
979 revwidth = max([len(str(rev)) for rev in revs])
980 filemode = {}
980 filemode = {}
981
981
982 def single(rev, seqno, fp):
982 def single(rev, seqno, fp):
983 ctx = repo[rev]
983 ctx = repo[rev]
984 node = ctx.node()
984 node = ctx.node()
985 parents = [p.node() for p in ctx.parents() if p]
985 parents = [p.node() for p in ctx.parents() if p]
986 branch = ctx.branch()
986 branch = ctx.branch()
987 if switch_parent:
987 if switch_parent:
988 parents.reverse()
988 parents.reverse()
989
989
990 if parents:
990 if parents:
991 prev = parents[0]
991 prev = parents[0]
992 else:
992 else:
993 prev = nullid
993 prev = nullid
994
994
995 shouldclose = False
995 shouldclose = False
996 if not fp and len(template) > 0:
996 if not fp and len(template) > 0:
997 desc_lines = ctx.description().rstrip().split('\n')
997 desc_lines = ctx.description().rstrip().split('\n')
998 desc = desc_lines[0] #Commit always has a first line.
998 desc = desc_lines[0] #Commit always has a first line.
999 fp = makefileobj(repo, template, node, desc=desc, total=total,
999 fp = makefileobj(repo, template, node, desc=desc, total=total,
1000 seqno=seqno, revwidth=revwidth, mode='wb',
1000 seqno=seqno, revwidth=revwidth, mode='wb',
1001 modemap=filemode)
1001 modemap=filemode)
1002 if fp != template:
1002 if fp != template:
1003 shouldclose = True
1003 shouldclose = True
1004 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1004 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1005 repo.ui.note("%s\n" % fp.name)
1005 repo.ui.note("%s\n" % fp.name)
1006
1006
1007 if not fp:
1007 if not fp:
1008 write = repo.ui.write
1008 write = repo.ui.write
1009 else:
1009 else:
1010 def write(s, **kw):
1010 def write(s, **kw):
1011 fp.write(s)
1011 fp.write(s)
1012
1012
1013 write("# HG changeset patch\n")
1013 write("# HG changeset patch\n")
1014 write("# User %s\n" % ctx.user())
1014 write("# User %s\n" % ctx.user())
1015 write("# Date %d %d\n" % ctx.date())
1015 write("# Date %d %d\n" % ctx.date())
1016 write("# %s\n" % util.datestr(ctx.date()))
1016 write("# %s\n" % util.datestr(ctx.date()))
1017 if branch and branch != 'default':
1017 if branch and branch != 'default':
1018 write("# Branch %s\n" % branch)
1018 write("# Branch %s\n" % branch)
1019 write("# Node ID %s\n" % hex(node))
1019 write("# Node ID %s\n" % hex(node))
1020 write("# Parent %s\n" % hex(prev))
1020 write("# Parent %s\n" % hex(prev))
1021 if len(parents) > 1:
1021 if len(parents) > 1:
1022 write("# Parent %s\n" % hex(parents[1]))
1022 write("# Parent %s\n" % hex(parents[1]))
1023 write(ctx.description().rstrip())
1023 write(ctx.description().rstrip())
1024 write("\n\n")
1024 write("\n\n")
1025
1025
1026 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
1026 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
1027 write(chunk, label=label)
1027 write(chunk, label=label)
1028
1028
1029 if shouldclose:
1029 if shouldclose:
1030 fp.close()
1030 fp.close()
1031
1031
1032 for seqno, rev in enumerate(revs):
1032 for seqno, rev in enumerate(revs):
1033 single(rev, seqno + 1, fp)
1033 single(rev, seqno + 1, fp)
1034
1034
1035 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1035 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1036 changes=None, stat=False, fp=None, prefix='',
1036 changes=None, stat=False, fp=None, prefix='',
1037 root='', listsubrepos=False):
1037 root='', listsubrepos=False):
1038 '''show diff or diffstat.'''
1038 '''show diff or diffstat.'''
1039 if fp is None:
1039 if fp is None:
1040 write = ui.write
1040 write = ui.write
1041 else:
1041 else:
1042 def write(s, **kw):
1042 def write(s, **kw):
1043 fp.write(s)
1043 fp.write(s)
1044
1044
1045 if root:
1045 if root:
1046 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1046 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1047 else:
1047 else:
1048 relroot = ''
1048 relroot = ''
1049 if relroot != '':
1049 if relroot != '':
1050 # XXX relative roots currently don't work if the root is within a
1050 # XXX relative roots currently don't work if the root is within a
1051 # subrepo
1051 # subrepo
1052 uirelroot = match.uipath(relroot)
1052 uirelroot = match.uipath(relroot)
1053 relroot += '/'
1053 relroot += '/'
1054 for matchroot in match.files():
1054 for matchroot in match.files():
1055 if not matchroot.startswith(relroot):
1055 if not matchroot.startswith(relroot):
1056 ui.warn(_('warning: %s not inside relative root %s\n') % (
1056 ui.warn(_('warning: %s not inside relative root %s\n') % (
1057 match.uipath(matchroot), uirelroot))
1057 match.uipath(matchroot), uirelroot))
1058
1058
1059 if stat:
1059 if stat:
1060 diffopts = diffopts.copy(context=0)
1060 diffopts = diffopts.copy(context=0)
1061 width = 80
1061 width = 80
1062 if not ui.plain():
1062 if not ui.plain():
1063 width = ui.termwidth()
1063 width = ui.termwidth()
1064 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1064 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1065 prefix=prefix, relroot=relroot)
1065 prefix=prefix, relroot=relroot)
1066 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1066 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1067 width=width,
1067 width=width,
1068 git=diffopts.git):
1068 git=diffopts.git):
1069 write(chunk, label=label)
1069 write(chunk, label=label)
1070 else:
1070 else:
1071 for chunk, label in patch.diffui(repo, node1, node2, match,
1071 for chunk, label in patch.diffui(repo, node1, node2, match,
1072 changes, diffopts, prefix=prefix,
1072 changes, diffopts, prefix=prefix,
1073 relroot=relroot):
1073 relroot=relroot):
1074 write(chunk, label=label)
1074 write(chunk, label=label)
1075
1075
1076 if listsubrepos:
1076 if listsubrepos:
1077 ctx1 = repo[node1]
1077 ctx1 = repo[node1]
1078 ctx2 = repo[node2]
1078 ctx2 = repo[node2]
1079 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1079 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1080 tempnode2 = node2
1080 tempnode2 = node2
1081 try:
1081 try:
1082 if node2 is not None:
1082 if node2 is not None:
1083 tempnode2 = ctx2.substate[subpath][1]
1083 tempnode2 = ctx2.substate[subpath][1]
1084 except KeyError:
1084 except KeyError:
1085 # A subrepo that existed in node1 was deleted between node1 and
1085 # A subrepo that existed in node1 was deleted between node1 and
1086 # node2 (inclusive). Thus, ctx2's substate won't contain that
1086 # node2 (inclusive). Thus, ctx2's substate won't contain that
1087 # subpath. The best we can do is to ignore it.
1087 # subpath. The best we can do is to ignore it.
1088 tempnode2 = None
1088 tempnode2 = None
1089 submatch = matchmod.narrowmatcher(subpath, match)
1089 submatch = matchmod.narrowmatcher(subpath, match)
1090 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1090 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1091 stat=stat, fp=fp, prefix=prefix)
1091 stat=stat, fp=fp, prefix=prefix)
1092
1092
1093 class changeset_printer(object):
1093 class changeset_printer(object):
1094 '''show changeset information when templating not requested.'''
1094 '''show changeset information when templating not requested.'''
1095
1095
1096 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1096 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1097 self.ui = ui
1097 self.ui = ui
1098 self.repo = repo
1098 self.repo = repo
1099 self.buffered = buffered
1099 self.buffered = buffered
1100 self.matchfn = matchfn
1100 self.matchfn = matchfn
1101 self.diffopts = diffopts
1101 self.diffopts = diffopts
1102 self.header = {}
1102 self.header = {}
1103 self.hunk = {}
1103 self.hunk = {}
1104 self.lastheader = None
1104 self.lastheader = None
1105 self.footer = None
1105 self.footer = None
1106
1106
1107 def flush(self, rev):
1107 def flush(self, rev):
1108 if rev in self.header:
1108 if rev in self.header:
1109 h = self.header[rev]
1109 h = self.header[rev]
1110 if h != self.lastheader:
1110 if h != self.lastheader:
1111 self.lastheader = h
1111 self.lastheader = h
1112 self.ui.write(h)
1112 self.ui.write(h)
1113 del self.header[rev]
1113 del self.header[rev]
1114 if rev in self.hunk:
1114 if rev in self.hunk:
1115 self.ui.write(self.hunk[rev])
1115 self.ui.write(self.hunk[rev])
1116 del self.hunk[rev]
1116 del self.hunk[rev]
1117 return 1
1117 return 1
1118 return 0
1118 return 0
1119
1119
1120 def close(self):
1120 def close(self):
1121 if self.footer:
1121 if self.footer:
1122 self.ui.write(self.footer)
1122 self.ui.write(self.footer)
1123
1123
1124 def show(self, ctx, copies=None, matchfn=None, **props):
1124 def show(self, ctx, copies=None, matchfn=None, **props):
1125 if self.buffered:
1125 if self.buffered:
1126 self.ui.pushbuffer()
1126 self.ui.pushbuffer()
1127 self._show(ctx, copies, matchfn, props)
1127 self._show(ctx, copies, matchfn, props)
1128 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1128 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1129 else:
1129 else:
1130 self._show(ctx, copies, matchfn, props)
1130 self._show(ctx, copies, matchfn, props)
1131
1131
1132 def _show(self, ctx, copies, matchfn, props):
1132 def _show(self, ctx, copies, matchfn, props):
1133 '''show a single changeset or file revision'''
1133 '''show a single changeset or file revision'''
1134 changenode = ctx.node()
1134 changenode = ctx.node()
1135 rev = ctx.rev()
1135 rev = ctx.rev()
1136 if self.ui.debugflag:
1136 if self.ui.debugflag:
1137 hexfunc = hex
1137 hexfunc = hex
1138 else:
1138 else:
1139 hexfunc = short
1139 hexfunc = short
1140 if rev is None:
1140 if rev is None:
1141 pctx = ctx.p1()
1141 pctx = ctx.p1()
1142 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1142 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1143 else:
1143 else:
1144 revnode = (rev, hexfunc(changenode))
1144 revnode = (rev, hexfunc(changenode))
1145
1145
1146 if self.ui.quiet:
1146 if self.ui.quiet:
1147 self.ui.write("%d:%s\n" % revnode, label='log.node')
1147 self.ui.write("%d:%s\n" % revnode, label='log.node')
1148 return
1148 return
1149
1149
1150 date = util.datestr(ctx.date())
1150 date = util.datestr(ctx.date())
1151
1151
1152 # i18n: column positioning for "hg log"
1152 # i18n: column positioning for "hg log"
1153 self.ui.write(_("changeset: %d:%s\n") % revnode,
1153 self.ui.write(_("changeset: %d:%s\n") % revnode,
1154 label='log.changeset changeset.%s' % ctx.phasestr())
1154 label='log.changeset changeset.%s' % ctx.phasestr())
1155
1155
1156 # branches are shown first before any other names due to backwards
1156 # branches are shown first before any other names due to backwards
1157 # compatibility
1157 # compatibility
1158 branch = ctx.branch()
1158 branch = ctx.branch()
1159 # don't show the default branch name
1159 # don't show the default branch name
1160 if branch != 'default':
1160 if branch != 'default':
1161 # i18n: column positioning for "hg log"
1161 # i18n: column positioning for "hg log"
1162 self.ui.write(_("branch: %s\n") % branch,
1162 self.ui.write(_("branch: %s\n") % branch,
1163 label='log.branch')
1163 label='log.branch')
1164
1164
1165 for name, ns in self.repo.names.iteritems():
1165 for name, ns in self.repo.names.iteritems():
1166 # branches has special logic already handled above, so here we just
1166 # branches has special logic already handled above, so here we just
1167 # skip it
1167 # skip it
1168 if name == 'branches':
1168 if name == 'branches':
1169 continue
1169 continue
1170 # we will use the templatename as the color name since those two
1170 # we will use the templatename as the color name since those two
1171 # should be the same
1171 # should be the same
1172 for name in ns.names(self.repo, changenode):
1172 for name in ns.names(self.repo, changenode):
1173 self.ui.write(ns.logfmt % name,
1173 self.ui.write(ns.logfmt % name,
1174 label='log.%s' % ns.colorname)
1174 label='log.%s' % ns.colorname)
1175 if self.ui.debugflag:
1175 if self.ui.debugflag:
1176 # i18n: column positioning for "hg log"
1176 # i18n: column positioning for "hg log"
1177 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1177 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1178 label='log.phase')
1178 label='log.phase')
1179 for pctx in self._meaningful_parentrevs(ctx):
1179 for pctx in self._meaningful_parentrevs(ctx):
1180 label = 'log.parent changeset.%s' % pctx.phasestr()
1180 label = 'log.parent changeset.%s' % pctx.phasestr()
1181 # i18n: column positioning for "hg log"
1181 # i18n: column positioning for "hg log"
1182 self.ui.write(_("parent: %d:%s\n")
1182 self.ui.write(_("parent: %d:%s\n")
1183 % (pctx.rev(), hexfunc(pctx.node())),
1183 % (pctx.rev(), hexfunc(pctx.node())),
1184 label=label)
1184 label=label)
1185
1185
1186 if self.ui.debugflag and rev is not None:
1186 if self.ui.debugflag and rev is not None:
1187 mnode = ctx.manifestnode()
1187 mnode = ctx.manifestnode()
1188 # i18n: column positioning for "hg log"
1188 # i18n: column positioning for "hg log"
1189 self.ui.write(_("manifest: %d:%s\n") %
1189 self.ui.write(_("manifest: %d:%s\n") %
1190 (self.repo.manifest.rev(mnode), hex(mnode)),
1190 (self.repo.manifest.rev(mnode), hex(mnode)),
1191 label='ui.debug log.manifest')
1191 label='ui.debug log.manifest')
1192 # i18n: column positioning for "hg log"
1192 # i18n: column positioning for "hg log"
1193 self.ui.write(_("user: %s\n") % ctx.user(),
1193 self.ui.write(_("user: %s\n") % ctx.user(),
1194 label='log.user')
1194 label='log.user')
1195 # i18n: column positioning for "hg log"
1195 # i18n: column positioning for "hg log"
1196 self.ui.write(_("date: %s\n") % date,
1196 self.ui.write(_("date: %s\n") % date,
1197 label='log.date')
1197 label='log.date')
1198
1198
1199 if self.ui.debugflag:
1199 if self.ui.debugflag:
1200 files = ctx.p1().status(ctx)[:3]
1200 files = ctx.p1().status(ctx)[:3]
1201 for key, value in zip([# i18n: column positioning for "hg log"
1201 for key, value in zip([# i18n: column positioning for "hg log"
1202 _("files:"),
1202 _("files:"),
1203 # i18n: column positioning for "hg log"
1203 # i18n: column positioning for "hg log"
1204 _("files+:"),
1204 _("files+:"),
1205 # i18n: column positioning for "hg log"
1205 # i18n: column positioning for "hg log"
1206 _("files-:")], files):
1206 _("files-:")], files):
1207 if value:
1207 if value:
1208 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1208 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1209 label='ui.debug log.files')
1209 label='ui.debug log.files')
1210 elif ctx.files() and self.ui.verbose:
1210 elif ctx.files() and self.ui.verbose:
1211 # i18n: column positioning for "hg log"
1211 # i18n: column positioning for "hg log"
1212 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1212 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1213 label='ui.note log.files')
1213 label='ui.note log.files')
1214 if copies and self.ui.verbose:
1214 if copies and self.ui.verbose:
1215 copies = ['%s (%s)' % c for c in copies]
1215 copies = ['%s (%s)' % c for c in copies]
1216 # i18n: column positioning for "hg log"
1216 # i18n: column positioning for "hg log"
1217 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1217 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1218 label='ui.note log.copies')
1218 label='ui.note log.copies')
1219
1219
1220 extra = ctx.extra()
1220 extra = ctx.extra()
1221 if extra and self.ui.debugflag:
1221 if extra and self.ui.debugflag:
1222 for key, value in sorted(extra.items()):
1222 for key, value in sorted(extra.items()):
1223 # i18n: column positioning for "hg log"
1223 # i18n: column positioning for "hg log"
1224 self.ui.write(_("extra: %s=%s\n")
1224 self.ui.write(_("extra: %s=%s\n")
1225 % (key, value.encode('string_escape')),
1225 % (key, value.encode('string_escape')),
1226 label='ui.debug log.extra')
1226 label='ui.debug log.extra')
1227
1227
1228 description = ctx.description().strip()
1228 description = ctx.description().strip()
1229 if description:
1229 if description:
1230 if self.ui.verbose:
1230 if self.ui.verbose:
1231 self.ui.write(_("description:\n"),
1231 self.ui.write(_("description:\n"),
1232 label='ui.note log.description')
1232 label='ui.note log.description')
1233 self.ui.write(description,
1233 self.ui.write(description,
1234 label='ui.note log.description')
1234 label='ui.note log.description')
1235 self.ui.write("\n\n")
1235 self.ui.write("\n\n")
1236 else:
1236 else:
1237 # i18n: column positioning for "hg log"
1237 # i18n: column positioning for "hg log"
1238 self.ui.write(_("summary: %s\n") %
1238 self.ui.write(_("summary: %s\n") %
1239 description.splitlines()[0],
1239 description.splitlines()[0],
1240 label='log.summary')
1240 label='log.summary')
1241 self.ui.write("\n")
1241 self.ui.write("\n")
1242
1242
1243 self.showpatch(changenode, matchfn)
1243 self.showpatch(changenode, matchfn)
1244
1244
1245 def showpatch(self, node, matchfn):
1245 def showpatch(self, node, matchfn):
1246 if not matchfn:
1246 if not matchfn:
1247 matchfn = self.matchfn
1247 matchfn = self.matchfn
1248 if matchfn:
1248 if matchfn:
1249 stat = self.diffopts.get('stat')
1249 stat = self.diffopts.get('stat')
1250 diff = self.diffopts.get('patch')
1250 diff = self.diffopts.get('patch')
1251 diffopts = patch.diffallopts(self.ui, self.diffopts)
1251 diffopts = patch.diffallopts(self.ui, self.diffopts)
1252 prev = self.repo.changelog.parents(node)[0]
1252 prev = self.repo.changelog.parents(node)[0]
1253 if stat:
1253 if stat:
1254 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1254 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1255 match=matchfn, stat=True)
1255 match=matchfn, stat=True)
1256 if diff:
1256 if diff:
1257 if stat:
1257 if stat:
1258 self.ui.write("\n")
1258 self.ui.write("\n")
1259 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1259 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1260 match=matchfn, stat=False)
1260 match=matchfn, stat=False)
1261 self.ui.write("\n")
1261 self.ui.write("\n")
1262
1262
1263 def _meaningful_parentrevs(self, ctx):
1263 def _meaningful_parentrevs(self, ctx):
1264 """Return list of meaningful (or all if debug) parentrevs for rev.
1264 """Return list of meaningful (or all if debug) parentrevs for rev.
1265
1265
1266 For merges (two non-nullrev revisions) both parents are meaningful.
1266 For merges (two non-nullrev revisions) both parents are meaningful.
1267 Otherwise the first parent revision is considered meaningful if it
1267 Otherwise the first parent revision is considered meaningful if it
1268 is not the preceding revision.
1268 is not the preceding revision.
1269 """
1269 """
1270 parents = ctx.parents()
1270 parents = ctx.parents()
1271 if len(parents) > 1:
1271 if len(parents) > 1:
1272 return parents
1272 return parents
1273 if self.ui.debugflag:
1273 if self.ui.debugflag:
1274 return [parents[0], self.repo['null']]
1274 return [parents[0], self.repo['null']]
1275 if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1:
1275 if parents[0].rev() >= scmutil.intrev(ctx.rev()) - 1:
1276 return []
1276 return []
1277 return parents
1277 return parents
1278
1278
1279 class jsonchangeset(changeset_printer):
1279 class jsonchangeset(changeset_printer):
1280 '''format changeset information.'''
1280 '''format changeset information.'''
1281
1281
1282 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1282 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1283 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1283 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1284 self.cache = {}
1284 self.cache = {}
1285 self._first = True
1285 self._first = True
1286
1286
1287 def close(self):
1287 def close(self):
1288 if not self._first:
1288 if not self._first:
1289 self.ui.write("\n]\n")
1289 self.ui.write("\n]\n")
1290 else:
1290 else:
1291 self.ui.write("[]\n")
1291 self.ui.write("[]\n")
1292
1292
1293 def _show(self, ctx, copies, matchfn, props):
1293 def _show(self, ctx, copies, matchfn, props):
1294 '''show a single changeset or file revision'''
1294 '''show a single changeset or file revision'''
1295 rev = ctx.rev()
1295 rev = ctx.rev()
1296 if rev is None:
1296 if rev is None:
1297 jrev = jnode = 'null'
1297 jrev = jnode = 'null'
1298 else:
1298 else:
1299 jrev = str(rev)
1299 jrev = str(rev)
1300 jnode = '"%s"' % hex(ctx.node())
1300 jnode = '"%s"' % hex(ctx.node())
1301 j = encoding.jsonescape
1301 j = encoding.jsonescape
1302
1302
1303 if self._first:
1303 if self._first:
1304 self.ui.write("[\n {")
1304 self.ui.write("[\n {")
1305 self._first = False
1305 self._first = False
1306 else:
1306 else:
1307 self.ui.write(",\n {")
1307 self.ui.write(",\n {")
1308
1308
1309 if self.ui.quiet:
1309 if self.ui.quiet:
1310 self.ui.write('\n "rev": %s' % jrev)
1310 self.ui.write('\n "rev": %s' % jrev)
1311 self.ui.write(',\n "node": %s' % jnode)
1311 self.ui.write(',\n "node": %s' % jnode)
1312 self.ui.write('\n }')
1312 self.ui.write('\n }')
1313 return
1313 return
1314
1314
1315 self.ui.write('\n "rev": %s' % jrev)
1315 self.ui.write('\n "rev": %s' % jrev)
1316 self.ui.write(',\n "node": %s' % jnode)
1316 self.ui.write(',\n "node": %s' % jnode)
1317 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1317 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1318 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1318 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1319 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1319 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1320 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1320 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1321 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1321 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1322
1322
1323 self.ui.write(',\n "bookmarks": [%s]' %
1323 self.ui.write(',\n "bookmarks": [%s]' %
1324 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1324 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1325 self.ui.write(',\n "tags": [%s]' %
1325 self.ui.write(',\n "tags": [%s]' %
1326 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1326 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1327 self.ui.write(',\n "parents": [%s]' %
1327 self.ui.write(',\n "parents": [%s]' %
1328 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1328 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1329
1329
1330 if self.ui.debugflag:
1330 if self.ui.debugflag:
1331 if rev is None:
1331 if rev is None:
1332 jmanifestnode = 'null'
1332 jmanifestnode = 'null'
1333 else:
1333 else:
1334 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1334 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1335 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1335 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1336
1336
1337 self.ui.write(',\n "extra": {%s}' %
1337 self.ui.write(',\n "extra": {%s}' %
1338 ", ".join('"%s": "%s"' % (j(k), j(v))
1338 ", ".join('"%s": "%s"' % (j(k), j(v))
1339 for k, v in ctx.extra().items()))
1339 for k, v in ctx.extra().items()))
1340
1340
1341 files = ctx.p1().status(ctx)
1341 files = ctx.p1().status(ctx)
1342 self.ui.write(',\n "modified": [%s]' %
1342 self.ui.write(',\n "modified": [%s]' %
1343 ", ".join('"%s"' % j(f) for f in files[0]))
1343 ", ".join('"%s"' % j(f) for f in files[0]))
1344 self.ui.write(',\n "added": [%s]' %
1344 self.ui.write(',\n "added": [%s]' %
1345 ", ".join('"%s"' % j(f) for f in files[1]))
1345 ", ".join('"%s"' % j(f) for f in files[1]))
1346 self.ui.write(',\n "removed": [%s]' %
1346 self.ui.write(',\n "removed": [%s]' %
1347 ", ".join('"%s"' % j(f) for f in files[2]))
1347 ", ".join('"%s"' % j(f) for f in files[2]))
1348
1348
1349 elif self.ui.verbose:
1349 elif self.ui.verbose:
1350 self.ui.write(',\n "files": [%s]' %
1350 self.ui.write(',\n "files": [%s]' %
1351 ", ".join('"%s"' % j(f) for f in ctx.files()))
1351 ", ".join('"%s"' % j(f) for f in ctx.files()))
1352
1352
1353 if copies:
1353 if copies:
1354 self.ui.write(',\n "copies": {%s}' %
1354 self.ui.write(',\n "copies": {%s}' %
1355 ", ".join('"%s": "%s"' % (j(k), j(v))
1355 ", ".join('"%s": "%s"' % (j(k), j(v))
1356 for k, v in copies))
1356 for k, v in copies))
1357
1357
1358 matchfn = self.matchfn
1358 matchfn = self.matchfn
1359 if matchfn:
1359 if matchfn:
1360 stat = self.diffopts.get('stat')
1360 stat = self.diffopts.get('stat')
1361 diff = self.diffopts.get('patch')
1361 diff = self.diffopts.get('patch')
1362 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1362 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1363 node, prev = ctx.node(), ctx.p1().node()
1363 node, prev = ctx.node(), ctx.p1().node()
1364 if stat:
1364 if stat:
1365 self.ui.pushbuffer()
1365 self.ui.pushbuffer()
1366 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1366 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1367 match=matchfn, stat=True)
1367 match=matchfn, stat=True)
1368 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1368 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1369 if diff:
1369 if diff:
1370 self.ui.pushbuffer()
1370 self.ui.pushbuffer()
1371 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1371 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1372 match=matchfn, stat=False)
1372 match=matchfn, stat=False)
1373 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1373 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1374
1374
1375 self.ui.write("\n }")
1375 self.ui.write("\n }")
1376
1376
1377 class changeset_templater(changeset_printer):
1377 class changeset_templater(changeset_printer):
1378 '''format changeset information.'''
1378 '''format changeset information.'''
1379
1379
1380 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1380 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1381 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1381 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1382 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1382 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1383 defaulttempl = {
1383 defaulttempl = {
1384 'parent': '{rev}:{node|formatnode} ',
1384 'parent': '{rev}:{node|formatnode} ',
1385 'manifest': '{rev}:{node|formatnode}',
1385 'manifest': '{rev}:{node|formatnode}',
1386 'file_copy': '{name} ({source})',
1386 'file_copy': '{name} ({source})',
1387 'extra': '{key}={value|stringescape}'
1387 'extra': '{key}={value|stringescape}'
1388 }
1388 }
1389 # filecopy is preserved for compatibility reasons
1389 # filecopy is preserved for compatibility reasons
1390 defaulttempl['filecopy'] = defaulttempl['file_copy']
1390 defaulttempl['filecopy'] = defaulttempl['file_copy']
1391 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1391 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1392 cache=defaulttempl)
1392 cache=defaulttempl)
1393 if tmpl:
1393 if tmpl:
1394 self.t.cache['changeset'] = tmpl
1394 self.t.cache['changeset'] = tmpl
1395
1395
1396 self.cache = {}
1396 self.cache = {}
1397
1397
1398 def _show(self, ctx, copies, matchfn, props):
1398 def _show(self, ctx, copies, matchfn, props):
1399 '''show a single changeset or file revision'''
1399 '''show a single changeset or file revision'''
1400
1400
1401 showlist = templatekw.showlist
1401 showlist = templatekw.showlist
1402
1402
1403 # showparents() behaviour depends on ui trace level which
1403 # showparents() behaviour depends on ui trace level which
1404 # causes unexpected behaviours at templating level and makes
1404 # causes unexpected behaviours at templating level and makes
1405 # it harder to extract it in a standalone function. Its
1405 # it harder to extract it in a standalone function. Its
1406 # behaviour cannot be changed so leave it here for now.
1406 # behaviour cannot be changed so leave it here for now.
1407 def showparents(**args):
1407 def showparents(**args):
1408 ctx = args['ctx']
1408 ctx = args['ctx']
1409 parents = [[('rev', p.rev()),
1409 parents = [[('rev', p.rev()),
1410 ('node', p.hex()),
1410 ('node', p.hex()),
1411 ('phase', p.phasestr())]
1411 ('phase', p.phasestr())]
1412 for p in self._meaningful_parentrevs(ctx)]
1412 for p in self._meaningful_parentrevs(ctx)]
1413 return showlist('parent', parents, **args)
1413 return showlist('parent', parents, **args)
1414
1414
1415 props = props.copy()
1415 props = props.copy()
1416 props.update(templatekw.keywords)
1416 props.update(templatekw.keywords)
1417 props['parents'] = showparents
1417 props['parents'] = showparents
1418 props['templ'] = self.t
1418 props['templ'] = self.t
1419 props['ctx'] = ctx
1419 props['ctx'] = ctx
1420 props['repo'] = self.repo
1420 props['repo'] = self.repo
1421 props['revcache'] = {'copies': copies}
1421 props['revcache'] = {'copies': copies}
1422 props['cache'] = self.cache
1422 props['cache'] = self.cache
1423
1423
1424 # find correct templates for current mode
1424 # find correct templates for current mode
1425
1425
1426 tmplmodes = [
1426 tmplmodes = [
1427 (True, None),
1427 (True, None),
1428 (self.ui.verbose, 'verbose'),
1428 (self.ui.verbose, 'verbose'),
1429 (self.ui.quiet, 'quiet'),
1429 (self.ui.quiet, 'quiet'),
1430 (self.ui.debugflag, 'debug'),
1430 (self.ui.debugflag, 'debug'),
1431 ]
1431 ]
1432
1432
1433 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1433 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1434 for mode, postfix in tmplmodes:
1434 for mode, postfix in tmplmodes:
1435 for type in types:
1435 for type in types:
1436 cur = postfix and ('%s_%s' % (type, postfix)) or type
1436 cur = postfix and ('%s_%s' % (type, postfix)) or type
1437 if mode and cur in self.t:
1437 if mode and cur in self.t:
1438 types[type] = cur
1438 types[type] = cur
1439
1439
1440 try:
1440 try:
1441
1441
1442 # write header
1442 # write header
1443 if types['header']:
1443 if types['header']:
1444 h = templater.stringify(self.t(types['header'], **props))
1444 h = templater.stringify(self.t(types['header'], **props))
1445 if self.buffered:
1445 if self.buffered:
1446 self.header[ctx.rev()] = h
1446 self.header[ctx.rev()] = h
1447 else:
1447 else:
1448 if self.lastheader != h:
1448 if self.lastheader != h:
1449 self.lastheader = h
1449 self.lastheader = h
1450 self.ui.write(h)
1450 self.ui.write(h)
1451
1451
1452 # write changeset metadata, then patch if requested
1452 # write changeset metadata, then patch if requested
1453 key = types['changeset']
1453 key = types['changeset']
1454 self.ui.write(templater.stringify(self.t(key, **props)))
1454 self.ui.write(templater.stringify(self.t(key, **props)))
1455 self.showpatch(ctx.node(), matchfn)
1455 self.showpatch(ctx.node(), matchfn)
1456
1456
1457 if types['footer']:
1457 if types['footer']:
1458 if not self.footer:
1458 if not self.footer:
1459 self.footer = templater.stringify(self.t(types['footer'],
1459 self.footer = templater.stringify(self.t(types['footer'],
1460 **props))
1460 **props))
1461
1461
1462 except KeyError as inst:
1462 except KeyError as inst:
1463 msg = _("%s: no key named '%s'")
1463 msg = _("%s: no key named '%s'")
1464 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1464 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1465 except SyntaxError as inst:
1465 except SyntaxError as inst:
1466 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1466 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1467
1467
1468 def gettemplate(ui, tmpl, style):
1468 def gettemplate(ui, tmpl, style):
1469 """
1469 """
1470 Find the template matching the given template spec or style.
1470 Find the template matching the given template spec or style.
1471 """
1471 """
1472
1472
1473 # ui settings
1473 # ui settings
1474 if not tmpl and not style: # template are stronger than style
1474 if not tmpl and not style: # template are stronger than style
1475 tmpl = ui.config('ui', 'logtemplate')
1475 tmpl = ui.config('ui', 'logtemplate')
1476 if tmpl:
1476 if tmpl:
1477 try:
1477 try:
1478 tmpl = templater.unquotestring(tmpl)
1478 tmpl = templater.unquotestring(tmpl)
1479 except SyntaxError:
1479 except SyntaxError:
1480 pass
1480 pass
1481 return tmpl, None
1481 return tmpl, None
1482 else:
1482 else:
1483 style = util.expandpath(ui.config('ui', 'style', ''))
1483 style = util.expandpath(ui.config('ui', 'style', ''))
1484
1484
1485 if not tmpl and style:
1485 if not tmpl and style:
1486 mapfile = style
1486 mapfile = style
1487 if not os.path.split(mapfile)[0]:
1487 if not os.path.split(mapfile)[0]:
1488 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1488 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1489 or templater.templatepath(mapfile))
1489 or templater.templatepath(mapfile))
1490 if mapname:
1490 if mapname:
1491 mapfile = mapname
1491 mapfile = mapname
1492 return None, mapfile
1492 return None, mapfile
1493
1493
1494 if not tmpl:
1494 if not tmpl:
1495 return None, None
1495 return None, None
1496
1496
1497 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1497 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1498
1498
1499 def show_changeset(ui, repo, opts, buffered=False):
1499 def show_changeset(ui, repo, opts, buffered=False):
1500 """show one changeset using template or regular display.
1500 """show one changeset using template or regular display.
1501
1501
1502 Display format will be the first non-empty hit of:
1502 Display format will be the first non-empty hit of:
1503 1. option 'template'
1503 1. option 'template'
1504 2. option 'style'
1504 2. option 'style'
1505 3. [ui] setting 'logtemplate'
1505 3. [ui] setting 'logtemplate'
1506 4. [ui] setting 'style'
1506 4. [ui] setting 'style'
1507 If all of these values are either the unset or the empty string,
1507 If all of these values are either the unset or the empty string,
1508 regular display via changeset_printer() is done.
1508 regular display via changeset_printer() is done.
1509 """
1509 """
1510 # options
1510 # options
1511 matchfn = None
1511 matchfn = None
1512 if opts.get('patch') or opts.get('stat'):
1512 if opts.get('patch') or opts.get('stat'):
1513 matchfn = scmutil.matchall(repo)
1513 matchfn = scmutil.matchall(repo)
1514
1514
1515 if opts.get('template') == 'json':
1515 if opts.get('template') == 'json':
1516 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1516 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1517
1517
1518 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1518 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1519
1519
1520 if not tmpl and not mapfile:
1520 if not tmpl and not mapfile:
1521 return changeset_printer(ui, repo, matchfn, opts, buffered)
1521 return changeset_printer(ui, repo, matchfn, opts, buffered)
1522
1522
1523 try:
1523 try:
1524 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1524 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1525 buffered)
1525 buffered)
1526 except SyntaxError as inst:
1526 except SyntaxError as inst:
1527 raise util.Abort(inst.args[0])
1527 raise util.Abort(inst.args[0])
1528 return t
1528 return t
1529
1529
1530 def showmarker(ui, marker):
1530 def showmarker(ui, marker):
1531 """utility function to display obsolescence marker in a readable way
1531 """utility function to display obsolescence marker in a readable way
1532
1532
1533 To be used by debug function."""
1533 To be used by debug function."""
1534 ui.write(hex(marker.precnode()))
1534 ui.write(hex(marker.precnode()))
1535 for repl in marker.succnodes():
1535 for repl in marker.succnodes():
1536 ui.write(' ')
1536 ui.write(' ')
1537 ui.write(hex(repl))
1537 ui.write(hex(repl))
1538 ui.write(' %X ' % marker.flags())
1538 ui.write(' %X ' % marker.flags())
1539 parents = marker.parentnodes()
1539 parents = marker.parentnodes()
1540 if parents is not None:
1540 if parents is not None:
1541 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1541 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1542 ui.write('(%s) ' % util.datestr(marker.date()))
1542 ui.write('(%s) ' % util.datestr(marker.date()))
1543 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1543 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1544 sorted(marker.metadata().items())
1544 sorted(marker.metadata().items())
1545 if t[0] != 'date')))
1545 if t[0] != 'date')))
1546 ui.write('\n')
1546 ui.write('\n')
1547
1547
1548 def finddate(ui, repo, date):
1548 def finddate(ui, repo, date):
1549 """Find the tipmost changeset that matches the given date spec"""
1549 """Find the tipmost changeset that matches the given date spec"""
1550
1550
1551 df = util.matchdate(date)
1551 df = util.matchdate(date)
1552 m = scmutil.matchall(repo)
1552 m = scmutil.matchall(repo)
1553 results = {}
1553 results = {}
1554
1554
1555 def prep(ctx, fns):
1555 def prep(ctx, fns):
1556 d = ctx.date()
1556 d = ctx.date()
1557 if df(d[0]):
1557 if df(d[0]):
1558 results[ctx.rev()] = d
1558 results[ctx.rev()] = d
1559
1559
1560 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1560 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1561 rev = ctx.rev()
1561 rev = ctx.rev()
1562 if rev in results:
1562 if rev in results:
1563 ui.status(_("found revision %s from %s\n") %
1563 ui.status(_("found revision %s from %s\n") %
1564 (rev, util.datestr(results[rev])))
1564 (rev, util.datestr(results[rev])))
1565 return str(rev)
1565 return str(rev)
1566
1566
1567 raise util.Abort(_("revision matching date not found"))
1567 raise util.Abort(_("revision matching date not found"))
1568
1568
1569 def increasingwindows(windowsize=8, sizelimit=512):
1569 def increasingwindows(windowsize=8, sizelimit=512):
1570 while True:
1570 while True:
1571 yield windowsize
1571 yield windowsize
1572 if windowsize < sizelimit:
1572 if windowsize < sizelimit:
1573 windowsize *= 2
1573 windowsize *= 2
1574
1574
1575 class FileWalkError(Exception):
1575 class FileWalkError(Exception):
1576 pass
1576 pass
1577
1577
1578 def walkfilerevs(repo, match, follow, revs, fncache):
1578 def walkfilerevs(repo, match, follow, revs, fncache):
1579 '''Walks the file history for the matched files.
1579 '''Walks the file history for the matched files.
1580
1580
1581 Returns the changeset revs that are involved in the file history.
1581 Returns the changeset revs that are involved in the file history.
1582
1582
1583 Throws FileWalkError if the file history can't be walked using
1583 Throws FileWalkError if the file history can't be walked using
1584 filelogs alone.
1584 filelogs alone.
1585 '''
1585 '''
1586 wanted = set()
1586 wanted = set()
1587 copies = []
1587 copies = []
1588 minrev, maxrev = min(revs), max(revs)
1588 minrev, maxrev = min(revs), max(revs)
1589 def filerevgen(filelog, last):
1589 def filerevgen(filelog, last):
1590 """
1590 """
1591 Only files, no patterns. Check the history of each file.
1591 Only files, no patterns. Check the history of each file.
1592
1592
1593 Examines filelog entries within minrev, maxrev linkrev range
1593 Examines filelog entries within minrev, maxrev linkrev range
1594 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1594 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1595 tuples in backwards order
1595 tuples in backwards order
1596 """
1596 """
1597 cl_count = len(repo)
1597 cl_count = len(repo)
1598 revs = []
1598 revs = []
1599 for j in xrange(0, last + 1):
1599 for j in xrange(0, last + 1):
1600 linkrev = filelog.linkrev(j)
1600 linkrev = filelog.linkrev(j)
1601 if linkrev < minrev:
1601 if linkrev < minrev:
1602 continue
1602 continue
1603 # only yield rev for which we have the changelog, it can
1603 # only yield rev for which we have the changelog, it can
1604 # happen while doing "hg log" during a pull or commit
1604 # happen while doing "hg log" during a pull or commit
1605 if linkrev >= cl_count:
1605 if linkrev >= cl_count:
1606 break
1606 break
1607
1607
1608 parentlinkrevs = []
1608 parentlinkrevs = []
1609 for p in filelog.parentrevs(j):
1609 for p in filelog.parentrevs(j):
1610 if p != nullrev:
1610 if p != nullrev:
1611 parentlinkrevs.append(filelog.linkrev(p))
1611 parentlinkrevs.append(filelog.linkrev(p))
1612 n = filelog.node(j)
1612 n = filelog.node(j)
1613 revs.append((linkrev, parentlinkrevs,
1613 revs.append((linkrev, parentlinkrevs,
1614 follow and filelog.renamed(n)))
1614 follow and filelog.renamed(n)))
1615
1615
1616 return reversed(revs)
1616 return reversed(revs)
1617 def iterfiles():
1617 def iterfiles():
1618 pctx = repo['.']
1618 pctx = repo['.']
1619 for filename in match.files():
1619 for filename in match.files():
1620 if follow:
1620 if follow:
1621 if filename not in pctx:
1621 if filename not in pctx:
1622 raise util.Abort(_('cannot follow file not in parent '
1622 raise util.Abort(_('cannot follow file not in parent '
1623 'revision: "%s"') % filename)
1623 'revision: "%s"') % filename)
1624 yield filename, pctx[filename].filenode()
1624 yield filename, pctx[filename].filenode()
1625 else:
1625 else:
1626 yield filename, None
1626 yield filename, None
1627 for filename_node in copies:
1627 for filename_node in copies:
1628 yield filename_node
1628 yield filename_node
1629
1629
1630 for file_, node in iterfiles():
1630 for file_, node in iterfiles():
1631 filelog = repo.file(file_)
1631 filelog = repo.file(file_)
1632 if not len(filelog):
1632 if not len(filelog):
1633 if node is None:
1633 if node is None:
1634 # A zero count may be a directory or deleted file, so
1634 # A zero count may be a directory or deleted file, so
1635 # try to find matching entries on the slow path.
1635 # try to find matching entries on the slow path.
1636 if follow:
1636 if follow:
1637 raise util.Abort(
1637 raise util.Abort(
1638 _('cannot follow nonexistent file: "%s"') % file_)
1638 _('cannot follow nonexistent file: "%s"') % file_)
1639 raise FileWalkError("Cannot walk via filelog")
1639 raise FileWalkError("Cannot walk via filelog")
1640 else:
1640 else:
1641 continue
1641 continue
1642
1642
1643 if node is None:
1643 if node is None:
1644 last = len(filelog) - 1
1644 last = len(filelog) - 1
1645 else:
1645 else:
1646 last = filelog.rev(node)
1646 last = filelog.rev(node)
1647
1647
1648 # keep track of all ancestors of the file
1648 # keep track of all ancestors of the file
1649 ancestors = set([filelog.linkrev(last)])
1649 ancestors = set([filelog.linkrev(last)])
1650
1650
1651 # iterate from latest to oldest revision
1651 # iterate from latest to oldest revision
1652 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1652 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1653 if not follow:
1653 if not follow:
1654 if rev > maxrev:
1654 if rev > maxrev:
1655 continue
1655 continue
1656 else:
1656 else:
1657 # Note that last might not be the first interesting
1657 # Note that last might not be the first interesting
1658 # rev to us:
1658 # rev to us:
1659 # if the file has been changed after maxrev, we'll
1659 # if the file has been changed after maxrev, we'll
1660 # have linkrev(last) > maxrev, and we still need
1660 # have linkrev(last) > maxrev, and we still need
1661 # to explore the file graph
1661 # to explore the file graph
1662 if rev not in ancestors:
1662 if rev not in ancestors:
1663 continue
1663 continue
1664 # XXX insert 1327 fix here
1664 # XXX insert 1327 fix here
1665 if flparentlinkrevs:
1665 if flparentlinkrevs:
1666 ancestors.update(flparentlinkrevs)
1666 ancestors.update(flparentlinkrevs)
1667
1667
1668 fncache.setdefault(rev, []).append(file_)
1668 fncache.setdefault(rev, []).append(file_)
1669 wanted.add(rev)
1669 wanted.add(rev)
1670 if copied:
1670 if copied:
1671 copies.append(copied)
1671 copies.append(copied)
1672
1672
1673 return wanted
1673 return wanted
1674
1674
1675 class _followfilter(object):
1675 class _followfilter(object):
1676 def __init__(self, repo, onlyfirst=False):
1676 def __init__(self, repo, onlyfirst=False):
1677 self.repo = repo
1677 self.repo = repo
1678 self.startrev = nullrev
1678 self.startrev = nullrev
1679 self.roots = set()
1679 self.roots = set()
1680 self.onlyfirst = onlyfirst
1680 self.onlyfirst = onlyfirst
1681
1681
1682 def match(self, rev):
1682 def match(self, rev):
1683 def realparents(rev):
1683 def realparents(rev):
1684 if self.onlyfirst:
1684 if self.onlyfirst:
1685 return self.repo.changelog.parentrevs(rev)[0:1]
1685 return self.repo.changelog.parentrevs(rev)[0:1]
1686 else:
1686 else:
1687 return filter(lambda x: x != nullrev,
1687 return filter(lambda x: x != nullrev,
1688 self.repo.changelog.parentrevs(rev))
1688 self.repo.changelog.parentrevs(rev))
1689
1689
1690 if self.startrev == nullrev:
1690 if self.startrev == nullrev:
1691 self.startrev = rev
1691 self.startrev = rev
1692 return True
1692 return True
1693
1693
1694 if rev > self.startrev:
1694 if rev > self.startrev:
1695 # forward: all descendants
1695 # forward: all descendants
1696 if not self.roots:
1696 if not self.roots:
1697 self.roots.add(self.startrev)
1697 self.roots.add(self.startrev)
1698 for parent in realparents(rev):
1698 for parent in realparents(rev):
1699 if parent in self.roots:
1699 if parent in self.roots:
1700 self.roots.add(rev)
1700 self.roots.add(rev)
1701 return True
1701 return True
1702 else:
1702 else:
1703 # backwards: all parents
1703 # backwards: all parents
1704 if not self.roots:
1704 if not self.roots:
1705 self.roots.update(realparents(self.startrev))
1705 self.roots.update(realparents(self.startrev))
1706 if rev in self.roots:
1706 if rev in self.roots:
1707 self.roots.remove(rev)
1707 self.roots.remove(rev)
1708 self.roots.update(realparents(rev))
1708 self.roots.update(realparents(rev))
1709 return True
1709 return True
1710
1710
1711 return False
1711 return False
1712
1712
1713 def walkchangerevs(repo, match, opts, prepare):
1713 def walkchangerevs(repo, match, opts, prepare):
1714 '''Iterate over files and the revs in which they changed.
1714 '''Iterate over files and the revs in which they changed.
1715
1715
1716 Callers most commonly need to iterate backwards over the history
1716 Callers most commonly need to iterate backwards over the history
1717 in which they are interested. Doing so has awful (quadratic-looking)
1717 in which they are interested. Doing so has awful (quadratic-looking)
1718 performance, so we use iterators in a "windowed" way.
1718 performance, so we use iterators in a "windowed" way.
1719
1719
1720 We walk a window of revisions in the desired order. Within the
1720 We walk a window of revisions in the desired order. Within the
1721 window, we first walk forwards to gather data, then in the desired
1721 window, we first walk forwards to gather data, then in the desired
1722 order (usually backwards) to display it.
1722 order (usually backwards) to display it.
1723
1723
1724 This function returns an iterator yielding contexts. Before
1724 This function returns an iterator yielding contexts. Before
1725 yielding each context, the iterator will first call the prepare
1725 yielding each context, the iterator will first call the prepare
1726 function on each context in the window in forward order.'''
1726 function on each context in the window in forward order.'''
1727
1727
1728 follow = opts.get('follow') or opts.get('follow_first')
1728 follow = opts.get('follow') or opts.get('follow_first')
1729 revs = _logrevs(repo, opts)
1729 revs = _logrevs(repo, opts)
1730 if not revs:
1730 if not revs:
1731 return []
1731 return []
1732 wanted = set()
1732 wanted = set()
1733 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1733 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1734 opts.get('removed'))
1734 opts.get('removed'))
1735 fncache = {}
1735 fncache = {}
1736 change = repo.changectx
1736 change = repo.changectx
1737
1737
1738 # First step is to fill wanted, the set of revisions that we want to yield.
1738 # First step is to fill wanted, the set of revisions that we want to yield.
1739 # When it does not induce extra cost, we also fill fncache for revisions in
1739 # When it does not induce extra cost, we also fill fncache for revisions in
1740 # wanted: a cache of filenames that were changed (ctx.files()) and that
1740 # wanted: a cache of filenames that were changed (ctx.files()) and that
1741 # match the file filtering conditions.
1741 # match the file filtering conditions.
1742
1742
1743 if match.always():
1743 if match.always():
1744 # No files, no patterns. Display all revs.
1744 # No files, no patterns. Display all revs.
1745 wanted = revs
1745 wanted = revs
1746 elif not slowpath:
1746 elif not slowpath:
1747 # We only have to read through the filelog to find wanted revisions
1747 # We only have to read through the filelog to find wanted revisions
1748
1748
1749 try:
1749 try:
1750 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1750 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1751 except FileWalkError:
1751 except FileWalkError:
1752 slowpath = True
1752 slowpath = True
1753
1753
1754 # We decided to fall back to the slowpath because at least one
1754 # We decided to fall back to the slowpath because at least one
1755 # of the paths was not a file. Check to see if at least one of them
1755 # of the paths was not a file. Check to see if at least one of them
1756 # existed in history, otherwise simply return
1756 # existed in history, otherwise simply return
1757 for path in match.files():
1757 for path in match.files():
1758 if path == '.' or path in repo.store:
1758 if path == '.' or path in repo.store:
1759 break
1759 break
1760 else:
1760 else:
1761 return []
1761 return []
1762
1762
1763 if slowpath:
1763 if slowpath:
1764 # We have to read the changelog to match filenames against
1764 # We have to read the changelog to match filenames against
1765 # changed files
1765 # changed files
1766
1766
1767 if follow:
1767 if follow:
1768 raise util.Abort(_('can only follow copies/renames for explicit '
1768 raise util.Abort(_('can only follow copies/renames for explicit '
1769 'filenames'))
1769 'filenames'))
1770
1770
1771 # The slow path checks files modified in every changeset.
1771 # The slow path checks files modified in every changeset.
1772 # This is really slow on large repos, so compute the set lazily.
1772 # This is really slow on large repos, so compute the set lazily.
1773 class lazywantedset(object):
1773 class lazywantedset(object):
1774 def __init__(self):
1774 def __init__(self):
1775 self.set = set()
1775 self.set = set()
1776 self.revs = set(revs)
1776 self.revs = set(revs)
1777
1777
1778 # No need to worry about locality here because it will be accessed
1778 # No need to worry about locality here because it will be accessed
1779 # in the same order as the increasing window below.
1779 # in the same order as the increasing window below.
1780 def __contains__(self, value):
1780 def __contains__(self, value):
1781 if value in self.set:
1781 if value in self.set:
1782 return True
1782 return True
1783 elif not value in self.revs:
1783 elif not value in self.revs:
1784 return False
1784 return False
1785 else:
1785 else:
1786 self.revs.discard(value)
1786 self.revs.discard(value)
1787 ctx = change(value)
1787 ctx = change(value)
1788 matches = filter(match, ctx.files())
1788 matches = filter(match, ctx.files())
1789 if matches:
1789 if matches:
1790 fncache[value] = matches
1790 fncache[value] = matches
1791 self.set.add(value)
1791 self.set.add(value)
1792 return True
1792 return True
1793 return False
1793 return False
1794
1794
1795 def discard(self, value):
1795 def discard(self, value):
1796 self.revs.discard(value)
1796 self.revs.discard(value)
1797 self.set.discard(value)
1797 self.set.discard(value)
1798
1798
1799 wanted = lazywantedset()
1799 wanted = lazywantedset()
1800
1800
1801 # it might be worthwhile to do this in the iterator if the rev range
1801 # it might be worthwhile to do this in the iterator if the rev range
1802 # is descending and the prune args are all within that range
1802 # is descending and the prune args are all within that range
1803 for rev in opts.get('prune', ()):
1803 for rev in opts.get('prune', ()):
1804 rev = repo[rev].rev()
1804 rev = repo[rev].rev()
1805 ff = _followfilter(repo)
1805 ff = _followfilter(repo)
1806 stop = min(revs[0], revs[-1])
1806 stop = min(revs[0], revs[-1])
1807 for x in xrange(rev, stop - 1, -1):
1807 for x in xrange(rev, stop - 1, -1):
1808 if ff.match(x):
1808 if ff.match(x):
1809 wanted = wanted - [x]
1809 wanted = wanted - [x]
1810
1810
1811 # Now that wanted is correctly initialized, we can iterate over the
1811 # Now that wanted is correctly initialized, we can iterate over the
1812 # revision range, yielding only revisions in wanted.
1812 # revision range, yielding only revisions in wanted.
1813 def iterate():
1813 def iterate():
1814 if follow and match.always():
1814 if follow and match.always():
1815 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1815 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1816 def want(rev):
1816 def want(rev):
1817 return ff.match(rev) and rev in wanted
1817 return ff.match(rev) and rev in wanted
1818 else:
1818 else:
1819 def want(rev):
1819 def want(rev):
1820 return rev in wanted
1820 return rev in wanted
1821
1821
1822 it = iter(revs)
1822 it = iter(revs)
1823 stopiteration = False
1823 stopiteration = False
1824 for windowsize in increasingwindows():
1824 for windowsize in increasingwindows():
1825 nrevs = []
1825 nrevs = []
1826 for i in xrange(windowsize):
1826 for i in xrange(windowsize):
1827 rev = next(it, None)
1827 rev = next(it, None)
1828 if rev is None:
1828 if rev is None:
1829 stopiteration = True
1829 stopiteration = True
1830 break
1830 break
1831 elif want(rev):
1831 elif want(rev):
1832 nrevs.append(rev)
1832 nrevs.append(rev)
1833 for rev in sorted(nrevs):
1833 for rev in sorted(nrevs):
1834 fns = fncache.get(rev)
1834 fns = fncache.get(rev)
1835 ctx = change(rev)
1835 ctx = change(rev)
1836 if not fns:
1836 if not fns:
1837 def fns_generator():
1837 def fns_generator():
1838 for f in ctx.files():
1838 for f in ctx.files():
1839 if match(f):
1839 if match(f):
1840 yield f
1840 yield f
1841 fns = fns_generator()
1841 fns = fns_generator()
1842 prepare(ctx, fns)
1842 prepare(ctx, fns)
1843 for rev in nrevs:
1843 for rev in nrevs:
1844 yield change(rev)
1844 yield change(rev)
1845
1845
1846 if stopiteration:
1846 if stopiteration:
1847 break
1847 break
1848
1848
1849 return iterate()
1849 return iterate()
1850
1850
1851 def _makefollowlogfilematcher(repo, files, followfirst):
1851 def _makefollowlogfilematcher(repo, files, followfirst):
1852 # When displaying a revision with --patch --follow FILE, we have
1852 # When displaying a revision with --patch --follow FILE, we have
1853 # to know which file of the revision must be diffed. With
1853 # to know which file of the revision must be diffed. With
1854 # --follow, we want the names of the ancestors of FILE in the
1854 # --follow, we want the names of the ancestors of FILE in the
1855 # revision, stored in "fcache". "fcache" is populated by
1855 # revision, stored in "fcache". "fcache" is populated by
1856 # reproducing the graph traversal already done by --follow revset
1856 # reproducing the graph traversal already done by --follow revset
1857 # and relating linkrevs to file names (which is not "correct" but
1857 # and relating linkrevs to file names (which is not "correct" but
1858 # good enough).
1858 # good enough).
1859 fcache = {}
1859 fcache = {}
1860 fcacheready = [False]
1860 fcacheready = [False]
1861 pctx = repo['.']
1861 pctx = repo['.']
1862
1862
1863 def populate():
1863 def populate():
1864 for fn in files:
1864 for fn in files:
1865 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1865 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1866 for c in i:
1866 for c in i:
1867 fcache.setdefault(c.linkrev(), set()).add(c.path())
1867 fcache.setdefault(c.linkrev(), set()).add(c.path())
1868
1868
1869 def filematcher(rev):
1869 def filematcher(rev):
1870 if not fcacheready[0]:
1870 if not fcacheready[0]:
1871 # Lazy initialization
1871 # Lazy initialization
1872 fcacheready[0] = True
1872 fcacheready[0] = True
1873 populate()
1873 populate()
1874 return scmutil.matchfiles(repo, fcache.get(rev, []))
1874 return scmutil.matchfiles(repo, fcache.get(rev, []))
1875
1875
1876 return filematcher
1876 return filematcher
1877
1877
1878 def _makenofollowlogfilematcher(repo, pats, opts):
1878 def _makenofollowlogfilematcher(repo, pats, opts):
1879 '''hook for extensions to override the filematcher for non-follow cases'''
1879 '''hook for extensions to override the filematcher for non-follow cases'''
1880 return None
1880 return None
1881
1881
1882 def _makelogrevset(repo, pats, opts, revs):
1882 def _makelogrevset(repo, pats, opts, revs):
1883 """Return (expr, filematcher) where expr is a revset string built
1883 """Return (expr, filematcher) where expr is a revset string built
1884 from log options and file patterns or None. If --stat or --patch
1884 from log options and file patterns or None. If --stat or --patch
1885 are not passed filematcher is None. Otherwise it is a callable
1885 are not passed filematcher is None. Otherwise it is a callable
1886 taking a revision number and returning a match objects filtering
1886 taking a revision number and returning a match objects filtering
1887 the files to be detailed when displaying the revision.
1887 the files to be detailed when displaying the revision.
1888 """
1888 """
1889 opt2revset = {
1889 opt2revset = {
1890 'no_merges': ('not merge()', None),
1890 'no_merges': ('not merge()', None),
1891 'only_merges': ('merge()', None),
1891 'only_merges': ('merge()', None),
1892 '_ancestors': ('ancestors(%(val)s)', None),
1892 '_ancestors': ('ancestors(%(val)s)', None),
1893 '_fancestors': ('_firstancestors(%(val)s)', None),
1893 '_fancestors': ('_firstancestors(%(val)s)', None),
1894 '_descendants': ('descendants(%(val)s)', None),
1894 '_descendants': ('descendants(%(val)s)', None),
1895 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1895 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1896 '_matchfiles': ('_matchfiles(%(val)s)', None),
1896 '_matchfiles': ('_matchfiles(%(val)s)', None),
1897 'date': ('date(%(val)r)', None),
1897 'date': ('date(%(val)r)', None),
1898 'branch': ('branch(%(val)r)', ' or '),
1898 'branch': ('branch(%(val)r)', ' or '),
1899 '_patslog': ('filelog(%(val)r)', ' or '),
1899 '_patslog': ('filelog(%(val)r)', ' or '),
1900 '_patsfollow': ('follow(%(val)r)', ' or '),
1900 '_patsfollow': ('follow(%(val)r)', ' or '),
1901 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1901 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1902 'keyword': ('keyword(%(val)r)', ' or '),
1902 'keyword': ('keyword(%(val)r)', ' or '),
1903 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1903 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1904 'user': ('user(%(val)r)', ' or '),
1904 'user': ('user(%(val)r)', ' or '),
1905 }
1905 }
1906
1906
1907 opts = dict(opts)
1907 opts = dict(opts)
1908 # follow or not follow?
1908 # follow or not follow?
1909 follow = opts.get('follow') or opts.get('follow_first')
1909 follow = opts.get('follow') or opts.get('follow_first')
1910 if opts.get('follow_first'):
1910 if opts.get('follow_first'):
1911 followfirst = 1
1911 followfirst = 1
1912 else:
1912 else:
1913 followfirst = 0
1913 followfirst = 0
1914 # --follow with FILE behaviour depends on revs...
1914 # --follow with FILE behaviour depends on revs...
1915 it = iter(revs)
1915 it = iter(revs)
1916 startrev = it.next()
1916 startrev = it.next()
1917 followdescendants = startrev < next(it, startrev)
1917 followdescendants = startrev < next(it, startrev)
1918
1918
1919 # branch and only_branch are really aliases and must be handled at
1919 # branch and only_branch are really aliases and must be handled at
1920 # the same time
1920 # the same time
1921 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1921 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1922 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1922 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1923 # pats/include/exclude are passed to match.match() directly in
1923 # pats/include/exclude are passed to match.match() directly in
1924 # _matchfiles() revset but walkchangerevs() builds its matcher with
1924 # _matchfiles() revset but walkchangerevs() builds its matcher with
1925 # scmutil.match(). The difference is input pats are globbed on
1925 # scmutil.match(). The difference is input pats are globbed on
1926 # platforms without shell expansion (windows).
1926 # platforms without shell expansion (windows).
1927 wctx = repo[None]
1927 wctx = repo[None]
1928 match, pats = scmutil.matchandpats(wctx, pats, opts)
1928 match, pats = scmutil.matchandpats(wctx, pats, opts)
1929 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1929 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1930 opts.get('removed'))
1930 opts.get('removed'))
1931 if not slowpath:
1931 if not slowpath:
1932 for f in match.files():
1932 for f in match.files():
1933 if follow and f not in wctx:
1933 if follow and f not in wctx:
1934 # If the file exists, it may be a directory, so let it
1934 # If the file exists, it may be a directory, so let it
1935 # take the slow path.
1935 # take the slow path.
1936 if os.path.exists(repo.wjoin(f)):
1936 if os.path.exists(repo.wjoin(f)):
1937 slowpath = True
1937 slowpath = True
1938 continue
1938 continue
1939 else:
1939 else:
1940 raise util.Abort(_('cannot follow file not in parent '
1940 raise util.Abort(_('cannot follow file not in parent '
1941 'revision: "%s"') % f)
1941 'revision: "%s"') % f)
1942 filelog = repo.file(f)
1942 filelog = repo.file(f)
1943 if not filelog:
1943 if not filelog:
1944 # A zero count may be a directory or deleted file, so
1944 # A zero count may be a directory or deleted file, so
1945 # try to find matching entries on the slow path.
1945 # try to find matching entries on the slow path.
1946 if follow:
1946 if follow:
1947 raise util.Abort(
1947 raise util.Abort(
1948 _('cannot follow nonexistent file: "%s"') % f)
1948 _('cannot follow nonexistent file: "%s"') % f)
1949 slowpath = True
1949 slowpath = True
1950
1950
1951 # We decided to fall back to the slowpath because at least one
1951 # We decided to fall back to the slowpath because at least one
1952 # of the paths was not a file. Check to see if at least one of them
1952 # of the paths was not a file. Check to see if at least one of them
1953 # existed in history - in that case, we'll continue down the
1953 # existed in history - in that case, we'll continue down the
1954 # slowpath; otherwise, we can turn off the slowpath
1954 # slowpath; otherwise, we can turn off the slowpath
1955 if slowpath:
1955 if slowpath:
1956 for path in match.files():
1956 for path in match.files():
1957 if path == '.' or path in repo.store:
1957 if path == '.' or path in repo.store:
1958 break
1958 break
1959 else:
1959 else:
1960 slowpath = False
1960 slowpath = False
1961
1961
1962 fpats = ('_patsfollow', '_patsfollowfirst')
1962 fpats = ('_patsfollow', '_patsfollowfirst')
1963 fnopats = (('_ancestors', '_fancestors'),
1963 fnopats = (('_ancestors', '_fancestors'),
1964 ('_descendants', '_fdescendants'))
1964 ('_descendants', '_fdescendants'))
1965 if slowpath:
1965 if slowpath:
1966 # See walkchangerevs() slow path.
1966 # See walkchangerevs() slow path.
1967 #
1967 #
1968 # pats/include/exclude cannot be represented as separate
1968 # pats/include/exclude cannot be represented as separate
1969 # revset expressions as their filtering logic applies at file
1969 # revset expressions as their filtering logic applies at file
1970 # level. For instance "-I a -X a" matches a revision touching
1970 # level. For instance "-I a -X a" matches a revision touching
1971 # "a" and "b" while "file(a) and not file(b)" does
1971 # "a" and "b" while "file(a) and not file(b)" does
1972 # not. Besides, filesets are evaluated against the working
1972 # not. Besides, filesets are evaluated against the working
1973 # directory.
1973 # directory.
1974 matchargs = ['r:', 'd:relpath']
1974 matchargs = ['r:', 'd:relpath']
1975 for p in pats:
1975 for p in pats:
1976 matchargs.append('p:' + p)
1976 matchargs.append('p:' + p)
1977 for p in opts.get('include', []):
1977 for p in opts.get('include', []):
1978 matchargs.append('i:' + p)
1978 matchargs.append('i:' + p)
1979 for p in opts.get('exclude', []):
1979 for p in opts.get('exclude', []):
1980 matchargs.append('x:' + p)
1980 matchargs.append('x:' + p)
1981 matchargs = ','.join(('%r' % p) for p in matchargs)
1981 matchargs = ','.join(('%r' % p) for p in matchargs)
1982 opts['_matchfiles'] = matchargs
1982 opts['_matchfiles'] = matchargs
1983 if follow:
1983 if follow:
1984 opts[fnopats[0][followfirst]] = '.'
1984 opts[fnopats[0][followfirst]] = '.'
1985 else:
1985 else:
1986 if follow:
1986 if follow:
1987 if pats:
1987 if pats:
1988 # follow() revset interprets its file argument as a
1988 # follow() revset interprets its file argument as a
1989 # manifest entry, so use match.files(), not pats.
1989 # manifest entry, so use match.files(), not pats.
1990 opts[fpats[followfirst]] = list(match.files())
1990 opts[fpats[followfirst]] = list(match.files())
1991 else:
1991 else:
1992 op = fnopats[followdescendants][followfirst]
1992 op = fnopats[followdescendants][followfirst]
1993 opts[op] = 'rev(%d)' % startrev
1993 opts[op] = 'rev(%d)' % startrev
1994 else:
1994 else:
1995 opts['_patslog'] = list(pats)
1995 opts['_patslog'] = list(pats)
1996
1996
1997 filematcher = None
1997 filematcher = None
1998 if opts.get('patch') or opts.get('stat'):
1998 if opts.get('patch') or opts.get('stat'):
1999 # When following files, track renames via a special matcher.
1999 # When following files, track renames via a special matcher.
2000 # If we're forced to take the slowpath it means we're following
2000 # If we're forced to take the slowpath it means we're following
2001 # at least one pattern/directory, so don't bother with rename tracking.
2001 # at least one pattern/directory, so don't bother with rename tracking.
2002 if follow and not match.always() and not slowpath:
2002 if follow and not match.always() and not slowpath:
2003 # _makefollowlogfilematcher expects its files argument to be
2003 # _makefollowlogfilematcher expects its files argument to be
2004 # relative to the repo root, so use match.files(), not pats.
2004 # relative to the repo root, so use match.files(), not pats.
2005 filematcher = _makefollowlogfilematcher(repo, match.files(),
2005 filematcher = _makefollowlogfilematcher(repo, match.files(),
2006 followfirst)
2006 followfirst)
2007 else:
2007 else:
2008 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2008 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2009 if filematcher is None:
2009 if filematcher is None:
2010 filematcher = lambda rev: match
2010 filematcher = lambda rev: match
2011
2011
2012 expr = []
2012 expr = []
2013 for op, val in sorted(opts.iteritems()):
2013 for op, val in sorted(opts.iteritems()):
2014 if not val:
2014 if not val:
2015 continue
2015 continue
2016 if op not in opt2revset:
2016 if op not in opt2revset:
2017 continue
2017 continue
2018 revop, andor = opt2revset[op]
2018 revop, andor = opt2revset[op]
2019 if '%(val)' not in revop:
2019 if '%(val)' not in revop:
2020 expr.append(revop)
2020 expr.append(revop)
2021 else:
2021 else:
2022 if not isinstance(val, list):
2022 if not isinstance(val, list):
2023 e = revop % {'val': val}
2023 e = revop % {'val': val}
2024 else:
2024 else:
2025 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2025 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2026 expr.append(e)
2026 expr.append(e)
2027
2027
2028 if expr:
2028 if expr:
2029 expr = '(' + ' and '.join(expr) + ')'
2029 expr = '(' + ' and '.join(expr) + ')'
2030 else:
2030 else:
2031 expr = None
2031 expr = None
2032 return expr, filematcher
2032 return expr, filematcher
2033
2033
2034 def _logrevs(repo, opts):
2034 def _logrevs(repo, opts):
2035 # Default --rev value depends on --follow but --follow behaviour
2035 # Default --rev value depends on --follow but --follow behaviour
2036 # depends on revisions resolved from --rev...
2036 # depends on revisions resolved from --rev...
2037 follow = opts.get('follow') or opts.get('follow_first')
2037 follow = opts.get('follow') or opts.get('follow_first')
2038 if opts.get('rev'):
2038 if opts.get('rev'):
2039 revs = scmutil.revrange(repo, opts['rev'])
2039 revs = scmutil.revrange(repo, opts['rev'])
2040 elif follow and repo.dirstate.p1() == nullid:
2040 elif follow and repo.dirstate.p1() == nullid:
2041 revs = revset.baseset()
2041 revs = revset.baseset()
2042 elif follow:
2042 elif follow:
2043 revs = repo.revs('reverse(:.)')
2043 revs = repo.revs('reverse(:.)')
2044 else:
2044 else:
2045 revs = revset.spanset(repo)
2045 revs = revset.spanset(repo)
2046 revs.reverse()
2046 revs.reverse()
2047 return revs
2047 return revs
2048
2048
2049 def getgraphlogrevs(repo, pats, opts):
2049 def getgraphlogrevs(repo, pats, opts):
2050 """Return (revs, expr, filematcher) where revs is an iterable of
2050 """Return (revs, expr, filematcher) where revs is an iterable of
2051 revision numbers, expr is a revset string built from log options
2051 revision numbers, expr is a revset string built from log options
2052 and file patterns or None, and used to filter 'revs'. If --stat or
2052 and file patterns or None, and used to filter 'revs'. If --stat or
2053 --patch are not passed filematcher is None. Otherwise it is a
2053 --patch are not passed filematcher is None. Otherwise it is a
2054 callable taking a revision number and returning a match objects
2054 callable taking a revision number and returning a match objects
2055 filtering the files to be detailed when displaying the revision.
2055 filtering the files to be detailed when displaying the revision.
2056 """
2056 """
2057 limit = loglimit(opts)
2057 limit = loglimit(opts)
2058 revs = _logrevs(repo, opts)
2058 revs = _logrevs(repo, opts)
2059 if not revs:
2059 if not revs:
2060 return revset.baseset(), None, None
2060 return revset.baseset(), None, None
2061 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2061 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2062 if opts.get('rev'):
2062 if opts.get('rev'):
2063 # User-specified revs might be unsorted, but don't sort before
2063 # User-specified revs might be unsorted, but don't sort before
2064 # _makelogrevset because it might depend on the order of revs
2064 # _makelogrevset because it might depend on the order of revs
2065 revs.sort(reverse=True)
2065 revs.sort(reverse=True)
2066 if expr:
2066 if expr:
2067 # Revset matchers often operate faster on revisions in changelog
2067 # Revset matchers often operate faster on revisions in changelog
2068 # order, because most filters deal with the changelog.
2068 # order, because most filters deal with the changelog.
2069 revs.reverse()
2069 revs.reverse()
2070 matcher = revset.match(repo.ui, expr)
2070 matcher = revset.match(repo.ui, expr)
2071 # Revset matches can reorder revisions. "A or B" typically returns
2071 # Revset matches can reorder revisions. "A or B" typically returns
2072 # returns the revision matching A then the revision matching B. Sort
2072 # returns the revision matching A then the revision matching B. Sort
2073 # again to fix that.
2073 # again to fix that.
2074 revs = matcher(repo, revs)
2074 revs = matcher(repo, revs)
2075 revs.sort(reverse=True)
2075 revs.sort(reverse=True)
2076 if limit is not None:
2076 if limit is not None:
2077 limitedrevs = []
2077 limitedrevs = []
2078 for idx, rev in enumerate(revs):
2078 for idx, rev in enumerate(revs):
2079 if idx >= limit:
2079 if idx >= limit:
2080 break
2080 break
2081 limitedrevs.append(rev)
2081 limitedrevs.append(rev)
2082 revs = revset.baseset(limitedrevs)
2082 revs = revset.baseset(limitedrevs)
2083
2083
2084 return revs, expr, filematcher
2084 return revs, expr, filematcher
2085
2085
2086 def getlogrevs(repo, pats, opts):
2086 def getlogrevs(repo, pats, opts):
2087 """Return (revs, expr, filematcher) where revs is an iterable of
2087 """Return (revs, expr, filematcher) where revs is an iterable of
2088 revision numbers, expr is a revset string built from log options
2088 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
2089 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
2090 --patch are not passed filematcher is None. Otherwise it is a
2091 callable taking a revision number and returning a match objects
2091 callable taking a revision number and returning a match objects
2092 filtering the files to be detailed when displaying the revision.
2092 filtering the files to be detailed when displaying the revision.
2093 """
2093 """
2094 limit = loglimit(opts)
2094 limit = loglimit(opts)
2095 revs = _logrevs(repo, opts)
2095 revs = _logrevs(repo, opts)
2096 if not revs:
2096 if not revs:
2097 return revset.baseset([]), None, None
2097 return revset.baseset([]), None, None
2098 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2098 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2099 if expr:
2099 if expr:
2100 # Revset matchers often operate faster on revisions in changelog
2100 # Revset matchers often operate faster on revisions in changelog
2101 # order, because most filters deal with the changelog.
2101 # order, because most filters deal with the changelog.
2102 if not opts.get('rev'):
2102 if not opts.get('rev'):
2103 revs.reverse()
2103 revs.reverse()
2104 matcher = revset.match(repo.ui, expr)
2104 matcher = revset.match(repo.ui, expr)
2105 # Revset matches can reorder revisions. "A or B" typically returns
2105 # Revset matches can reorder revisions. "A or B" typically returns
2106 # returns the revision matching A then the revision matching B. Sort
2106 # returns the revision matching A then the revision matching B. Sort
2107 # again to fix that.
2107 # again to fix that.
2108 revs = matcher(repo, revs)
2108 revs = matcher(repo, revs)
2109 if not opts.get('rev'):
2109 if not opts.get('rev'):
2110 revs.sort(reverse=True)
2110 revs.sort(reverse=True)
2111 if limit is not None:
2111 if limit is not None:
2112 limitedrevs = []
2112 limitedrevs = []
2113 for idx, r in enumerate(revs):
2113 for idx, r in enumerate(revs):
2114 if limit <= idx:
2114 if limit <= idx:
2115 break
2115 break
2116 limitedrevs.append(r)
2116 limitedrevs.append(r)
2117 revs = revset.baseset(limitedrevs)
2117 revs = revset.baseset(limitedrevs)
2118
2118
2119 return revs, expr, filematcher
2119 return revs, expr, filematcher
2120
2120
2121 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2121 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2122 filematcher=None):
2122 filematcher=None):
2123 seen, state = [], graphmod.asciistate()
2123 seen, state = [], graphmod.asciistate()
2124 for rev, type, ctx, parents in dag:
2124 for rev, type, ctx, parents in dag:
2125 char = 'o'
2125 char = 'o'
2126 if ctx.node() in showparents:
2126 if ctx.node() in showparents:
2127 char = '@'
2127 char = '@'
2128 elif ctx.obsolete():
2128 elif ctx.obsolete():
2129 char = 'x'
2129 char = 'x'
2130 elif ctx.closesbranch():
2130 elif ctx.closesbranch():
2131 char = '_'
2131 char = '_'
2132 copies = None
2132 copies = None
2133 if getrenamed and ctx.rev():
2133 if getrenamed and ctx.rev():
2134 copies = []
2134 copies = []
2135 for fn in ctx.files():
2135 for fn in ctx.files():
2136 rename = getrenamed(fn, ctx.rev())
2136 rename = getrenamed(fn, ctx.rev())
2137 if rename:
2137 if rename:
2138 copies.append((fn, rename[0]))
2138 copies.append((fn, rename[0]))
2139 revmatchfn = None
2139 revmatchfn = None
2140 if filematcher is not None:
2140 if filematcher is not None:
2141 revmatchfn = filematcher(ctx.rev())
2141 revmatchfn = filematcher(ctx.rev())
2142 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2142 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2143 lines = displayer.hunk.pop(rev).split('\n')
2143 lines = displayer.hunk.pop(rev).split('\n')
2144 if not lines[-1]:
2144 if not lines[-1]:
2145 del lines[-1]
2145 del lines[-1]
2146 displayer.flush(rev)
2146 displayer.flush(rev)
2147 edges = edgefn(type, char, lines, seen, rev, parents)
2147 edges = edgefn(type, char, lines, seen, rev, parents)
2148 for type, char, lines, coldata in edges:
2148 for type, char, lines, coldata in edges:
2149 graphmod.ascii(ui, state, type, char, lines, coldata)
2149 graphmod.ascii(ui, state, type, char, lines, coldata)
2150 displayer.close()
2150 displayer.close()
2151
2151
2152 def graphlog(ui, repo, *pats, **opts):
2152 def graphlog(ui, repo, *pats, **opts):
2153 # Parameters are identical to log command ones
2153 # Parameters are identical to log command ones
2154 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2154 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2155 revdag = graphmod.dagwalker(repo, revs)
2155 revdag = graphmod.dagwalker(repo, revs)
2156
2156
2157 getrenamed = None
2157 getrenamed = None
2158 if opts.get('copies'):
2158 if opts.get('copies'):
2159 endrev = None
2159 endrev = None
2160 if opts.get('rev'):
2160 if opts.get('rev'):
2161 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2161 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2162 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2162 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2163 displayer = show_changeset(ui, repo, opts, buffered=True)
2163 displayer = show_changeset(ui, repo, opts, buffered=True)
2164 showparents = [ctx.node() for ctx in repo[None].parents()]
2164 showparents = [ctx.node() for ctx in repo[None].parents()]
2165 displaygraph(ui, revdag, displayer, showparents,
2165 displaygraph(ui, revdag, displayer, showparents,
2166 graphmod.asciiedges, getrenamed, filematcher)
2166 graphmod.asciiedges, getrenamed, filematcher)
2167
2167
2168 def checkunsupportedgraphflags(pats, opts):
2168 def checkunsupportedgraphflags(pats, opts):
2169 for op in ["newest_first"]:
2169 for op in ["newest_first"]:
2170 if op in opts and opts[op]:
2170 if op in opts and opts[op]:
2171 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2171 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2172 % op.replace("_", "-"))
2172 % op.replace("_", "-"))
2173
2173
2174 def graphrevs(repo, nodes, opts):
2174 def graphrevs(repo, nodes, opts):
2175 limit = loglimit(opts)
2175 limit = loglimit(opts)
2176 nodes.reverse()
2176 nodes.reverse()
2177 if limit is not None:
2177 if limit is not None:
2178 nodes = nodes[:limit]
2178 nodes = nodes[:limit]
2179 return graphmod.nodes(repo, nodes)
2179 return graphmod.nodes(repo, nodes)
2180
2180
2181 def add(ui, repo, match, prefix, explicitonly, **opts):
2181 def add(ui, repo, match, prefix, explicitonly, **opts):
2182 join = lambda f: os.path.join(prefix, f)
2182 join = lambda f: os.path.join(prefix, f)
2183 bad = []
2183 bad = []
2184
2184
2185 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2185 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2186 names = []
2186 names = []
2187 wctx = repo[None]
2187 wctx = repo[None]
2188 cca = None
2188 cca = None
2189 abort, warn = scmutil.checkportabilityalert(ui)
2189 abort, warn = scmutil.checkportabilityalert(ui)
2190 if abort or warn:
2190 if abort or warn:
2191 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2191 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2192
2192
2193 for f in wctx.walk(matchmod.badmatch(match, badfn)):
2193 for f in wctx.walk(matchmod.badmatch(match, badfn)):
2194 exact = match.exact(f)
2194 exact = match.exact(f)
2195 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2195 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2196 if cca:
2196 if cca:
2197 cca(f)
2197 cca(f)
2198 names.append(f)
2198 names.append(f)
2199 if ui.verbose or not exact:
2199 if ui.verbose or not exact:
2200 ui.status(_('adding %s\n') % match.rel(f))
2200 ui.status(_('adding %s\n') % match.rel(f))
2201
2201
2202 for subpath in sorted(wctx.substate):
2202 for subpath in sorted(wctx.substate):
2203 sub = wctx.sub(subpath)
2203 sub = wctx.sub(subpath)
2204 try:
2204 try:
2205 submatch = matchmod.narrowmatcher(subpath, match)
2205 submatch = matchmod.narrowmatcher(subpath, match)
2206 if opts.get('subrepos'):
2206 if opts.get('subrepos'):
2207 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2207 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2208 else:
2208 else:
2209 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2209 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2210 except error.LookupError:
2210 except error.LookupError:
2211 ui.status(_("skipping missing subrepository: %s\n")
2211 ui.status(_("skipping missing subrepository: %s\n")
2212 % join(subpath))
2212 % join(subpath))
2213
2213
2214 if not opts.get('dry_run'):
2214 if not opts.get('dry_run'):
2215 rejected = wctx.add(names, prefix)
2215 rejected = wctx.add(names, prefix)
2216 bad.extend(f for f in rejected if f in match.files())
2216 bad.extend(f for f in rejected if f in match.files())
2217 return bad
2217 return bad
2218
2218
2219 def forget(ui, repo, match, prefix, explicitonly):
2219 def forget(ui, repo, match, prefix, explicitonly):
2220 join = lambda f: os.path.join(prefix, f)
2220 join = lambda f: os.path.join(prefix, f)
2221 bad = []
2221 bad = []
2222 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2222 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2223 wctx = repo[None]
2223 wctx = repo[None]
2224 forgot = []
2224 forgot = []
2225
2225
2226 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2226 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2227 forget = sorted(s[0] + s[1] + s[3] + s[6])
2227 forget = sorted(s[0] + s[1] + s[3] + s[6])
2228 if explicitonly:
2228 if explicitonly:
2229 forget = [f for f in forget if match.exact(f)]
2229 forget = [f for f in forget if match.exact(f)]
2230
2230
2231 for subpath in sorted(wctx.substate):
2231 for subpath in sorted(wctx.substate):
2232 sub = wctx.sub(subpath)
2232 sub = wctx.sub(subpath)
2233 try:
2233 try:
2234 submatch = matchmod.narrowmatcher(subpath, match)
2234 submatch = matchmod.narrowmatcher(subpath, match)
2235 subbad, subforgot = sub.forget(submatch, prefix)
2235 subbad, subforgot = sub.forget(submatch, prefix)
2236 bad.extend([subpath + '/' + f for f in subbad])
2236 bad.extend([subpath + '/' + f for f in subbad])
2237 forgot.extend([subpath + '/' + f for f in subforgot])
2237 forgot.extend([subpath + '/' + f for f in subforgot])
2238 except error.LookupError:
2238 except error.LookupError:
2239 ui.status(_("skipping missing subrepository: %s\n")
2239 ui.status(_("skipping missing subrepository: %s\n")
2240 % join(subpath))
2240 % join(subpath))
2241
2241
2242 if not explicitonly:
2242 if not explicitonly:
2243 for f in match.files():
2243 for f in match.files():
2244 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2244 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2245 if f not in forgot:
2245 if f not in forgot:
2246 if repo.wvfs.exists(f):
2246 if repo.wvfs.exists(f):
2247 # Don't complain if the exact case match wasn't given.
2247 # Don't complain if the exact case match wasn't given.
2248 # But don't do this until after checking 'forgot', so
2248 # But don't do this until after checking 'forgot', so
2249 # that subrepo files aren't normalized, and this op is
2249 # that subrepo files aren't normalized, and this op is
2250 # purely from data cached by the status walk above.
2250 # purely from data cached by the status walk above.
2251 if repo.dirstate.normalize(f) in repo.dirstate:
2251 if repo.dirstate.normalize(f) in repo.dirstate:
2252 continue
2252 continue
2253 ui.warn(_('not removing %s: '
2253 ui.warn(_('not removing %s: '
2254 'file is already untracked\n')
2254 'file is already untracked\n')
2255 % match.rel(f))
2255 % match.rel(f))
2256 bad.append(f)
2256 bad.append(f)
2257
2257
2258 for f in forget:
2258 for f in forget:
2259 if ui.verbose or not match.exact(f):
2259 if ui.verbose or not match.exact(f):
2260 ui.status(_('removing %s\n') % match.rel(f))
2260 ui.status(_('removing %s\n') % match.rel(f))
2261
2261
2262 rejected = wctx.forget(forget, prefix)
2262 rejected = wctx.forget(forget, prefix)
2263 bad.extend(f for f in rejected if f in match.files())
2263 bad.extend(f for f in rejected if f in match.files())
2264 forgot.extend(f for f in forget if f not in rejected)
2264 forgot.extend(f for f in forget if f not in rejected)
2265 return bad, forgot
2265 return bad, forgot
2266
2266
2267 def files(ui, ctx, m, fm, fmt, subrepos):
2267 def files(ui, ctx, m, fm, fmt, subrepos):
2268 rev = ctx.rev()
2268 rev = ctx.rev()
2269 ret = 1
2269 ret = 1
2270 ds = ctx.repo().dirstate
2270 ds = ctx.repo().dirstate
2271
2271
2272 for f in ctx.matches(m):
2272 for f in ctx.matches(m):
2273 if rev is None and ds[f] == 'r':
2273 if rev is None and ds[f] == 'r':
2274 continue
2274 continue
2275 fm.startitem()
2275 fm.startitem()
2276 if ui.verbose:
2276 if ui.verbose:
2277 fc = ctx[f]
2277 fc = ctx[f]
2278 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2278 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2279 fm.data(abspath=f)
2279 fm.data(abspath=f)
2280 fm.write('path', fmt, m.rel(f))
2280 fm.write('path', fmt, m.rel(f))
2281 ret = 0
2281 ret = 0
2282
2282
2283 for subpath in sorted(ctx.substate):
2283 for subpath in sorted(ctx.substate):
2284 def matchessubrepo(subpath):
2284 def matchessubrepo(subpath):
2285 return (m.always() or m.exact(subpath)
2285 return (m.always() or m.exact(subpath)
2286 or any(f.startswith(subpath + '/') for f in m.files()))
2286 or any(f.startswith(subpath + '/') for f in m.files()))
2287
2287
2288 if subrepos or matchessubrepo(subpath):
2288 if subrepos or matchessubrepo(subpath):
2289 sub = ctx.sub(subpath)
2289 sub = ctx.sub(subpath)
2290 try:
2290 try:
2291 submatch = matchmod.narrowmatcher(subpath, m)
2291 submatch = matchmod.narrowmatcher(subpath, m)
2292 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2292 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2293 ret = 0
2293 ret = 0
2294 except error.LookupError:
2294 except error.LookupError:
2295 ui.status(_("skipping missing subrepository: %s\n")
2295 ui.status(_("skipping missing subrepository: %s\n")
2296 % m.abs(subpath))
2296 % m.abs(subpath))
2297
2297
2298 return ret
2298 return ret
2299
2299
2300 def remove(ui, repo, m, prefix, after, force, subrepos):
2300 def remove(ui, repo, m, prefix, after, force, subrepos):
2301 join = lambda f: os.path.join(prefix, f)
2301 join = lambda f: os.path.join(prefix, f)
2302 ret = 0
2302 ret = 0
2303 s = repo.status(match=m, clean=True)
2303 s = repo.status(match=m, clean=True)
2304 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2304 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2305
2305
2306 wctx = repo[None]
2306 wctx = repo[None]
2307
2307
2308 for subpath in sorted(wctx.substate):
2308 for subpath in sorted(wctx.substate):
2309 def matchessubrepo(matcher, subpath):
2309 def matchessubrepo(matcher, subpath):
2310 if matcher.exact(subpath):
2310 if matcher.exact(subpath):
2311 return True
2311 return True
2312 for f in matcher.files():
2312 for f in matcher.files():
2313 if f.startswith(subpath):
2313 if f.startswith(subpath):
2314 return True
2314 return True
2315 return False
2315 return False
2316
2316
2317 if subrepos or matchessubrepo(m, subpath):
2317 if subrepos or matchessubrepo(m, subpath):
2318 sub = wctx.sub(subpath)
2318 sub = wctx.sub(subpath)
2319 try:
2319 try:
2320 submatch = matchmod.narrowmatcher(subpath, m)
2320 submatch = matchmod.narrowmatcher(subpath, m)
2321 if sub.removefiles(submatch, prefix, after, force, subrepos):
2321 if sub.removefiles(submatch, prefix, after, force, subrepos):
2322 ret = 1
2322 ret = 1
2323 except error.LookupError:
2323 except error.LookupError:
2324 ui.status(_("skipping missing subrepository: %s\n")
2324 ui.status(_("skipping missing subrepository: %s\n")
2325 % join(subpath))
2325 % join(subpath))
2326
2326
2327 # warn about failure to delete explicit files/dirs
2327 # warn about failure to delete explicit files/dirs
2328 deleteddirs = util.dirs(deleted)
2328 deleteddirs = util.dirs(deleted)
2329 for f in m.files():
2329 for f in m.files():
2330 def insubrepo():
2330 def insubrepo():
2331 for subpath in wctx.substate:
2331 for subpath in wctx.substate:
2332 if f.startswith(subpath):
2332 if f.startswith(subpath):
2333 return True
2333 return True
2334 return False
2334 return False
2335
2335
2336 isdir = f in deleteddirs or wctx.hasdir(f)
2336 isdir = f in deleteddirs or wctx.hasdir(f)
2337 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2337 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2338 continue
2338 continue
2339
2339
2340 if repo.wvfs.exists(f):
2340 if repo.wvfs.exists(f):
2341 if repo.wvfs.isdir(f):
2341 if repo.wvfs.isdir(f):
2342 ui.warn(_('not removing %s: no tracked files\n')
2342 ui.warn(_('not removing %s: no tracked files\n')
2343 % m.rel(f))
2343 % m.rel(f))
2344 else:
2344 else:
2345 ui.warn(_('not removing %s: file is untracked\n')
2345 ui.warn(_('not removing %s: file is untracked\n')
2346 % m.rel(f))
2346 % m.rel(f))
2347 # missing files will generate a warning elsewhere
2347 # missing files will generate a warning elsewhere
2348 ret = 1
2348 ret = 1
2349
2349
2350 if force:
2350 if force:
2351 list = modified + deleted + clean + added
2351 list = modified + deleted + clean + added
2352 elif after:
2352 elif after:
2353 list = deleted
2353 list = deleted
2354 for f in modified + added + clean:
2354 for f in modified + added + clean:
2355 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2355 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2356 ret = 1
2356 ret = 1
2357 else:
2357 else:
2358 list = deleted + clean
2358 list = deleted + clean
2359 for f in modified:
2359 for f in modified:
2360 ui.warn(_('not removing %s: file is modified (use -f'
2360 ui.warn(_('not removing %s: file is modified (use -f'
2361 ' to force removal)\n') % m.rel(f))
2361 ' to force removal)\n') % m.rel(f))
2362 ret = 1
2362 ret = 1
2363 for f in added:
2363 for f in added:
2364 ui.warn(_('not removing %s: file has been marked for add'
2364 ui.warn(_('not removing %s: file has been marked for add'
2365 ' (use forget to undo)\n') % m.rel(f))
2365 ' (use forget to undo)\n') % m.rel(f))
2366 ret = 1
2366 ret = 1
2367
2367
2368 for f in sorted(list):
2368 for f in sorted(list):
2369 if ui.verbose or not m.exact(f):
2369 if ui.verbose or not m.exact(f):
2370 ui.status(_('removing %s\n') % m.rel(f))
2370 ui.status(_('removing %s\n') % m.rel(f))
2371
2371
2372 wlock = repo.wlock()
2372 wlock = repo.wlock()
2373 try:
2373 try:
2374 if not after:
2374 if not after:
2375 for f in list:
2375 for f in list:
2376 if f in added:
2376 if f in added:
2377 continue # we never unlink added files on remove
2377 continue # we never unlink added files on remove
2378 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2378 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2379 repo[None].forget(list)
2379 repo[None].forget(list)
2380 finally:
2380 finally:
2381 wlock.release()
2381 wlock.release()
2382
2382
2383 return ret
2383 return ret
2384
2384
2385 def cat(ui, repo, ctx, matcher, prefix, **opts):
2385 def cat(ui, repo, ctx, matcher, prefix, **opts):
2386 err = 1
2386 err = 1
2387
2387
2388 def write(path):
2388 def write(path):
2389 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2389 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2390 pathname=os.path.join(prefix, path))
2390 pathname=os.path.join(prefix, path))
2391 data = ctx[path].data()
2391 data = ctx[path].data()
2392 if opts.get('decode'):
2392 if opts.get('decode'):
2393 data = repo.wwritedata(path, data)
2393 data = repo.wwritedata(path, data)
2394 fp.write(data)
2394 fp.write(data)
2395 fp.close()
2395 fp.close()
2396
2396
2397 # Automation often uses hg cat on single files, so special case it
2397 # Automation often uses hg cat on single files, so special case it
2398 # for performance to avoid the cost of parsing the manifest.
2398 # for performance to avoid the cost of parsing the manifest.
2399 if len(matcher.files()) == 1 and not matcher.anypats():
2399 if len(matcher.files()) == 1 and not matcher.anypats():
2400 file = matcher.files()[0]
2400 file = matcher.files()[0]
2401 mf = repo.manifest
2401 mf = repo.manifest
2402 mfnode = ctx.manifestnode()
2402 mfnode = ctx.manifestnode()
2403 if mfnode and mf.find(mfnode, file)[0]:
2403 if mfnode and mf.find(mfnode, file)[0]:
2404 write(file)
2404 write(file)
2405 return 0
2405 return 0
2406
2406
2407 # Don't warn about "missing" files that are really in subrepos
2407 # Don't warn about "missing" files that are really in subrepos
2408 def badfn(path, msg):
2408 def badfn(path, msg):
2409 for subpath in ctx.substate:
2409 for subpath in ctx.substate:
2410 if path.startswith(subpath):
2410 if path.startswith(subpath):
2411 return
2411 return
2412 matcher.bad(path, msg)
2412 matcher.bad(path, msg)
2413
2413
2414 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2414 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2415 write(abs)
2415 write(abs)
2416 err = 0
2416 err = 0
2417
2417
2418 for subpath in sorted(ctx.substate):
2418 for subpath in sorted(ctx.substate):
2419 sub = ctx.sub(subpath)
2419 sub = ctx.sub(subpath)
2420 try:
2420 try:
2421 submatch = matchmod.narrowmatcher(subpath, matcher)
2421 submatch = matchmod.narrowmatcher(subpath, matcher)
2422
2422
2423 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2423 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2424 **opts):
2424 **opts):
2425 err = 0
2425 err = 0
2426 except error.RepoLookupError:
2426 except error.RepoLookupError:
2427 ui.status(_("skipping missing subrepository: %s\n")
2427 ui.status(_("skipping missing subrepository: %s\n")
2428 % os.path.join(prefix, subpath))
2428 % os.path.join(prefix, subpath))
2429
2429
2430 return err
2430 return err
2431
2431
2432 def commit(ui, repo, commitfunc, pats, opts):
2432 def commit(ui, repo, commitfunc, pats, opts):
2433 '''commit the specified files or all outstanding changes'''
2433 '''commit the specified files or all outstanding changes'''
2434 date = opts.get('date')
2434 date = opts.get('date')
2435 if date:
2435 if date:
2436 opts['date'] = util.parsedate(date)
2436 opts['date'] = util.parsedate(date)
2437 message = logmessage(ui, opts)
2437 message = logmessage(ui, opts)
2438 matcher = scmutil.match(repo[None], pats, opts)
2438 matcher = scmutil.match(repo[None], pats, opts)
2439
2439
2440 # extract addremove carefully -- this function can be called from a command
2440 # extract addremove carefully -- this function can be called from a command
2441 # that doesn't support addremove
2441 # that doesn't support addremove
2442 if opts.get('addremove'):
2442 if opts.get('addremove'):
2443 if scmutil.addremove(repo, matcher, "", opts) != 0:
2443 if scmutil.addremove(repo, matcher, "", opts) != 0:
2444 raise util.Abort(
2444 raise util.Abort(
2445 _("failed to mark all new/missing files as added/removed"))
2445 _("failed to mark all new/missing files as added/removed"))
2446
2446
2447 return commitfunc(ui, repo, message, matcher, opts)
2447 return commitfunc(ui, repo, message, matcher, opts)
2448
2448
2449 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2449 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2450 # amend will reuse the existing user if not specified, but the obsolete
2450 # amend will reuse the existing user if not specified, but the obsolete
2451 # marker creation requires that the current user's name is specified.
2451 # marker creation requires that the current user's name is specified.
2452 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2452 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2453 ui.username() # raise exception if username not set
2453 ui.username() # raise exception if username not set
2454
2454
2455 ui.note(_('amending changeset %s\n') % old)
2455 ui.note(_('amending changeset %s\n') % old)
2456 base = old.p1()
2456 base = old.p1()
2457 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2457 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2458
2458
2459 wlock = dsguard = lock = newid = None
2459 wlock = dsguard = lock = newid = None
2460 try:
2460 try:
2461 wlock = repo.wlock()
2461 wlock = repo.wlock()
2462 dsguard = dirstateguard(repo, 'amend')
2462 dsguard = dirstateguard(repo, 'amend')
2463 lock = repo.lock()
2463 lock = repo.lock()
2464 tr = repo.transaction('amend')
2464 tr = repo.transaction('amend')
2465 try:
2465 try:
2466 # See if we got a message from -m or -l, if not, open the editor
2466 # See if we got a message from -m or -l, if not, open the editor
2467 # with the message of the changeset to amend
2467 # with the message of the changeset to amend
2468 message = logmessage(ui, opts)
2468 message = logmessage(ui, opts)
2469 # ensure logfile does not conflict with later enforcement of the
2469 # ensure logfile does not conflict with later enforcement of the
2470 # message. potential logfile content has been processed by
2470 # message. potential logfile content has been processed by
2471 # `logmessage` anyway.
2471 # `logmessage` anyway.
2472 opts.pop('logfile')
2472 opts.pop('logfile')
2473 # First, do a regular commit to record all changes in the working
2473 # First, do a regular commit to record all changes in the working
2474 # directory (if there are any)
2474 # directory (if there are any)
2475 ui.callhooks = False
2475 ui.callhooks = False
2476 activebookmark = repo._activebookmark
2476 activebookmark = repo._activebookmark
2477 try:
2477 try:
2478 repo._activebookmark = None
2478 repo._activebookmark = None
2479 opts['message'] = 'temporary amend commit for %s' % old
2479 opts['message'] = 'temporary amend commit for %s' % old
2480 node = commit(ui, repo, commitfunc, pats, opts)
2480 node = commit(ui, repo, commitfunc, pats, opts)
2481 finally:
2481 finally:
2482 repo._activebookmark = activebookmark
2482 repo._activebookmark = activebookmark
2483 ui.callhooks = True
2483 ui.callhooks = True
2484 ctx = repo[node]
2484 ctx = repo[node]
2485
2485
2486 # Participating changesets:
2486 # Participating changesets:
2487 #
2487 #
2488 # node/ctx o - new (intermediate) commit that contains changes
2488 # node/ctx o - new (intermediate) commit that contains changes
2489 # | from working dir to go into amending commit
2489 # | from working dir to go into amending commit
2490 # | (or a workingctx if there were no changes)
2490 # | (or a workingctx if there were no changes)
2491 # |
2491 # |
2492 # old o - changeset to amend
2492 # old o - changeset to amend
2493 # |
2493 # |
2494 # base o - parent of amending changeset
2494 # base o - parent of amending changeset
2495
2495
2496 # Update extra dict from amended commit (e.g. to preserve graft
2496 # Update extra dict from amended commit (e.g. to preserve graft
2497 # source)
2497 # source)
2498 extra.update(old.extra())
2498 extra.update(old.extra())
2499
2499
2500 # Also update it from the intermediate commit or from the wctx
2500 # Also update it from the intermediate commit or from the wctx
2501 extra.update(ctx.extra())
2501 extra.update(ctx.extra())
2502
2502
2503 if len(old.parents()) > 1:
2503 if len(old.parents()) > 1:
2504 # ctx.files() isn't reliable for merges, so fall back to the
2504 # ctx.files() isn't reliable for merges, so fall back to the
2505 # slower repo.status() method
2505 # slower repo.status() method
2506 files = set([fn for st in repo.status(base, old)[:3]
2506 files = set([fn for st in repo.status(base, old)[:3]
2507 for fn in st])
2507 for fn in st])
2508 else:
2508 else:
2509 files = set(old.files())
2509 files = set(old.files())
2510
2510
2511 # Second, we use either the commit we just did, or if there were no
2511 # Second, we use either the commit we just did, or if there were no
2512 # changes the parent of the working directory as the version of the
2512 # changes the parent of the working directory as the version of the
2513 # files in the final amend commit
2513 # files in the final amend commit
2514 if node:
2514 if node:
2515 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2515 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2516
2516
2517 user = ctx.user()
2517 user = ctx.user()
2518 date = ctx.date()
2518 date = ctx.date()
2519 # Recompute copies (avoid recording a -> b -> a)
2519 # Recompute copies (avoid recording a -> b -> a)
2520 copied = copies.pathcopies(base, ctx)
2520 copied = copies.pathcopies(base, ctx)
2521 if old.p2:
2521 if old.p2:
2522 copied.update(copies.pathcopies(old.p2(), ctx))
2522 copied.update(copies.pathcopies(old.p2(), ctx))
2523
2523
2524 # Prune files which were reverted by the updates: if old
2524 # Prune files which were reverted by the updates: if old
2525 # introduced file X and our intermediate commit, node,
2525 # introduced file X and our intermediate commit, node,
2526 # renamed that file, then those two files are the same and
2526 # renamed that file, then those two files are the same and
2527 # we can discard X from our list of files. Likewise if X
2527 # we can discard X from our list of files. Likewise if X
2528 # was deleted, it's no longer relevant
2528 # was deleted, it's no longer relevant
2529 files.update(ctx.files())
2529 files.update(ctx.files())
2530
2530
2531 def samefile(f):
2531 def samefile(f):
2532 if f in ctx.manifest():
2532 if f in ctx.manifest():
2533 a = ctx.filectx(f)
2533 a = ctx.filectx(f)
2534 if f in base.manifest():
2534 if f in base.manifest():
2535 b = base.filectx(f)
2535 b = base.filectx(f)
2536 return (not a.cmp(b)
2536 return (not a.cmp(b)
2537 and a.flags() == b.flags())
2537 and a.flags() == b.flags())
2538 else:
2538 else:
2539 return False
2539 return False
2540 else:
2540 else:
2541 return f not in base.manifest()
2541 return f not in base.manifest()
2542 files = [f for f in files if not samefile(f)]
2542 files = [f for f in files if not samefile(f)]
2543
2543
2544 def filectxfn(repo, ctx_, path):
2544 def filectxfn(repo, ctx_, path):
2545 try:
2545 try:
2546 fctx = ctx[path]
2546 fctx = ctx[path]
2547 flags = fctx.flags()
2547 flags = fctx.flags()
2548 mctx = context.memfilectx(repo,
2548 mctx = context.memfilectx(repo,
2549 fctx.path(), fctx.data(),
2549 fctx.path(), fctx.data(),
2550 islink='l' in flags,
2550 islink='l' in flags,
2551 isexec='x' in flags,
2551 isexec='x' in flags,
2552 copied=copied.get(path))
2552 copied=copied.get(path))
2553 return mctx
2553 return mctx
2554 except KeyError:
2554 except KeyError:
2555 return None
2555 return None
2556 else:
2556 else:
2557 ui.note(_('copying changeset %s to %s\n') % (old, base))
2557 ui.note(_('copying changeset %s to %s\n') % (old, base))
2558
2558
2559 # Use version of files as in the old cset
2559 # Use version of files as in the old cset
2560 def filectxfn(repo, ctx_, path):
2560 def filectxfn(repo, ctx_, path):
2561 try:
2561 try:
2562 return old.filectx(path)
2562 return old.filectx(path)
2563 except KeyError:
2563 except KeyError:
2564 return None
2564 return None
2565
2565
2566 user = opts.get('user') or old.user()
2566 user = opts.get('user') or old.user()
2567 date = opts.get('date') or old.date()
2567 date = opts.get('date') or old.date()
2568 editform = mergeeditform(old, 'commit.amend')
2568 editform = mergeeditform(old, 'commit.amend')
2569 editor = getcommiteditor(editform=editform, **opts)
2569 editor = getcommiteditor(editform=editform, **opts)
2570 if not message:
2570 if not message:
2571 editor = getcommiteditor(edit=True, editform=editform)
2571 editor = getcommiteditor(edit=True, editform=editform)
2572 message = old.description()
2572 message = old.description()
2573
2573
2574 pureextra = extra.copy()
2574 pureextra = extra.copy()
2575 extra['amend_source'] = old.hex()
2575 extra['amend_source'] = old.hex()
2576
2576
2577 new = context.memctx(repo,
2577 new = context.memctx(repo,
2578 parents=[base.node(), old.p2().node()],
2578 parents=[base.node(), old.p2().node()],
2579 text=message,
2579 text=message,
2580 files=files,
2580 files=files,
2581 filectxfn=filectxfn,
2581 filectxfn=filectxfn,
2582 user=user,
2582 user=user,
2583 date=date,
2583 date=date,
2584 extra=extra,
2584 extra=extra,
2585 editor=editor)
2585 editor=editor)
2586
2586
2587 newdesc = changelog.stripdesc(new.description())
2587 newdesc = changelog.stripdesc(new.description())
2588 if ((not node)
2588 if ((not node)
2589 and newdesc == old.description()
2589 and newdesc == old.description()
2590 and user == old.user()
2590 and user == old.user()
2591 and date == old.date()
2591 and date == old.date()
2592 and pureextra == old.extra()):
2592 and pureextra == old.extra()):
2593 # nothing changed. continuing here would create a new node
2593 # nothing changed. continuing here would create a new node
2594 # anyway because of the amend_source noise.
2594 # anyway because of the amend_source noise.
2595 #
2595 #
2596 # This not what we expect from amend.
2596 # This not what we expect from amend.
2597 return old.node()
2597 return old.node()
2598
2598
2599 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2599 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2600 try:
2600 try:
2601 if opts.get('secret'):
2601 if opts.get('secret'):
2602 commitphase = 'secret'
2602 commitphase = 'secret'
2603 else:
2603 else:
2604 commitphase = old.phase()
2604 commitphase = old.phase()
2605 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2605 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2606 newid = repo.commitctx(new)
2606 newid = repo.commitctx(new)
2607 finally:
2607 finally:
2608 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2608 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2609 if newid != old.node():
2609 if newid != old.node():
2610 # Reroute the working copy parent to the new changeset
2610 # Reroute the working copy parent to the new changeset
2611 repo.setparents(newid, nullid)
2611 repo.setparents(newid, nullid)
2612
2612
2613 # Move bookmarks from old parent to amend commit
2613 # Move bookmarks from old parent to amend commit
2614 bms = repo.nodebookmarks(old.node())
2614 bms = repo.nodebookmarks(old.node())
2615 if bms:
2615 if bms:
2616 marks = repo._bookmarks
2616 marks = repo._bookmarks
2617 for bm in bms:
2617 for bm in bms:
2618 ui.debug('moving bookmarks %r from %s to %s\n' %
2618 ui.debug('moving bookmarks %r from %s to %s\n' %
2619 (marks, old.hex(), hex(newid)))
2619 (marks, old.hex(), hex(newid)))
2620 marks[bm] = newid
2620 marks[bm] = newid
2621 marks.recordchange(tr)
2621 marks.recordchange(tr)
2622 #commit the whole amend process
2622 #commit the whole amend process
2623 if createmarkers:
2623 if createmarkers:
2624 # mark the new changeset as successor of the rewritten one
2624 # mark the new changeset as successor of the rewritten one
2625 new = repo[newid]
2625 new = repo[newid]
2626 obs = [(old, (new,))]
2626 obs = [(old, (new,))]
2627 if node:
2627 if node:
2628 obs.append((ctx, ()))
2628 obs.append((ctx, ()))
2629
2629
2630 obsolete.createmarkers(repo, obs)
2630 obsolete.createmarkers(repo, obs)
2631 tr.close()
2631 tr.close()
2632 finally:
2632 finally:
2633 tr.release()
2633 tr.release()
2634 dsguard.close()
2634 dsguard.close()
2635 if not createmarkers and newid != old.node():
2635 if not createmarkers and newid != old.node():
2636 # Strip the intermediate commit (if there was one) and the amended
2636 # Strip the intermediate commit (if there was one) and the amended
2637 # commit
2637 # commit
2638 if node:
2638 if node:
2639 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2639 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2640 ui.note(_('stripping amended changeset %s\n') % old)
2640 ui.note(_('stripping amended changeset %s\n') % old)
2641 repair.strip(ui, repo, old.node(), topic='amend-backup')
2641 repair.strip(ui, repo, old.node(), topic='amend-backup')
2642 finally:
2642 finally:
2643 lockmod.release(lock, dsguard, wlock)
2643 lockmod.release(lock, dsguard, wlock)
2644 return newid
2644 return newid
2645
2645
2646 def commiteditor(repo, ctx, subs, editform=''):
2646 def commiteditor(repo, ctx, subs, editform=''):
2647 if ctx.description():
2647 if ctx.description():
2648 return ctx.description()
2648 return ctx.description()
2649 return commitforceeditor(repo, ctx, subs, editform=editform)
2649 return commitforceeditor(repo, ctx, subs, editform=editform)
2650
2650
2651 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2651 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2652 editform=''):
2652 editform=''):
2653 if not extramsg:
2653 if not extramsg:
2654 extramsg = _("Leave message empty to abort commit.")
2654 extramsg = _("Leave message empty to abort commit.")
2655
2655
2656 forms = [e for e in editform.split('.') if e]
2656 forms = [e for e in editform.split('.') if e]
2657 forms.insert(0, 'changeset')
2657 forms.insert(0, 'changeset')
2658 while forms:
2658 while forms:
2659 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2659 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2660 if tmpl:
2660 if tmpl:
2661 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2661 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2662 break
2662 break
2663 forms.pop()
2663 forms.pop()
2664 else:
2664 else:
2665 committext = buildcommittext(repo, ctx, subs, extramsg)
2665 committext = buildcommittext(repo, ctx, subs, extramsg)
2666
2666
2667 # run editor in the repository root
2667 # run editor in the repository root
2668 olddir = os.getcwd()
2668 olddir = os.getcwd()
2669 os.chdir(repo.root)
2669 os.chdir(repo.root)
2670 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2670 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2671 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2671 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2672 os.chdir(olddir)
2672 os.chdir(olddir)
2673
2673
2674 if finishdesc:
2674 if finishdesc:
2675 text = finishdesc(text)
2675 text = finishdesc(text)
2676 if not text.strip():
2676 if not text.strip():
2677 raise util.Abort(_("empty commit message"))
2677 raise util.Abort(_("empty commit message"))
2678
2678
2679 return text
2679 return text
2680
2680
2681 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2681 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2682 ui = repo.ui
2682 ui = repo.ui
2683 tmpl, mapfile = gettemplate(ui, tmpl, None)
2683 tmpl, mapfile = gettemplate(ui, tmpl, None)
2684
2684
2685 try:
2685 try:
2686 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2686 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2687 except SyntaxError as inst:
2687 except SyntaxError as inst:
2688 raise util.Abort(inst.args[0])
2688 raise util.Abort(inst.args[0])
2689
2689
2690 for k, v in repo.ui.configitems('committemplate'):
2690 for k, v in repo.ui.configitems('committemplate'):
2691 if k != 'changeset':
2691 if k != 'changeset':
2692 t.t.cache[k] = v
2692 t.t.cache[k] = v
2693
2693
2694 if not extramsg:
2694 if not extramsg:
2695 extramsg = '' # ensure that extramsg is string
2695 extramsg = '' # ensure that extramsg is string
2696
2696
2697 ui.pushbuffer()
2697 ui.pushbuffer()
2698 t.show(ctx, extramsg=extramsg)
2698 t.show(ctx, extramsg=extramsg)
2699 return ui.popbuffer()
2699 return ui.popbuffer()
2700
2700
2701 def buildcommittext(repo, ctx, subs, extramsg):
2701 def buildcommittext(repo, ctx, subs, extramsg):
2702 edittext = []
2702 edittext = []
2703 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2703 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2704 if ctx.description():
2704 if ctx.description():
2705 edittext.append(ctx.description())
2705 edittext.append(ctx.description())
2706 edittext.append("")
2706 edittext.append("")
2707 edittext.append("") # Empty line between message and comments.
2707 edittext.append("") # Empty line between message and comments.
2708 edittext.append(_("HG: Enter commit message."
2708 edittext.append(_("HG: Enter commit message."
2709 " Lines beginning with 'HG:' are removed."))
2709 " Lines beginning with 'HG:' are removed."))
2710 edittext.append("HG: %s" % extramsg)
2710 edittext.append("HG: %s" % extramsg)
2711 edittext.append("HG: --")
2711 edittext.append("HG: --")
2712 edittext.append(_("HG: user: %s") % ctx.user())
2712 edittext.append(_("HG: user: %s") % ctx.user())
2713 if ctx.p2():
2713 if ctx.p2():
2714 edittext.append(_("HG: branch merge"))
2714 edittext.append(_("HG: branch merge"))
2715 if ctx.branch():
2715 if ctx.branch():
2716 edittext.append(_("HG: branch '%s'") % ctx.branch())
2716 edittext.append(_("HG: branch '%s'") % ctx.branch())
2717 if bookmarks.isactivewdirparent(repo):
2717 if bookmarks.isactivewdirparent(repo):
2718 edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
2718 edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
2719 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2719 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2720 edittext.extend([_("HG: added %s") % f for f in added])
2720 edittext.extend([_("HG: added %s") % f for f in added])
2721 edittext.extend([_("HG: changed %s") % f for f in modified])
2721 edittext.extend([_("HG: changed %s") % f for f in modified])
2722 edittext.extend([_("HG: removed %s") % f for f in removed])
2722 edittext.extend([_("HG: removed %s") % f for f in removed])
2723 if not added and not modified and not removed:
2723 if not added and not modified and not removed:
2724 edittext.append(_("HG: no files changed"))
2724 edittext.append(_("HG: no files changed"))
2725 edittext.append("")
2725 edittext.append("")
2726
2726
2727 return "\n".join(edittext)
2727 return "\n".join(edittext)
2728
2728
2729 def commitstatus(repo, node, branch, bheads=None, opts={}):
2729 def commitstatus(repo, node, branch, bheads=None, opts={}):
2730 ctx = repo[node]
2730 ctx = repo[node]
2731 parents = ctx.parents()
2731 parents = ctx.parents()
2732
2732
2733 if (not opts.get('amend') and bheads and node not in bheads and not
2733 if (not opts.get('amend') and bheads and node not in bheads and not
2734 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2734 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2735 repo.ui.status(_('created new head\n'))
2735 repo.ui.status(_('created new head\n'))
2736 # The message is not printed for initial roots. For the other
2736 # The message is not printed for initial roots. For the other
2737 # changesets, it is printed in the following situations:
2737 # changesets, it is printed in the following situations:
2738 #
2738 #
2739 # Par column: for the 2 parents with ...
2739 # Par column: for the 2 parents with ...
2740 # N: null or no parent
2740 # N: null or no parent
2741 # B: parent is on another named branch
2741 # B: parent is on another named branch
2742 # C: parent is a regular non head changeset
2742 # C: parent is a regular non head changeset
2743 # H: parent was a branch head of the current branch
2743 # H: parent was a branch head of the current branch
2744 # Msg column: whether we print "created new head" message
2744 # Msg column: whether we print "created new head" message
2745 # In the following, it is assumed that there already exists some
2745 # In the following, it is assumed that there already exists some
2746 # initial branch heads of the current branch, otherwise nothing is
2746 # initial branch heads of the current branch, otherwise nothing is
2747 # printed anyway.
2747 # printed anyway.
2748 #
2748 #
2749 # Par Msg Comment
2749 # Par Msg Comment
2750 # N N y additional topo root
2750 # N N y additional topo root
2751 #
2751 #
2752 # B N y additional branch root
2752 # B N y additional branch root
2753 # C N y additional topo head
2753 # C N y additional topo head
2754 # H N n usual case
2754 # H N n usual case
2755 #
2755 #
2756 # B B y weird additional branch root
2756 # B B y weird additional branch root
2757 # C B y branch merge
2757 # C B y branch merge
2758 # H B n merge with named branch
2758 # H B n merge with named branch
2759 #
2759 #
2760 # C C y additional head from merge
2760 # C C y additional head from merge
2761 # C H n merge with a head
2761 # C H n merge with a head
2762 #
2762 #
2763 # H H n head merge: head count decreases
2763 # H H n head merge: head count decreases
2764
2764
2765 if not opts.get('close_branch'):
2765 if not opts.get('close_branch'):
2766 for r in parents:
2766 for r in parents:
2767 if r.closesbranch() and r.branch() == branch:
2767 if r.closesbranch() and r.branch() == branch:
2768 repo.ui.status(_('reopening closed branch head %d\n') % r)
2768 repo.ui.status(_('reopening closed branch head %d\n') % r)
2769
2769
2770 if repo.ui.debugflag:
2770 if repo.ui.debugflag:
2771 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2771 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2772 elif repo.ui.verbose:
2772 elif repo.ui.verbose:
2773 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2773 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2774
2774
2775 def revert(ui, repo, ctx, parents, *pats, **opts):
2775 def revert(ui, repo, ctx, parents, *pats, **opts):
2776 parent, p2 = parents
2776 parent, p2 = parents
2777 node = ctx.node()
2777 node = ctx.node()
2778
2778
2779 mf = ctx.manifest()
2779 mf = ctx.manifest()
2780 if node == p2:
2780 if node == p2:
2781 parent = p2
2781 parent = p2
2782 if node == parent:
2782 if node == parent:
2783 pmf = mf
2783 pmf = mf
2784 else:
2784 else:
2785 pmf = None
2785 pmf = None
2786
2786
2787 # need all matching names in dirstate and manifest of target rev,
2787 # need all matching names in dirstate and manifest of target rev,
2788 # so have to walk both. do not print errors if files exist in one
2788 # so have to walk both. do not print errors if files exist in one
2789 # but not other. in both cases, filesets should be evaluated against
2789 # but not other. in both cases, filesets should be evaluated against
2790 # workingctx to get consistent result (issue4497). this means 'set:**'
2790 # workingctx to get consistent result (issue4497). this means 'set:**'
2791 # cannot be used to select missing files from target rev.
2791 # cannot be used to select missing files from target rev.
2792
2792
2793 # `names` is a mapping for all elements in working copy and target revision
2793 # `names` is a mapping for all elements in working copy and target revision
2794 # The mapping is in the form:
2794 # The mapping is in the form:
2795 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2795 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2796 names = {}
2796 names = {}
2797
2797
2798 wlock = repo.wlock()
2798 wlock = repo.wlock()
2799 try:
2799 try:
2800 ## filling of the `names` mapping
2800 ## filling of the `names` mapping
2801 # walk dirstate to fill `names`
2801 # walk dirstate to fill `names`
2802
2802
2803 interactive = opts.get('interactive', False)
2803 interactive = opts.get('interactive', False)
2804 wctx = repo[None]
2804 wctx = repo[None]
2805 m = scmutil.match(wctx, pats, opts)
2805 m = scmutil.match(wctx, pats, opts)
2806
2806
2807 # we'll need this later
2807 # we'll need this later
2808 targetsubs = sorted(s for s in wctx.substate if m(s))
2808 targetsubs = sorted(s for s in wctx.substate if m(s))
2809
2809
2810 if not m.always():
2810 if not m.always():
2811 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2811 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2812 names[abs] = m.rel(abs), m.exact(abs)
2812 names[abs] = m.rel(abs), m.exact(abs)
2813
2813
2814 # walk target manifest to fill `names`
2814 # walk target manifest to fill `names`
2815
2815
2816 def badfn(path, msg):
2816 def badfn(path, msg):
2817 if path in names:
2817 if path in names:
2818 return
2818 return
2819 if path in ctx.substate:
2819 if path in ctx.substate:
2820 return
2820 return
2821 path_ = path + '/'
2821 path_ = path + '/'
2822 for f in names:
2822 for f in names:
2823 if f.startswith(path_):
2823 if f.startswith(path_):
2824 return
2824 return
2825 ui.warn("%s: %s\n" % (m.rel(path), msg))
2825 ui.warn("%s: %s\n" % (m.rel(path), msg))
2826
2826
2827 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2827 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2828 if abs not in names:
2828 if abs not in names:
2829 names[abs] = m.rel(abs), m.exact(abs)
2829 names[abs] = m.rel(abs), m.exact(abs)
2830
2830
2831 # Find status of all file in `names`.
2831 # Find status of all file in `names`.
2832 m = scmutil.matchfiles(repo, names)
2832 m = scmutil.matchfiles(repo, names)
2833
2833
2834 changes = repo.status(node1=node, match=m,
2834 changes = repo.status(node1=node, match=m,
2835 unknown=True, ignored=True, clean=True)
2835 unknown=True, ignored=True, clean=True)
2836 else:
2836 else:
2837 changes = repo.status(node1=node, match=m)
2837 changes = repo.status(node1=node, match=m)
2838 for kind in changes:
2838 for kind in changes:
2839 for abs in kind:
2839 for abs in kind:
2840 names[abs] = m.rel(abs), m.exact(abs)
2840 names[abs] = m.rel(abs), m.exact(abs)
2841
2841
2842 m = scmutil.matchfiles(repo, names)
2842 m = scmutil.matchfiles(repo, names)
2843
2843
2844 modified = set(changes.modified)
2844 modified = set(changes.modified)
2845 added = set(changes.added)
2845 added = set(changes.added)
2846 removed = set(changes.removed)
2846 removed = set(changes.removed)
2847 _deleted = set(changes.deleted)
2847 _deleted = set(changes.deleted)
2848 unknown = set(changes.unknown)
2848 unknown = set(changes.unknown)
2849 unknown.update(changes.ignored)
2849 unknown.update(changes.ignored)
2850 clean = set(changes.clean)
2850 clean = set(changes.clean)
2851 modadded = set()
2851 modadded = set()
2852
2852
2853 # split between files known in target manifest and the others
2853 # split between files known in target manifest and the others
2854 smf = set(mf)
2854 smf = set(mf)
2855
2855
2856 # determine the exact nature of the deleted changesets
2856 # determine the exact nature of the deleted changesets
2857 deladded = _deleted - smf
2857 deladded = _deleted - smf
2858 deleted = _deleted - deladded
2858 deleted = _deleted - deladded
2859
2859
2860 # We need to account for the state of the file in the dirstate,
2860 # We need to account for the state of the file in the dirstate,
2861 # even when we revert against something else than parent. This will
2861 # even when we revert against something else than parent. This will
2862 # slightly alter the behavior of revert (doing back up or not, delete
2862 # slightly alter the behavior of revert (doing back up or not, delete
2863 # or just forget etc).
2863 # or just forget etc).
2864 if parent == node:
2864 if parent == node:
2865 dsmodified = modified
2865 dsmodified = modified
2866 dsadded = added
2866 dsadded = added
2867 dsremoved = removed
2867 dsremoved = removed
2868 # store all local modifications, useful later for rename detection
2868 # store all local modifications, useful later for rename detection
2869 localchanges = dsmodified | dsadded
2869 localchanges = dsmodified | dsadded
2870 modified, added, removed = set(), set(), set()
2870 modified, added, removed = set(), set(), set()
2871 else:
2871 else:
2872 changes = repo.status(node1=parent, match=m)
2872 changes = repo.status(node1=parent, match=m)
2873 dsmodified = set(changes.modified)
2873 dsmodified = set(changes.modified)
2874 dsadded = set(changes.added)
2874 dsadded = set(changes.added)
2875 dsremoved = set(changes.removed)
2875 dsremoved = set(changes.removed)
2876 # store all local modifications, useful later for rename detection
2876 # store all local modifications, useful later for rename detection
2877 localchanges = dsmodified | dsadded
2877 localchanges = dsmodified | dsadded
2878
2878
2879 # only take into account for removes between wc and target
2879 # only take into account for removes between wc and target
2880 clean |= dsremoved - removed
2880 clean |= dsremoved - removed
2881 dsremoved &= removed
2881 dsremoved &= removed
2882 # distinct between dirstate remove and other
2882 # distinct between dirstate remove and other
2883 removed -= dsremoved
2883 removed -= dsremoved
2884
2884
2885 modadded = added & dsmodified
2885 modadded = added & dsmodified
2886 added -= modadded
2886 added -= modadded
2887
2887
2888 # tell newly modified apart.
2888 # tell newly modified apart.
2889 dsmodified &= modified
2889 dsmodified &= modified
2890 dsmodified |= modified & dsadded # dirstate added may needs backup
2890 dsmodified |= modified & dsadded # dirstate added may needs backup
2891 modified -= dsmodified
2891 modified -= dsmodified
2892
2892
2893 # We need to wait for some post-processing to update this set
2893 # We need to wait for some post-processing to update this set
2894 # before making the distinction. The dirstate will be used for
2894 # before making the distinction. The dirstate will be used for
2895 # that purpose.
2895 # that purpose.
2896 dsadded = added
2896 dsadded = added
2897
2897
2898 # in case of merge, files that are actually added can be reported as
2898 # in case of merge, files that are actually added can be reported as
2899 # modified, we need to post process the result
2899 # modified, we need to post process the result
2900 if p2 != nullid:
2900 if p2 != nullid:
2901 if pmf is None:
2901 if pmf is None:
2902 # only need parent manifest in the merge case,
2902 # only need parent manifest in the merge case,
2903 # so do not read by default
2903 # so do not read by default
2904 pmf = repo[parent].manifest()
2904 pmf = repo[parent].manifest()
2905 mergeadd = dsmodified - set(pmf)
2905 mergeadd = dsmodified - set(pmf)
2906 dsadded |= mergeadd
2906 dsadded |= mergeadd
2907 dsmodified -= mergeadd
2907 dsmodified -= mergeadd
2908
2908
2909 # if f is a rename, update `names` to also revert the source
2909 # if f is a rename, update `names` to also revert the source
2910 cwd = repo.getcwd()
2910 cwd = repo.getcwd()
2911 for f in localchanges:
2911 for f in localchanges:
2912 src = repo.dirstate.copied(f)
2912 src = repo.dirstate.copied(f)
2913 # XXX should we check for rename down to target node?
2913 # XXX should we check for rename down to target node?
2914 if src and src not in names and repo.dirstate[src] == 'r':
2914 if src and src not in names and repo.dirstate[src] == 'r':
2915 dsremoved.add(src)
2915 dsremoved.add(src)
2916 names[src] = (repo.pathto(src, cwd), True)
2916 names[src] = (repo.pathto(src, cwd), True)
2917
2917
2918 # distinguish between file to forget and the other
2918 # distinguish between file to forget and the other
2919 added = set()
2919 added = set()
2920 for abs in dsadded:
2920 for abs in dsadded:
2921 if repo.dirstate[abs] != 'a':
2921 if repo.dirstate[abs] != 'a':
2922 added.add(abs)
2922 added.add(abs)
2923 dsadded -= added
2923 dsadded -= added
2924
2924
2925 for abs in deladded:
2925 for abs in deladded:
2926 if repo.dirstate[abs] == 'a':
2926 if repo.dirstate[abs] == 'a':
2927 dsadded.add(abs)
2927 dsadded.add(abs)
2928 deladded -= dsadded
2928 deladded -= dsadded
2929
2929
2930 # For files marked as removed, we check if an unknown file is present at
2930 # For files marked as removed, we check if an unknown file is present at
2931 # the same path. If a such file exists it may need to be backed up.
2931 # the same path. If a such file exists it may need to be backed up.
2932 # Making the distinction at this stage helps have simpler backup
2932 # Making the distinction at this stage helps have simpler backup
2933 # logic.
2933 # logic.
2934 removunk = set()
2934 removunk = set()
2935 for abs in removed:
2935 for abs in removed:
2936 target = repo.wjoin(abs)
2936 target = repo.wjoin(abs)
2937 if os.path.lexists(target):
2937 if os.path.lexists(target):
2938 removunk.add(abs)
2938 removunk.add(abs)
2939 removed -= removunk
2939 removed -= removunk
2940
2940
2941 dsremovunk = set()
2941 dsremovunk = set()
2942 for abs in dsremoved:
2942 for abs in dsremoved:
2943 target = repo.wjoin(abs)
2943 target = repo.wjoin(abs)
2944 if os.path.lexists(target):
2944 if os.path.lexists(target):
2945 dsremovunk.add(abs)
2945 dsremovunk.add(abs)
2946 dsremoved -= dsremovunk
2946 dsremoved -= dsremovunk
2947
2947
2948 # action to be actually performed by revert
2948 # action to be actually performed by revert
2949 # (<list of file>, message>) tuple
2949 # (<list of file>, message>) tuple
2950 actions = {'revert': ([], _('reverting %s\n')),
2950 actions = {'revert': ([], _('reverting %s\n')),
2951 'add': ([], _('adding %s\n')),
2951 'add': ([], _('adding %s\n')),
2952 'remove': ([], _('removing %s\n')),
2952 'remove': ([], _('removing %s\n')),
2953 'drop': ([], _('removing %s\n')),
2953 'drop': ([], _('removing %s\n')),
2954 'forget': ([], _('forgetting %s\n')),
2954 'forget': ([], _('forgetting %s\n')),
2955 'undelete': ([], _('undeleting %s\n')),
2955 'undelete': ([], _('undeleting %s\n')),
2956 'noop': (None, _('no changes needed to %s\n')),
2956 'noop': (None, _('no changes needed to %s\n')),
2957 'unknown': (None, _('file not managed: %s\n')),
2957 'unknown': (None, _('file not managed: %s\n')),
2958 }
2958 }
2959
2959
2960 # "constant" that convey the backup strategy.
2960 # "constant" that convey the backup strategy.
2961 # All set to `discard` if `no-backup` is set do avoid checking
2961 # All set to `discard` if `no-backup` is set do avoid checking
2962 # no_backup lower in the code.
2962 # no_backup lower in the code.
2963 # These values are ordered for comparison purposes
2963 # These values are ordered for comparison purposes
2964 backup = 2 # unconditionally do backup
2964 backup = 2 # unconditionally do backup
2965 check = 1 # check if the existing file differs from target
2965 check = 1 # check if the existing file differs from target
2966 discard = 0 # never do backup
2966 discard = 0 # never do backup
2967 if opts.get('no_backup'):
2967 if opts.get('no_backup'):
2968 backup = check = discard
2968 backup = check = discard
2969
2969
2970 backupanddel = actions['remove']
2970 backupanddel = actions['remove']
2971 if not opts.get('no_backup'):
2971 if not opts.get('no_backup'):
2972 backupanddel = actions['drop']
2972 backupanddel = actions['drop']
2973
2973
2974 disptable = (
2974 disptable = (
2975 # dispatch table:
2975 # dispatch table:
2976 # file state
2976 # file state
2977 # action
2977 # action
2978 # make backup
2978 # make backup
2979
2979
2980 ## Sets that results that will change file on disk
2980 ## Sets that results that will change file on disk
2981 # Modified compared to target, no local change
2981 # Modified compared to target, no local change
2982 (modified, actions['revert'], discard),
2982 (modified, actions['revert'], discard),
2983 # Modified compared to target, but local file is deleted
2983 # Modified compared to target, but local file is deleted
2984 (deleted, actions['revert'], discard),
2984 (deleted, actions['revert'], discard),
2985 # Modified compared to target, local change
2985 # Modified compared to target, local change
2986 (dsmodified, actions['revert'], backup),
2986 (dsmodified, actions['revert'], backup),
2987 # Added since target
2987 # Added since target
2988 (added, actions['remove'], discard),
2988 (added, actions['remove'], discard),
2989 # Added in working directory
2989 # Added in working directory
2990 (dsadded, actions['forget'], discard),
2990 (dsadded, actions['forget'], discard),
2991 # Added since target, have local modification
2991 # Added since target, have local modification
2992 (modadded, backupanddel, backup),
2992 (modadded, backupanddel, backup),
2993 # Added since target but file is missing in working directory
2993 # Added since target but file is missing in working directory
2994 (deladded, actions['drop'], discard),
2994 (deladded, actions['drop'], discard),
2995 # Removed since target, before working copy parent
2995 # Removed since target, before working copy parent
2996 (removed, actions['add'], discard),
2996 (removed, actions['add'], discard),
2997 # Same as `removed` but an unknown file exists at the same path
2997 # Same as `removed` but an unknown file exists at the same path
2998 (removunk, actions['add'], check),
2998 (removunk, actions['add'], check),
2999 # Removed since targe, marked as such in working copy parent
2999 # Removed since targe, marked as such in working copy parent
3000 (dsremoved, actions['undelete'], discard),
3000 (dsremoved, actions['undelete'], discard),
3001 # Same as `dsremoved` but an unknown file exists at the same path
3001 # Same as `dsremoved` but an unknown file exists at the same path
3002 (dsremovunk, actions['undelete'], check),
3002 (dsremovunk, actions['undelete'], check),
3003 ## the following sets does not result in any file changes
3003 ## the following sets does not result in any file changes
3004 # File with no modification
3004 # File with no modification
3005 (clean, actions['noop'], discard),
3005 (clean, actions['noop'], discard),
3006 # Existing file, not tracked anywhere
3006 # Existing file, not tracked anywhere
3007 (unknown, actions['unknown'], discard),
3007 (unknown, actions['unknown'], discard),
3008 )
3008 )
3009
3009
3010 for abs, (rel, exact) in sorted(names.items()):
3010 for abs, (rel, exact) in sorted(names.items()):
3011 # target file to be touch on disk (relative to cwd)
3011 # target file to be touch on disk (relative to cwd)
3012 target = repo.wjoin(abs)
3012 target = repo.wjoin(abs)
3013 # search the entry in the dispatch table.
3013 # search the entry in the dispatch table.
3014 # if the file is in any of these sets, it was touched in the working
3014 # if the file is in any of these sets, it was touched in the working
3015 # directory parent and we are sure it needs to be reverted.
3015 # directory parent and we are sure it needs to be reverted.
3016 for table, (xlist, msg), dobackup in disptable:
3016 for table, (xlist, msg), dobackup in disptable:
3017 if abs not in table:
3017 if abs not in table:
3018 continue
3018 continue
3019 if xlist is not None:
3019 if xlist is not None:
3020 xlist.append(abs)
3020 xlist.append(abs)
3021 if dobackup and (backup <= dobackup
3021 if dobackup and (backup <= dobackup
3022 or wctx[abs].cmp(ctx[abs])):
3022 or wctx[abs].cmp(ctx[abs])):
3023 bakname = "%s.orig" % rel
3023 bakname = "%s.orig" % rel
3024 ui.note(_('saving current version of %s as %s\n') %
3024 ui.note(_('saving current version of %s as %s\n') %
3025 (rel, bakname))
3025 (rel, bakname))
3026 if not opts.get('dry_run'):
3026 if not opts.get('dry_run'):
3027 if interactive:
3027 if interactive:
3028 util.copyfile(target, bakname)
3028 util.copyfile(target, bakname)
3029 else:
3029 else:
3030 util.rename(target, bakname)
3030 util.rename(target, bakname)
3031 if ui.verbose or not exact:
3031 if ui.verbose or not exact:
3032 if not isinstance(msg, basestring):
3032 if not isinstance(msg, basestring):
3033 msg = msg(abs)
3033 msg = msg(abs)
3034 ui.status(msg % rel)
3034 ui.status(msg % rel)
3035 elif exact:
3035 elif exact:
3036 ui.warn(msg % rel)
3036 ui.warn(msg % rel)
3037 break
3037 break
3038
3038
3039 if not opts.get('dry_run'):
3039 if not opts.get('dry_run'):
3040 needdata = ('revert', 'add', 'undelete')
3040 needdata = ('revert', 'add', 'undelete')
3041 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3041 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3042 _performrevert(repo, parents, ctx, actions, interactive)
3042 _performrevert(repo, parents, ctx, actions, interactive)
3043
3043
3044 if targetsubs:
3044 if targetsubs:
3045 # Revert the subrepos on the revert list
3045 # Revert the subrepos on the revert list
3046 for sub in targetsubs:
3046 for sub in targetsubs:
3047 try:
3047 try:
3048 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3048 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3049 except KeyError:
3049 except KeyError:
3050 raise util.Abort("subrepository '%s' does not exist in %s!"
3050 raise util.Abort("subrepository '%s' does not exist in %s!"
3051 % (sub, short(ctx.node())))
3051 % (sub, short(ctx.node())))
3052 finally:
3052 finally:
3053 wlock.release()
3053 wlock.release()
3054
3054
3055 def _revertprefetch(repo, ctx, *files):
3055 def _revertprefetch(repo, ctx, *files):
3056 """Let extension changing the storage layer prefetch content"""
3056 """Let extension changing the storage layer prefetch content"""
3057 pass
3057 pass
3058
3058
3059 def _performrevert(repo, parents, ctx, actions, interactive=False):
3059 def _performrevert(repo, parents, ctx, actions, interactive=False):
3060 """function that actually perform all the actions computed for revert
3060 """function that actually perform all the actions computed for revert
3061
3061
3062 This is an independent function to let extension to plug in and react to
3062 This is an independent function to let extension to plug in and react to
3063 the imminent revert.
3063 the imminent revert.
3064
3064
3065 Make sure you have the working directory locked when calling this function.
3065 Make sure you have the working directory locked when calling this function.
3066 """
3066 """
3067 parent, p2 = parents
3067 parent, p2 = parents
3068 node = ctx.node()
3068 node = ctx.node()
3069 def checkout(f):
3069 def checkout(f):
3070 fc = ctx[f]
3070 fc = ctx[f]
3071 return repo.wwrite(f, fc.data(), fc.flags())
3071 return repo.wwrite(f, fc.data(), fc.flags())
3072
3072
3073 audit_path = pathutil.pathauditor(repo.root)
3073 audit_path = pathutil.pathauditor(repo.root)
3074 for f in actions['forget'][0]:
3074 for f in actions['forget'][0]:
3075 repo.dirstate.drop(f)
3075 repo.dirstate.drop(f)
3076 for f in actions['remove'][0]:
3076 for f in actions['remove'][0]:
3077 audit_path(f)
3077 audit_path(f)
3078 try:
3078 try:
3079 util.unlinkpath(repo.wjoin(f))
3079 util.unlinkpath(repo.wjoin(f))
3080 except OSError:
3080 except OSError:
3081 pass
3081 pass
3082 repo.dirstate.remove(f)
3082 repo.dirstate.remove(f)
3083 for f in actions['drop'][0]:
3083 for f in actions['drop'][0]:
3084 audit_path(f)
3084 audit_path(f)
3085 repo.dirstate.remove(f)
3085 repo.dirstate.remove(f)
3086
3086
3087 normal = None
3087 normal = None
3088 if node == parent:
3088 if node == parent:
3089 # We're reverting to our parent. If possible, we'd like status
3089 # We're reverting to our parent. If possible, we'd like status
3090 # to report the file as clean. We have to use normallookup for
3090 # to report the file as clean. We have to use normallookup for
3091 # merges to avoid losing information about merged/dirty files.
3091 # merges to avoid losing information about merged/dirty files.
3092 if p2 != nullid:
3092 if p2 != nullid:
3093 normal = repo.dirstate.normallookup
3093 normal = repo.dirstate.normallookup
3094 else:
3094 else:
3095 normal = repo.dirstate.normal
3095 normal = repo.dirstate.normal
3096
3096
3097 newlyaddedandmodifiedfiles = set()
3097 newlyaddedandmodifiedfiles = set()
3098 if interactive:
3098 if interactive:
3099 # Prompt the user for changes to revert
3099 # Prompt the user for changes to revert
3100 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3100 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3101 m = scmutil.match(ctx, torevert, {})
3101 m = scmutil.match(ctx, torevert, {})
3102 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3102 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3103 diffopts.nodates = True
3103 diffopts.nodates = True
3104 diffopts.git = True
3104 diffopts.git = True
3105 reversehunks = repo.ui.configbool('experimental',
3105 reversehunks = repo.ui.configbool('experimental',
3106 'revertalternateinteractivemode',
3106 'revertalternateinteractivemode',
3107 True)
3107 True)
3108 if reversehunks:
3108 if reversehunks:
3109 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3109 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3110 else:
3110 else:
3111 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3111 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3112 originalchunks = patch.parsepatch(diff)
3112 originalchunks = patch.parsepatch(diff)
3113
3113
3114 try:
3114 try:
3115
3115
3116 chunks = recordfilter(repo.ui, originalchunks)
3116 chunks = recordfilter(repo.ui, originalchunks)
3117 if reversehunks:
3117 if reversehunks:
3118 chunks = patch.reversehunks(chunks)
3118 chunks = patch.reversehunks(chunks)
3119
3119
3120 except patch.PatchError as err:
3120 except patch.PatchError as err:
3121 raise util.Abort(_('error parsing patch: %s') % err)
3121 raise util.Abort(_('error parsing patch: %s') % err)
3122
3122
3123 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3123 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3124 # Apply changes
3124 # Apply changes
3125 fp = cStringIO.StringIO()
3125 fp = cStringIO.StringIO()
3126 for c in chunks:
3126 for c in chunks:
3127 c.write(fp)
3127 c.write(fp)
3128 dopatch = fp.tell()
3128 dopatch = fp.tell()
3129 fp.seek(0)
3129 fp.seek(0)
3130 if dopatch:
3130 if dopatch:
3131 try:
3131 try:
3132 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3132 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3133 except patch.PatchError as err:
3133 except patch.PatchError as err:
3134 raise util.Abort(str(err))
3134 raise util.Abort(str(err))
3135 del fp
3135 del fp
3136 else:
3136 else:
3137 for f in actions['revert'][0]:
3137 for f in actions['revert'][0]:
3138 wsize = checkout(f)
3138 wsize = checkout(f)
3139 if normal:
3139 if normal:
3140 normal(f)
3140 normal(f)
3141 elif wsize == repo.dirstate._map[f][2]:
3141 elif wsize == repo.dirstate._map[f][2]:
3142 # changes may be overlooked without normallookup,
3142 # changes may be overlooked without normallookup,
3143 # if size isn't changed at reverting
3143 # if size isn't changed at reverting
3144 repo.dirstate.normallookup(f)
3144 repo.dirstate.normallookup(f)
3145
3145
3146 for f in actions['add'][0]:
3146 for f in actions['add'][0]:
3147 # Don't checkout modified files, they are already created by the diff
3147 # Don't checkout modified files, they are already created by the diff
3148 if f not in newlyaddedandmodifiedfiles:
3148 if f not in newlyaddedandmodifiedfiles:
3149 checkout(f)
3149 checkout(f)
3150 repo.dirstate.add(f)
3150 repo.dirstate.add(f)
3151
3151
3152 normal = repo.dirstate.normallookup
3152 normal = repo.dirstate.normallookup
3153 if node == parent and p2 == nullid:
3153 if node == parent and p2 == nullid:
3154 normal = repo.dirstate.normal
3154 normal = repo.dirstate.normal
3155 for f in actions['undelete'][0]:
3155 for f in actions['undelete'][0]:
3156 checkout(f)
3156 checkout(f)
3157 normal(f)
3157 normal(f)
3158
3158
3159 copied = copies.pathcopies(repo[parent], ctx)
3159 copied = copies.pathcopies(repo[parent], ctx)
3160
3160
3161 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3161 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3162 if f in copied:
3162 if f in copied:
3163 repo.dirstate.copy(copied[f], f)
3163 repo.dirstate.copy(copied[f], f)
3164
3164
3165 def command(table):
3165 def command(table):
3166 """Returns a function object to be used as a decorator for making commands.
3166 """Returns a function object to be used as a decorator for making commands.
3167
3167
3168 This function receives a command table as its argument. The table should
3168 This function receives a command table as its argument. The table should
3169 be a dict.
3169 be a dict.
3170
3170
3171 The returned function can be used as a decorator for adding commands
3171 The returned function can be used as a decorator for adding commands
3172 to that command table. This function accepts multiple arguments to define
3172 to that command table. This function accepts multiple arguments to define
3173 a command.
3173 a command.
3174
3174
3175 The first argument is the command name.
3175 The first argument is the command name.
3176
3176
3177 The options argument is an iterable of tuples defining command arguments.
3177 The options argument is an iterable of tuples defining command arguments.
3178 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3178 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3179
3179
3180 The synopsis argument defines a short, one line summary of how to use the
3180 The synopsis argument defines a short, one line summary of how to use the
3181 command. This shows up in the help output.
3181 command. This shows up in the help output.
3182
3182
3183 The norepo argument defines whether the command does not require a
3183 The norepo argument defines whether the command does not require a
3184 local repository. Most commands operate against a repository, thus the
3184 local repository. Most commands operate against a repository, thus the
3185 default is False.
3185 default is False.
3186
3186
3187 The optionalrepo argument defines whether the command optionally requires
3187 The optionalrepo argument defines whether the command optionally requires
3188 a local repository.
3188 a local repository.
3189
3189
3190 The inferrepo argument defines whether to try to find a repository from the
3190 The inferrepo argument defines whether to try to find a repository from the
3191 command line arguments. If True, arguments will be examined for potential
3191 command line arguments. If True, arguments will be examined for potential
3192 repository locations. See ``findrepo()``. If a repository is found, it
3192 repository locations. See ``findrepo()``. If a repository is found, it
3193 will be used.
3193 will be used.
3194 """
3194 """
3195 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3195 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3196 inferrepo=False):
3196 inferrepo=False):
3197 def decorator(func):
3197 def decorator(func):
3198 if synopsis:
3198 if synopsis:
3199 table[name] = func, list(options), synopsis
3199 table[name] = func, list(options), synopsis
3200 else:
3200 else:
3201 table[name] = func, list(options)
3201 table[name] = func, list(options)
3202
3202
3203 if norepo:
3203 if norepo:
3204 # Avoid import cycle.
3204 # Avoid import cycle.
3205 import commands
3205 import commands
3206 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3206 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3207
3207
3208 if optionalrepo:
3208 if optionalrepo:
3209 import commands
3209 import commands
3210 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3210 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3211
3211
3212 if inferrepo:
3212 if inferrepo:
3213 import commands
3213 import commands
3214 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3214 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3215
3215
3216 return func
3216 return func
3217 return decorator
3217 return decorator
3218
3218
3219 return cmd
3219 return cmd
3220
3220
3221 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3221 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3222 # commands.outgoing. "missing" is "missing" of the result of
3222 # commands.outgoing. "missing" is "missing" of the result of
3223 # "findcommonoutgoing()"
3223 # "findcommonoutgoing()"
3224 outgoinghooks = util.hooks()
3224 outgoinghooks = util.hooks()
3225
3225
3226 # a list of (ui, repo) functions called by commands.summary
3226 # a list of (ui, repo) functions called by commands.summary
3227 summaryhooks = util.hooks()
3227 summaryhooks = util.hooks()
3228
3228
3229 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3229 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3230 #
3230 #
3231 # functions should return tuple of booleans below, if 'changes' is None:
3231 # functions should return tuple of booleans below, if 'changes' is None:
3232 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3232 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3233 #
3233 #
3234 # otherwise, 'changes' is a tuple of tuples below:
3234 # otherwise, 'changes' is a tuple of tuples below:
3235 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3235 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3236 # - (desturl, destbranch, destpeer, outgoing)
3236 # - (desturl, destbranch, destpeer, outgoing)
3237 summaryremotehooks = util.hooks()
3237 summaryremotehooks = util.hooks()
3238
3238
3239 # A list of state files kept by multistep operations like graft.
3239 # A list of state files kept by multistep operations like graft.
3240 # Since graft cannot be aborted, it is considered 'clearable' by update.
3240 # Since graft cannot be aborted, it is considered 'clearable' by update.
3241 # note: bisect is intentionally excluded
3241 # note: bisect is intentionally excluded
3242 # (state file, clearable, allowcommit, error, hint)
3242 # (state file, clearable, allowcommit, error, hint)
3243 unfinishedstates = [
3243 unfinishedstates = [
3244 ('graftstate', True, False, _('graft in progress'),
3244 ('graftstate', True, False, _('graft in progress'),
3245 _("use 'hg graft --continue' or 'hg update' to abort")),
3245 _("use 'hg graft --continue' or 'hg update' to abort")),
3246 ('updatestate', True, False, _('last update was interrupted'),
3246 ('updatestate', True, False, _('last update was interrupted'),
3247 _("use 'hg update' to get a consistent checkout"))
3247 _("use 'hg update' to get a consistent checkout"))
3248 ]
3248 ]
3249
3249
3250 def checkunfinished(repo, commit=False):
3250 def checkunfinished(repo, commit=False):
3251 '''Look for an unfinished multistep operation, like graft, and abort
3251 '''Look for an unfinished multistep operation, like graft, and abort
3252 if found. It's probably good to check this right before
3252 if found. It's probably good to check this right before
3253 bailifchanged().
3253 bailifchanged().
3254 '''
3254 '''
3255 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3255 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3256 if commit and allowcommit:
3256 if commit and allowcommit:
3257 continue
3257 continue
3258 if repo.vfs.exists(f):
3258 if repo.vfs.exists(f):
3259 raise util.Abort(msg, hint=hint)
3259 raise util.Abort(msg, hint=hint)
3260
3260
3261 def clearunfinished(repo):
3261 def clearunfinished(repo):
3262 '''Check for unfinished operations (as above), and clear the ones
3262 '''Check for unfinished operations (as above), and clear the ones
3263 that are clearable.
3263 that are clearable.
3264 '''
3264 '''
3265 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3265 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3266 if not clearable and repo.vfs.exists(f):
3266 if not clearable and repo.vfs.exists(f):
3267 raise util.Abort(msg, hint=hint)
3267 raise util.Abort(msg, hint=hint)
3268 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3268 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3269 if clearable and repo.vfs.exists(f):
3269 if clearable and repo.vfs.exists(f):
3270 util.unlink(repo.join(f))
3270 util.unlink(repo.join(f))
3271
3271
3272 class dirstateguard(object):
3272 class dirstateguard(object):
3273 '''Restore dirstate at unexpected failure.
3273 '''Restore dirstate at unexpected failure.
3274
3274
3275 At the construction, this class does:
3275 At the construction, this class does:
3276
3276
3277 - write current ``repo.dirstate`` out, and
3277 - write current ``repo.dirstate`` out, and
3278 - save ``.hg/dirstate`` into the backup file
3278 - save ``.hg/dirstate`` into the backup file
3279
3279
3280 This restores ``.hg/dirstate`` from backup file, if ``release()``
3280 This restores ``.hg/dirstate`` from backup file, if ``release()``
3281 is invoked before ``close()``.
3281 is invoked before ``close()``.
3282
3282
3283 This just removes the backup file at ``close()`` before ``release()``.
3283 This just removes the backup file at ``close()`` before ``release()``.
3284 '''
3284 '''
3285
3285
3286 def __init__(self, repo, name):
3286 def __init__(self, repo, name):
3287 repo.dirstate.write()
3287 repo.dirstate.write()
3288 self._repo = repo
3288 self._repo = repo
3289 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3289 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3290 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3290 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3291 self._active = True
3291 self._active = True
3292 self._closed = False
3292 self._closed = False
3293
3293
3294 def __del__(self):
3294 def __del__(self):
3295 if self._active: # still active
3295 if self._active: # still active
3296 # this may occur, even if this class is used correctly:
3296 # this may occur, even if this class is used correctly:
3297 # for example, releasing other resources like transaction
3297 # for example, releasing other resources like transaction
3298 # may raise exception before ``dirstateguard.release`` in
3298 # may raise exception before ``dirstateguard.release`` in
3299 # ``release(tr, ....)``.
3299 # ``release(tr, ....)``.
3300 self._abort()
3300 self._abort()
3301
3301
3302 def close(self):
3302 def close(self):
3303 if not self._active: # already inactivated
3303 if not self._active: # already inactivated
3304 msg = (_("can't close already inactivated backup: %s")
3304 msg = (_("can't close already inactivated backup: %s")
3305 % self._filename)
3305 % self._filename)
3306 raise util.Abort(msg)
3306 raise util.Abort(msg)
3307
3307
3308 self._repo.vfs.unlink(self._filename)
3308 self._repo.vfs.unlink(self._filename)
3309 self._active = False
3309 self._active = False
3310 self._closed = True
3310 self._closed = True
3311
3311
3312 def _abort(self):
3312 def _abort(self):
3313 # this "invalidate()" prevents "wlock.release()" from writing
3313 # this "invalidate()" prevents "wlock.release()" from writing
3314 # changes of dirstate out after restoring to original status
3314 # changes of dirstate out after restoring to original status
3315 self._repo.dirstate.invalidate()
3315 self._repo.dirstate.invalidate()
3316
3316
3317 self._repo.vfs.rename(self._filename, 'dirstate')
3317 self._repo.vfs.rename(self._filename, 'dirstate')
3318 self._active = False
3318 self._active = False
3319
3319
3320 def release(self):
3320 def release(self):
3321 if not self._closed:
3321 if not self._closed:
3322 if not self._active: # already inactivated
3322 if not self._active: # already inactivated
3323 msg = (_("can't release already inactivated backup: %s")
3323 msg = (_("can't release already inactivated backup: %s")
3324 % self._filename)
3324 % self._filename)
3325 raise util.Abort(msg)
3325 raise util.Abort(msg)
3326 self._abort()
3326 self._abort()
@@ -1,1156 +1,1156 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright 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 i18n import _
8 from i18n import _
9 from mercurial.node import nullrev
9 from mercurial.node import nullrev, wdirrev
10 import util, error, osutil, revset, similar, encoding, phases
10 import util, error, osutil, revset, similar, encoding, phases
11 import pathutil
11 import pathutil
12 import match as matchmod
12 import match as matchmod
13 import os, errno, re, glob, tempfile, shutil, stat
13 import os, errno, re, glob, tempfile, shutil, stat
14
14
15 if os.name == 'nt':
15 if os.name == 'nt':
16 import scmwindows as scmplatform
16 import scmwindows as scmplatform
17 else:
17 else:
18 import scmposix as scmplatform
18 import scmposix as scmplatform
19
19
20 systemrcpath = scmplatform.systemrcpath
20 systemrcpath = scmplatform.systemrcpath
21 userrcpath = scmplatform.userrcpath
21 userrcpath = scmplatform.userrcpath
22
22
23 class status(tuple):
23 class status(tuple):
24 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
24 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
25 and 'ignored' properties are only relevant to the working copy.
25 and 'ignored' properties are only relevant to the working copy.
26 '''
26 '''
27
27
28 __slots__ = ()
28 __slots__ = ()
29
29
30 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
30 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
31 clean):
31 clean):
32 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
32 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
33 ignored, clean))
33 ignored, clean))
34
34
35 @property
35 @property
36 def modified(self):
36 def modified(self):
37 '''files that have been modified'''
37 '''files that have been modified'''
38 return self[0]
38 return self[0]
39
39
40 @property
40 @property
41 def added(self):
41 def added(self):
42 '''files that have been added'''
42 '''files that have been added'''
43 return self[1]
43 return self[1]
44
44
45 @property
45 @property
46 def removed(self):
46 def removed(self):
47 '''files that have been removed'''
47 '''files that have been removed'''
48 return self[2]
48 return self[2]
49
49
50 @property
50 @property
51 def deleted(self):
51 def deleted(self):
52 '''files that are in the dirstate, but have been deleted from the
52 '''files that are in the dirstate, but have been deleted from the
53 working copy (aka "missing")
53 working copy (aka "missing")
54 '''
54 '''
55 return self[3]
55 return self[3]
56
56
57 @property
57 @property
58 def unknown(self):
58 def unknown(self):
59 '''files not in the dirstate that are not ignored'''
59 '''files not in the dirstate that are not ignored'''
60 return self[4]
60 return self[4]
61
61
62 @property
62 @property
63 def ignored(self):
63 def ignored(self):
64 '''files not in the dirstate that are ignored (by _dirignore())'''
64 '''files not in the dirstate that are ignored (by _dirignore())'''
65 return self[5]
65 return self[5]
66
66
67 @property
67 @property
68 def clean(self):
68 def clean(self):
69 '''files that have not been modified'''
69 '''files that have not been modified'''
70 return self[6]
70 return self[6]
71
71
72 def __repr__(self, *args, **kwargs):
72 def __repr__(self, *args, **kwargs):
73 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
73 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
74 'unknown=%r, ignored=%r, clean=%r>') % self)
74 'unknown=%r, ignored=%r, clean=%r>') % self)
75
75
76 def itersubrepos(ctx1, ctx2):
76 def itersubrepos(ctx1, ctx2):
77 """find subrepos in ctx1 or ctx2"""
77 """find subrepos in ctx1 or ctx2"""
78 # Create a (subpath, ctx) mapping where we prefer subpaths from
78 # Create a (subpath, ctx) mapping where we prefer subpaths from
79 # ctx1. The subpaths from ctx2 are important when the .hgsub file
79 # ctx1. The subpaths from ctx2 are important when the .hgsub file
80 # has been modified (in ctx2) but not yet committed (in ctx1).
80 # has been modified (in ctx2) but not yet committed (in ctx1).
81 subpaths = dict.fromkeys(ctx2.substate, ctx2)
81 subpaths = dict.fromkeys(ctx2.substate, ctx2)
82 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
82 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
83
83
84 missing = set()
84 missing = set()
85
85
86 for subpath in ctx2.substate:
86 for subpath in ctx2.substate:
87 if subpath not in ctx1.substate:
87 if subpath not in ctx1.substate:
88 del subpaths[subpath]
88 del subpaths[subpath]
89 missing.add(subpath)
89 missing.add(subpath)
90
90
91 for subpath, ctx in sorted(subpaths.iteritems()):
91 for subpath, ctx in sorted(subpaths.iteritems()):
92 yield subpath, ctx.sub(subpath)
92 yield subpath, ctx.sub(subpath)
93
93
94 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
94 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
95 # status and diff will have an accurate result when it does
95 # status and diff will have an accurate result when it does
96 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
96 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
97 # against itself.
97 # against itself.
98 for subpath in missing:
98 for subpath in missing:
99 yield subpath, ctx2.nullsub(subpath, ctx1)
99 yield subpath, ctx2.nullsub(subpath, ctx1)
100
100
101 def nochangesfound(ui, repo, excluded=None):
101 def nochangesfound(ui, repo, excluded=None):
102 '''Report no changes for push/pull, excluded is None or a list of
102 '''Report no changes for push/pull, excluded is None or a list of
103 nodes excluded from the push/pull.
103 nodes excluded from the push/pull.
104 '''
104 '''
105 secretlist = []
105 secretlist = []
106 if excluded:
106 if excluded:
107 for n in excluded:
107 for n in excluded:
108 if n not in repo:
108 if n not in repo:
109 # discovery should not have included the filtered revision,
109 # discovery should not have included the filtered revision,
110 # we have to explicitly exclude it until discovery is cleanup.
110 # we have to explicitly exclude it until discovery is cleanup.
111 continue
111 continue
112 ctx = repo[n]
112 ctx = repo[n]
113 if ctx.phase() >= phases.secret and not ctx.extinct():
113 if ctx.phase() >= phases.secret and not ctx.extinct():
114 secretlist.append(n)
114 secretlist.append(n)
115
115
116 if secretlist:
116 if secretlist:
117 ui.status(_("no changes found (ignored %d secret changesets)\n")
117 ui.status(_("no changes found (ignored %d secret changesets)\n")
118 % len(secretlist))
118 % len(secretlist))
119 else:
119 else:
120 ui.status(_("no changes found\n"))
120 ui.status(_("no changes found\n"))
121
121
122 def checknewlabel(repo, lbl, kind):
122 def checknewlabel(repo, lbl, kind):
123 # Do not use the "kind" parameter in ui output.
123 # Do not use the "kind" parameter in ui output.
124 # It makes strings difficult to translate.
124 # It makes strings difficult to translate.
125 if lbl in ['tip', '.', 'null']:
125 if lbl in ['tip', '.', 'null']:
126 raise util.Abort(_("the name '%s' is reserved") % lbl)
126 raise util.Abort(_("the name '%s' is reserved") % lbl)
127 for c in (':', '\0', '\n', '\r'):
127 for c in (':', '\0', '\n', '\r'):
128 if c in lbl:
128 if c in lbl:
129 raise util.Abort(_("%r cannot be used in a name") % c)
129 raise util.Abort(_("%r cannot be used in a name") % c)
130 try:
130 try:
131 int(lbl)
131 int(lbl)
132 raise util.Abort(_("cannot use an integer as a name"))
132 raise util.Abort(_("cannot use an integer as a name"))
133 except ValueError:
133 except ValueError:
134 pass
134 pass
135
135
136 def checkfilename(f):
136 def checkfilename(f):
137 '''Check that the filename f is an acceptable filename for a tracked file'''
137 '''Check that the filename f is an acceptable filename for a tracked file'''
138 if '\r' in f or '\n' in f:
138 if '\r' in f or '\n' in f:
139 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
139 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
140
140
141 def checkportable(ui, f):
141 def checkportable(ui, f):
142 '''Check if filename f is portable and warn or abort depending on config'''
142 '''Check if filename f is portable and warn or abort depending on config'''
143 checkfilename(f)
143 checkfilename(f)
144 abort, warn = checkportabilityalert(ui)
144 abort, warn = checkportabilityalert(ui)
145 if abort or warn:
145 if abort or warn:
146 msg = util.checkwinfilename(f)
146 msg = util.checkwinfilename(f)
147 if msg:
147 if msg:
148 msg = "%s: %r" % (msg, f)
148 msg = "%s: %r" % (msg, f)
149 if abort:
149 if abort:
150 raise util.Abort(msg)
150 raise util.Abort(msg)
151 ui.warn(_("warning: %s\n") % msg)
151 ui.warn(_("warning: %s\n") % msg)
152
152
153 def checkportabilityalert(ui):
153 def checkportabilityalert(ui):
154 '''check if the user's config requests nothing, a warning, or abort for
154 '''check if the user's config requests nothing, a warning, or abort for
155 non-portable filenames'''
155 non-portable filenames'''
156 val = ui.config('ui', 'portablefilenames', 'warn')
156 val = ui.config('ui', 'portablefilenames', 'warn')
157 lval = val.lower()
157 lval = val.lower()
158 bval = util.parsebool(val)
158 bval = util.parsebool(val)
159 abort = os.name == 'nt' or lval == 'abort'
159 abort = os.name == 'nt' or lval == 'abort'
160 warn = bval or lval == 'warn'
160 warn = bval or lval == 'warn'
161 if bval is None and not (warn or abort or lval == 'ignore'):
161 if bval is None and not (warn or abort or lval == 'ignore'):
162 raise error.ConfigError(
162 raise error.ConfigError(
163 _("ui.portablefilenames value is invalid ('%s')") % val)
163 _("ui.portablefilenames value is invalid ('%s')") % val)
164 return abort, warn
164 return abort, warn
165
165
166 class casecollisionauditor(object):
166 class casecollisionauditor(object):
167 def __init__(self, ui, abort, dirstate):
167 def __init__(self, ui, abort, dirstate):
168 self._ui = ui
168 self._ui = ui
169 self._abort = abort
169 self._abort = abort
170 allfiles = '\0'.join(dirstate._map)
170 allfiles = '\0'.join(dirstate._map)
171 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
171 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
172 self._dirstate = dirstate
172 self._dirstate = dirstate
173 # The purpose of _newfiles is so that we don't complain about
173 # The purpose of _newfiles is so that we don't complain about
174 # case collisions if someone were to call this object with the
174 # case collisions if someone were to call this object with the
175 # same filename twice.
175 # same filename twice.
176 self._newfiles = set()
176 self._newfiles = set()
177
177
178 def __call__(self, f):
178 def __call__(self, f):
179 if f in self._newfiles:
179 if f in self._newfiles:
180 return
180 return
181 fl = encoding.lower(f)
181 fl = encoding.lower(f)
182 if fl in self._loweredfiles and f not in self._dirstate:
182 if fl in self._loweredfiles and f not in self._dirstate:
183 msg = _('possible case-folding collision for %s') % f
183 msg = _('possible case-folding collision for %s') % f
184 if self._abort:
184 if self._abort:
185 raise util.Abort(msg)
185 raise util.Abort(msg)
186 self._ui.warn(_("warning: %s\n") % msg)
186 self._ui.warn(_("warning: %s\n") % msg)
187 self._loweredfiles.add(fl)
187 self._loweredfiles.add(fl)
188 self._newfiles.add(f)
188 self._newfiles.add(f)
189
189
190 def filteredhash(repo, maxrev):
190 def filteredhash(repo, maxrev):
191 """build hash of filtered revisions in the current repoview.
191 """build hash of filtered revisions in the current repoview.
192
192
193 Multiple caches perform up-to-date validation by checking that the
193 Multiple caches perform up-to-date validation by checking that the
194 tiprev and tipnode stored in the cache file match the current repository.
194 tiprev and tipnode stored in the cache file match the current repository.
195 However, this is not sufficient for validating repoviews because the set
195 However, this is not sufficient for validating repoviews because the set
196 of revisions in the view may change without the repository tiprev and
196 of revisions in the view may change without the repository tiprev and
197 tipnode changing.
197 tipnode changing.
198
198
199 This function hashes all the revs filtered from the view and returns
199 This function hashes all the revs filtered from the view and returns
200 that SHA-1 digest.
200 that SHA-1 digest.
201 """
201 """
202 cl = repo.changelog
202 cl = repo.changelog
203 if not cl.filteredrevs:
203 if not cl.filteredrevs:
204 return None
204 return None
205 key = None
205 key = None
206 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
206 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
207 if revs:
207 if revs:
208 s = util.sha1()
208 s = util.sha1()
209 for rev in revs:
209 for rev in revs:
210 s.update('%s;' % rev)
210 s.update('%s;' % rev)
211 key = s.digest()
211 key = s.digest()
212 return key
212 return key
213
213
214 class abstractvfs(object):
214 class abstractvfs(object):
215 """Abstract base class; cannot be instantiated"""
215 """Abstract base class; cannot be instantiated"""
216
216
217 def __init__(self, *args, **kwargs):
217 def __init__(self, *args, **kwargs):
218 '''Prevent instantiation; don't call this from subclasses.'''
218 '''Prevent instantiation; don't call this from subclasses.'''
219 raise NotImplementedError('attempted instantiating ' + str(type(self)))
219 raise NotImplementedError('attempted instantiating ' + str(type(self)))
220
220
221 def tryread(self, path):
221 def tryread(self, path):
222 '''gracefully return an empty string for missing files'''
222 '''gracefully return an empty string for missing files'''
223 try:
223 try:
224 return self.read(path)
224 return self.read(path)
225 except IOError as inst:
225 except IOError as inst:
226 if inst.errno != errno.ENOENT:
226 if inst.errno != errno.ENOENT:
227 raise
227 raise
228 return ""
228 return ""
229
229
230 def tryreadlines(self, path, mode='rb'):
230 def tryreadlines(self, path, mode='rb'):
231 '''gracefully return an empty array for missing files'''
231 '''gracefully return an empty array for missing files'''
232 try:
232 try:
233 return self.readlines(path, mode=mode)
233 return self.readlines(path, mode=mode)
234 except IOError as inst:
234 except IOError as inst:
235 if inst.errno != errno.ENOENT:
235 if inst.errno != errno.ENOENT:
236 raise
236 raise
237 return []
237 return []
238
238
239 def open(self, path, mode="r", text=False, atomictemp=False,
239 def open(self, path, mode="r", text=False, atomictemp=False,
240 notindexed=False):
240 notindexed=False):
241 '''Open ``path`` file, which is relative to vfs root.
241 '''Open ``path`` file, which is relative to vfs root.
242
242
243 Newly created directories are marked as "not to be indexed by
243 Newly created directories are marked as "not to be indexed by
244 the content indexing service", if ``notindexed`` is specified
244 the content indexing service", if ``notindexed`` is specified
245 for "write" mode access.
245 for "write" mode access.
246 '''
246 '''
247 self.open = self.__call__
247 self.open = self.__call__
248 return self.__call__(path, mode, text, atomictemp, notindexed)
248 return self.__call__(path, mode, text, atomictemp, notindexed)
249
249
250 def read(self, path):
250 def read(self, path):
251 fp = self(path, 'rb')
251 fp = self(path, 'rb')
252 try:
252 try:
253 return fp.read()
253 return fp.read()
254 finally:
254 finally:
255 fp.close()
255 fp.close()
256
256
257 def readlines(self, path, mode='rb'):
257 def readlines(self, path, mode='rb'):
258 fp = self(path, mode=mode)
258 fp = self(path, mode=mode)
259 try:
259 try:
260 return fp.readlines()
260 return fp.readlines()
261 finally:
261 finally:
262 fp.close()
262 fp.close()
263
263
264 def write(self, path, data):
264 def write(self, path, data):
265 fp = self(path, 'wb')
265 fp = self(path, 'wb')
266 try:
266 try:
267 return fp.write(data)
267 return fp.write(data)
268 finally:
268 finally:
269 fp.close()
269 fp.close()
270
270
271 def writelines(self, path, data, mode='wb', notindexed=False):
271 def writelines(self, path, data, mode='wb', notindexed=False):
272 fp = self(path, mode=mode, notindexed=notindexed)
272 fp = self(path, mode=mode, notindexed=notindexed)
273 try:
273 try:
274 return fp.writelines(data)
274 return fp.writelines(data)
275 finally:
275 finally:
276 fp.close()
276 fp.close()
277
277
278 def append(self, path, data):
278 def append(self, path, data):
279 fp = self(path, 'ab')
279 fp = self(path, 'ab')
280 try:
280 try:
281 return fp.write(data)
281 return fp.write(data)
282 finally:
282 finally:
283 fp.close()
283 fp.close()
284
284
285 def chmod(self, path, mode):
285 def chmod(self, path, mode):
286 return os.chmod(self.join(path), mode)
286 return os.chmod(self.join(path), mode)
287
287
288 def exists(self, path=None):
288 def exists(self, path=None):
289 return os.path.exists(self.join(path))
289 return os.path.exists(self.join(path))
290
290
291 def fstat(self, fp):
291 def fstat(self, fp):
292 return util.fstat(fp)
292 return util.fstat(fp)
293
293
294 def isdir(self, path=None):
294 def isdir(self, path=None):
295 return os.path.isdir(self.join(path))
295 return os.path.isdir(self.join(path))
296
296
297 def isfile(self, path=None):
297 def isfile(self, path=None):
298 return os.path.isfile(self.join(path))
298 return os.path.isfile(self.join(path))
299
299
300 def islink(self, path=None):
300 def islink(self, path=None):
301 return os.path.islink(self.join(path))
301 return os.path.islink(self.join(path))
302
302
303 def reljoin(self, *paths):
303 def reljoin(self, *paths):
304 """join various elements of a path together (as os.path.join would do)
304 """join various elements of a path together (as os.path.join would do)
305
305
306 The vfs base is not injected so that path stay relative. This exists
306 The vfs base is not injected so that path stay relative. This exists
307 to allow handling of strange encoding if needed."""
307 to allow handling of strange encoding if needed."""
308 return os.path.join(*paths)
308 return os.path.join(*paths)
309
309
310 def split(self, path):
310 def split(self, path):
311 """split top-most element of a path (as os.path.split would do)
311 """split top-most element of a path (as os.path.split would do)
312
312
313 This exists to allow handling of strange encoding if needed."""
313 This exists to allow handling of strange encoding if needed."""
314 return os.path.split(path)
314 return os.path.split(path)
315
315
316 def lexists(self, path=None):
316 def lexists(self, path=None):
317 return os.path.lexists(self.join(path))
317 return os.path.lexists(self.join(path))
318
318
319 def lstat(self, path=None):
319 def lstat(self, path=None):
320 return os.lstat(self.join(path))
320 return os.lstat(self.join(path))
321
321
322 def listdir(self, path=None):
322 def listdir(self, path=None):
323 return os.listdir(self.join(path))
323 return os.listdir(self.join(path))
324
324
325 def makedir(self, path=None, notindexed=True):
325 def makedir(self, path=None, notindexed=True):
326 return util.makedir(self.join(path), notindexed)
326 return util.makedir(self.join(path), notindexed)
327
327
328 def makedirs(self, path=None, mode=None):
328 def makedirs(self, path=None, mode=None):
329 return util.makedirs(self.join(path), mode)
329 return util.makedirs(self.join(path), mode)
330
330
331 def makelock(self, info, path):
331 def makelock(self, info, path):
332 return util.makelock(info, self.join(path))
332 return util.makelock(info, self.join(path))
333
333
334 def mkdir(self, path=None):
334 def mkdir(self, path=None):
335 return os.mkdir(self.join(path))
335 return os.mkdir(self.join(path))
336
336
337 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
337 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
338 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
338 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
339 dir=self.join(dir), text=text)
339 dir=self.join(dir), text=text)
340 dname, fname = util.split(name)
340 dname, fname = util.split(name)
341 if dir:
341 if dir:
342 return fd, os.path.join(dir, fname)
342 return fd, os.path.join(dir, fname)
343 else:
343 else:
344 return fd, fname
344 return fd, fname
345
345
346 def readdir(self, path=None, stat=None, skip=None):
346 def readdir(self, path=None, stat=None, skip=None):
347 return osutil.listdir(self.join(path), stat, skip)
347 return osutil.listdir(self.join(path), stat, skip)
348
348
349 def readlock(self, path):
349 def readlock(self, path):
350 return util.readlock(self.join(path))
350 return util.readlock(self.join(path))
351
351
352 def rename(self, src, dst):
352 def rename(self, src, dst):
353 return util.rename(self.join(src), self.join(dst))
353 return util.rename(self.join(src), self.join(dst))
354
354
355 def readlink(self, path):
355 def readlink(self, path):
356 return os.readlink(self.join(path))
356 return os.readlink(self.join(path))
357
357
358 def removedirs(self, path=None):
358 def removedirs(self, path=None):
359 """Remove a leaf directory and all empty intermediate ones
359 """Remove a leaf directory and all empty intermediate ones
360 """
360 """
361 return util.removedirs(self.join(path))
361 return util.removedirs(self.join(path))
362
362
363 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
363 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
364 """Remove a directory tree recursively
364 """Remove a directory tree recursively
365
365
366 If ``forcibly``, this tries to remove READ-ONLY files, too.
366 If ``forcibly``, this tries to remove READ-ONLY files, too.
367 """
367 """
368 if forcibly:
368 if forcibly:
369 def onerror(function, path, excinfo):
369 def onerror(function, path, excinfo):
370 if function is not os.remove:
370 if function is not os.remove:
371 raise
371 raise
372 # read-only files cannot be unlinked under Windows
372 # read-only files cannot be unlinked under Windows
373 s = os.stat(path)
373 s = os.stat(path)
374 if (s.st_mode & stat.S_IWRITE) != 0:
374 if (s.st_mode & stat.S_IWRITE) != 0:
375 raise
375 raise
376 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
376 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
377 os.remove(path)
377 os.remove(path)
378 else:
378 else:
379 onerror = None
379 onerror = None
380 return shutil.rmtree(self.join(path),
380 return shutil.rmtree(self.join(path),
381 ignore_errors=ignore_errors, onerror=onerror)
381 ignore_errors=ignore_errors, onerror=onerror)
382
382
383 def setflags(self, path, l, x):
383 def setflags(self, path, l, x):
384 return util.setflags(self.join(path), l, x)
384 return util.setflags(self.join(path), l, x)
385
385
386 def stat(self, path=None):
386 def stat(self, path=None):
387 return os.stat(self.join(path))
387 return os.stat(self.join(path))
388
388
389 def unlink(self, path=None):
389 def unlink(self, path=None):
390 return util.unlink(self.join(path))
390 return util.unlink(self.join(path))
391
391
392 def unlinkpath(self, path=None, ignoremissing=False):
392 def unlinkpath(self, path=None, ignoremissing=False):
393 return util.unlinkpath(self.join(path), ignoremissing)
393 return util.unlinkpath(self.join(path), ignoremissing)
394
394
395 def utime(self, path=None, t=None):
395 def utime(self, path=None, t=None):
396 return os.utime(self.join(path), t)
396 return os.utime(self.join(path), t)
397
397
398 def walk(self, path=None, onerror=None):
398 def walk(self, path=None, onerror=None):
399 """Yield (dirpath, dirs, files) tuple for each directories under path
399 """Yield (dirpath, dirs, files) tuple for each directories under path
400
400
401 ``dirpath`` is relative one from the root of this vfs. This
401 ``dirpath`` is relative one from the root of this vfs. This
402 uses ``os.sep`` as path separator, even you specify POSIX
402 uses ``os.sep`` as path separator, even you specify POSIX
403 style ``path``.
403 style ``path``.
404
404
405 "The root of this vfs" is represented as empty ``dirpath``.
405 "The root of this vfs" is represented as empty ``dirpath``.
406 """
406 """
407 root = os.path.normpath(self.join(None))
407 root = os.path.normpath(self.join(None))
408 # when dirpath == root, dirpath[prefixlen:] becomes empty
408 # when dirpath == root, dirpath[prefixlen:] becomes empty
409 # because len(dirpath) < prefixlen.
409 # because len(dirpath) < prefixlen.
410 prefixlen = len(pathutil.normasprefix(root))
410 prefixlen = len(pathutil.normasprefix(root))
411 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
411 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
412 yield (dirpath[prefixlen:], dirs, files)
412 yield (dirpath[prefixlen:], dirs, files)
413
413
414 class vfs(abstractvfs):
414 class vfs(abstractvfs):
415 '''Operate files relative to a base directory
415 '''Operate files relative to a base directory
416
416
417 This class is used to hide the details of COW semantics and
417 This class is used to hide the details of COW semantics and
418 remote file access from higher level code.
418 remote file access from higher level code.
419 '''
419 '''
420 def __init__(self, base, audit=True, expandpath=False, realpath=False):
420 def __init__(self, base, audit=True, expandpath=False, realpath=False):
421 if expandpath:
421 if expandpath:
422 base = util.expandpath(base)
422 base = util.expandpath(base)
423 if realpath:
423 if realpath:
424 base = os.path.realpath(base)
424 base = os.path.realpath(base)
425 self.base = base
425 self.base = base
426 self._setmustaudit(audit)
426 self._setmustaudit(audit)
427 self.createmode = None
427 self.createmode = None
428 self._trustnlink = None
428 self._trustnlink = None
429
429
430 def _getmustaudit(self):
430 def _getmustaudit(self):
431 return self._audit
431 return self._audit
432
432
433 def _setmustaudit(self, onoff):
433 def _setmustaudit(self, onoff):
434 self._audit = onoff
434 self._audit = onoff
435 if onoff:
435 if onoff:
436 self.audit = pathutil.pathauditor(self.base)
436 self.audit = pathutil.pathauditor(self.base)
437 else:
437 else:
438 self.audit = util.always
438 self.audit = util.always
439
439
440 mustaudit = property(_getmustaudit, _setmustaudit)
440 mustaudit = property(_getmustaudit, _setmustaudit)
441
441
442 @util.propertycache
442 @util.propertycache
443 def _cansymlink(self):
443 def _cansymlink(self):
444 return util.checklink(self.base)
444 return util.checklink(self.base)
445
445
446 @util.propertycache
446 @util.propertycache
447 def _chmod(self):
447 def _chmod(self):
448 return util.checkexec(self.base)
448 return util.checkexec(self.base)
449
449
450 def _fixfilemode(self, name):
450 def _fixfilemode(self, name):
451 if self.createmode is None or not self._chmod:
451 if self.createmode is None or not self._chmod:
452 return
452 return
453 os.chmod(name, self.createmode & 0o666)
453 os.chmod(name, self.createmode & 0o666)
454
454
455 def __call__(self, path, mode="r", text=False, atomictemp=False,
455 def __call__(self, path, mode="r", text=False, atomictemp=False,
456 notindexed=False):
456 notindexed=False):
457 '''Open ``path`` file, which is relative to vfs root.
457 '''Open ``path`` file, which is relative to vfs root.
458
458
459 Newly created directories are marked as "not to be indexed by
459 Newly created directories are marked as "not to be indexed by
460 the content indexing service", if ``notindexed`` is specified
460 the content indexing service", if ``notindexed`` is specified
461 for "write" mode access.
461 for "write" mode access.
462 '''
462 '''
463 if self._audit:
463 if self._audit:
464 r = util.checkosfilename(path)
464 r = util.checkosfilename(path)
465 if r:
465 if r:
466 raise util.Abort("%s: %r" % (r, path))
466 raise util.Abort("%s: %r" % (r, path))
467 self.audit(path)
467 self.audit(path)
468 f = self.join(path)
468 f = self.join(path)
469
469
470 if not text and "b" not in mode:
470 if not text and "b" not in mode:
471 mode += "b" # for that other OS
471 mode += "b" # for that other OS
472
472
473 nlink = -1
473 nlink = -1
474 if mode not in ('r', 'rb'):
474 if mode not in ('r', 'rb'):
475 dirname, basename = util.split(f)
475 dirname, basename = util.split(f)
476 # If basename is empty, then the path is malformed because it points
476 # If basename is empty, then the path is malformed because it points
477 # to a directory. Let the posixfile() call below raise IOError.
477 # to a directory. Let the posixfile() call below raise IOError.
478 if basename:
478 if basename:
479 if atomictemp:
479 if atomictemp:
480 util.ensuredirs(dirname, self.createmode, notindexed)
480 util.ensuredirs(dirname, self.createmode, notindexed)
481 return util.atomictempfile(f, mode, self.createmode)
481 return util.atomictempfile(f, mode, self.createmode)
482 try:
482 try:
483 if 'w' in mode:
483 if 'w' in mode:
484 util.unlink(f)
484 util.unlink(f)
485 nlink = 0
485 nlink = 0
486 else:
486 else:
487 # nlinks() may behave differently for files on Windows
487 # nlinks() may behave differently for files on Windows
488 # shares if the file is open.
488 # shares if the file is open.
489 fd = util.posixfile(f)
489 fd = util.posixfile(f)
490 nlink = util.nlinks(f)
490 nlink = util.nlinks(f)
491 if nlink < 1:
491 if nlink < 1:
492 nlink = 2 # force mktempcopy (issue1922)
492 nlink = 2 # force mktempcopy (issue1922)
493 fd.close()
493 fd.close()
494 except (OSError, IOError) as e:
494 except (OSError, IOError) as e:
495 if e.errno != errno.ENOENT:
495 if e.errno != errno.ENOENT:
496 raise
496 raise
497 nlink = 0
497 nlink = 0
498 util.ensuredirs(dirname, self.createmode, notindexed)
498 util.ensuredirs(dirname, self.createmode, notindexed)
499 if nlink > 0:
499 if nlink > 0:
500 if self._trustnlink is None:
500 if self._trustnlink is None:
501 self._trustnlink = nlink > 1 or util.checknlink(f)
501 self._trustnlink = nlink > 1 or util.checknlink(f)
502 if nlink > 1 or not self._trustnlink:
502 if nlink > 1 or not self._trustnlink:
503 util.rename(util.mktempcopy(f), f)
503 util.rename(util.mktempcopy(f), f)
504 fp = util.posixfile(f, mode)
504 fp = util.posixfile(f, mode)
505 if nlink == 0:
505 if nlink == 0:
506 self._fixfilemode(f)
506 self._fixfilemode(f)
507 return fp
507 return fp
508
508
509 def symlink(self, src, dst):
509 def symlink(self, src, dst):
510 self.audit(dst)
510 self.audit(dst)
511 linkname = self.join(dst)
511 linkname = self.join(dst)
512 try:
512 try:
513 os.unlink(linkname)
513 os.unlink(linkname)
514 except OSError:
514 except OSError:
515 pass
515 pass
516
516
517 util.ensuredirs(os.path.dirname(linkname), self.createmode)
517 util.ensuredirs(os.path.dirname(linkname), self.createmode)
518
518
519 if self._cansymlink:
519 if self._cansymlink:
520 try:
520 try:
521 os.symlink(src, linkname)
521 os.symlink(src, linkname)
522 except OSError as err:
522 except OSError as err:
523 raise OSError(err.errno, _('could not symlink to %r: %s') %
523 raise OSError(err.errno, _('could not symlink to %r: %s') %
524 (src, err.strerror), linkname)
524 (src, err.strerror), linkname)
525 else:
525 else:
526 self.write(dst, src)
526 self.write(dst, src)
527
527
528 def join(self, path, *insidef):
528 def join(self, path, *insidef):
529 if path:
529 if path:
530 return os.path.join(self.base, path, *insidef)
530 return os.path.join(self.base, path, *insidef)
531 else:
531 else:
532 return self.base
532 return self.base
533
533
534 opener = vfs
534 opener = vfs
535
535
536 class auditvfs(object):
536 class auditvfs(object):
537 def __init__(self, vfs):
537 def __init__(self, vfs):
538 self.vfs = vfs
538 self.vfs = vfs
539
539
540 def _getmustaudit(self):
540 def _getmustaudit(self):
541 return self.vfs.mustaudit
541 return self.vfs.mustaudit
542
542
543 def _setmustaudit(self, onoff):
543 def _setmustaudit(self, onoff):
544 self.vfs.mustaudit = onoff
544 self.vfs.mustaudit = onoff
545
545
546 mustaudit = property(_getmustaudit, _setmustaudit)
546 mustaudit = property(_getmustaudit, _setmustaudit)
547
547
548 class filtervfs(abstractvfs, auditvfs):
548 class filtervfs(abstractvfs, auditvfs):
549 '''Wrapper vfs for filtering filenames with a function.'''
549 '''Wrapper vfs for filtering filenames with a function.'''
550
550
551 def __init__(self, vfs, filter):
551 def __init__(self, vfs, filter):
552 auditvfs.__init__(self, vfs)
552 auditvfs.__init__(self, vfs)
553 self._filter = filter
553 self._filter = filter
554
554
555 def __call__(self, path, *args, **kwargs):
555 def __call__(self, path, *args, **kwargs):
556 return self.vfs(self._filter(path), *args, **kwargs)
556 return self.vfs(self._filter(path), *args, **kwargs)
557
557
558 def join(self, path, *insidef):
558 def join(self, path, *insidef):
559 if path:
559 if path:
560 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
560 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
561 else:
561 else:
562 return self.vfs.join(path)
562 return self.vfs.join(path)
563
563
564 filteropener = filtervfs
564 filteropener = filtervfs
565
565
566 class readonlyvfs(abstractvfs, auditvfs):
566 class readonlyvfs(abstractvfs, auditvfs):
567 '''Wrapper vfs preventing any writing.'''
567 '''Wrapper vfs preventing any writing.'''
568
568
569 def __init__(self, vfs):
569 def __init__(self, vfs):
570 auditvfs.__init__(self, vfs)
570 auditvfs.__init__(self, vfs)
571
571
572 def __call__(self, path, mode='r', *args, **kw):
572 def __call__(self, path, mode='r', *args, **kw):
573 if mode not in ('r', 'rb'):
573 if mode not in ('r', 'rb'):
574 raise util.Abort('this vfs is read only')
574 raise util.Abort('this vfs is read only')
575 return self.vfs(path, mode, *args, **kw)
575 return self.vfs(path, mode, *args, **kw)
576
576
577
577
578 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
578 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
579 '''yield every hg repository under path, always recursively.
579 '''yield every hg repository under path, always recursively.
580 The recurse flag will only control recursion into repo working dirs'''
580 The recurse flag will only control recursion into repo working dirs'''
581 def errhandler(err):
581 def errhandler(err):
582 if err.filename == path:
582 if err.filename == path:
583 raise err
583 raise err
584 samestat = getattr(os.path, 'samestat', None)
584 samestat = getattr(os.path, 'samestat', None)
585 if followsym and samestat is not None:
585 if followsym and samestat is not None:
586 def adddir(dirlst, dirname):
586 def adddir(dirlst, dirname):
587 match = False
587 match = False
588 dirstat = os.stat(dirname)
588 dirstat = os.stat(dirname)
589 for lstdirstat in dirlst:
589 for lstdirstat in dirlst:
590 if samestat(dirstat, lstdirstat):
590 if samestat(dirstat, lstdirstat):
591 match = True
591 match = True
592 break
592 break
593 if not match:
593 if not match:
594 dirlst.append(dirstat)
594 dirlst.append(dirstat)
595 return not match
595 return not match
596 else:
596 else:
597 followsym = False
597 followsym = False
598
598
599 if (seen_dirs is None) and followsym:
599 if (seen_dirs is None) and followsym:
600 seen_dirs = []
600 seen_dirs = []
601 adddir(seen_dirs, path)
601 adddir(seen_dirs, path)
602 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
602 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
603 dirs.sort()
603 dirs.sort()
604 if '.hg' in dirs:
604 if '.hg' in dirs:
605 yield root # found a repository
605 yield root # found a repository
606 qroot = os.path.join(root, '.hg', 'patches')
606 qroot = os.path.join(root, '.hg', 'patches')
607 if os.path.isdir(os.path.join(qroot, '.hg')):
607 if os.path.isdir(os.path.join(qroot, '.hg')):
608 yield qroot # we have a patch queue repo here
608 yield qroot # we have a patch queue repo here
609 if recurse:
609 if recurse:
610 # avoid recursing inside the .hg directory
610 # avoid recursing inside the .hg directory
611 dirs.remove('.hg')
611 dirs.remove('.hg')
612 else:
612 else:
613 dirs[:] = [] # don't descend further
613 dirs[:] = [] # don't descend further
614 elif followsym:
614 elif followsym:
615 newdirs = []
615 newdirs = []
616 for d in dirs:
616 for d in dirs:
617 fname = os.path.join(root, d)
617 fname = os.path.join(root, d)
618 if adddir(seen_dirs, fname):
618 if adddir(seen_dirs, fname):
619 if os.path.islink(fname):
619 if os.path.islink(fname):
620 for hgname in walkrepos(fname, True, seen_dirs):
620 for hgname in walkrepos(fname, True, seen_dirs):
621 yield hgname
621 yield hgname
622 else:
622 else:
623 newdirs.append(d)
623 newdirs.append(d)
624 dirs[:] = newdirs
624 dirs[:] = newdirs
625
625
626 def osrcpath():
626 def osrcpath():
627 '''return default os-specific hgrc search path'''
627 '''return default os-specific hgrc search path'''
628 path = []
628 path = []
629 defaultpath = os.path.join(util.datapath, 'default.d')
629 defaultpath = os.path.join(util.datapath, 'default.d')
630 if os.path.isdir(defaultpath):
630 if os.path.isdir(defaultpath):
631 for f, kind in osutil.listdir(defaultpath):
631 for f, kind in osutil.listdir(defaultpath):
632 if f.endswith('.rc'):
632 if f.endswith('.rc'):
633 path.append(os.path.join(defaultpath, f))
633 path.append(os.path.join(defaultpath, f))
634 path.extend(systemrcpath())
634 path.extend(systemrcpath())
635 path.extend(userrcpath())
635 path.extend(userrcpath())
636 path = [os.path.normpath(f) for f in path]
636 path = [os.path.normpath(f) for f in path]
637 return path
637 return path
638
638
639 _rcpath = None
639 _rcpath = None
640
640
641 def rcpath():
641 def rcpath():
642 '''return hgrc search path. if env var HGRCPATH is set, use it.
642 '''return hgrc search path. if env var HGRCPATH is set, use it.
643 for each item in path, if directory, use files ending in .rc,
643 for each item in path, if directory, use files ending in .rc,
644 else use item.
644 else use item.
645 make HGRCPATH empty to only look in .hg/hgrc of current repo.
645 make HGRCPATH empty to only look in .hg/hgrc of current repo.
646 if no HGRCPATH, use default os-specific path.'''
646 if no HGRCPATH, use default os-specific path.'''
647 global _rcpath
647 global _rcpath
648 if _rcpath is None:
648 if _rcpath is None:
649 if 'HGRCPATH' in os.environ:
649 if 'HGRCPATH' in os.environ:
650 _rcpath = []
650 _rcpath = []
651 for p in os.environ['HGRCPATH'].split(os.pathsep):
651 for p in os.environ['HGRCPATH'].split(os.pathsep):
652 if not p:
652 if not p:
653 continue
653 continue
654 p = util.expandpath(p)
654 p = util.expandpath(p)
655 if os.path.isdir(p):
655 if os.path.isdir(p):
656 for f, kind in osutil.listdir(p):
656 for f, kind in osutil.listdir(p):
657 if f.endswith('.rc'):
657 if f.endswith('.rc'):
658 _rcpath.append(os.path.join(p, f))
658 _rcpath.append(os.path.join(p, f))
659 else:
659 else:
660 _rcpath.append(p)
660 _rcpath.append(p)
661 else:
661 else:
662 _rcpath = osrcpath()
662 _rcpath = osrcpath()
663 return _rcpath
663 return _rcpath
664
664
665 def intrev(repo, rev):
665 def intrev(rev):
666 """Return integer for a given revision that can be used in comparison or
666 """Return integer for a given revision that can be used in comparison or
667 arithmetic operation"""
667 arithmetic operation"""
668 if rev is None:
668 if rev is None:
669 return len(repo)
669 return wdirrev
670 return rev
670 return rev
671
671
672 def revsingle(repo, revspec, default='.'):
672 def revsingle(repo, revspec, default='.'):
673 if not revspec and revspec != 0:
673 if not revspec and revspec != 0:
674 return repo[default]
674 return repo[default]
675
675
676 l = revrange(repo, [revspec])
676 l = revrange(repo, [revspec])
677 if not l:
677 if not l:
678 raise util.Abort(_('empty revision set'))
678 raise util.Abort(_('empty revision set'))
679 return repo[l.last()]
679 return repo[l.last()]
680
680
681 def revpair(repo, revs):
681 def revpair(repo, revs):
682 if not revs:
682 if not revs:
683 return repo.dirstate.p1(), None
683 return repo.dirstate.p1(), None
684
684
685 l = revrange(repo, revs)
685 l = revrange(repo, revs)
686
686
687 if not l:
687 if not l:
688 first = second = None
688 first = second = None
689 elif l.isascending():
689 elif l.isascending():
690 first = l.min()
690 first = l.min()
691 second = l.max()
691 second = l.max()
692 elif l.isdescending():
692 elif l.isdescending():
693 first = l.max()
693 first = l.max()
694 second = l.min()
694 second = l.min()
695 else:
695 else:
696 first = l.first()
696 first = l.first()
697 second = l.last()
697 second = l.last()
698
698
699 if first is None:
699 if first is None:
700 raise util.Abort(_('empty revision range'))
700 raise util.Abort(_('empty revision range'))
701
701
702 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
702 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
703 return repo.lookup(first), None
703 return repo.lookup(first), None
704
704
705 return repo.lookup(first), repo.lookup(second)
705 return repo.lookup(first), repo.lookup(second)
706
706
707 _revrangesep = ':'
707 _revrangesep = ':'
708
708
709 def revrange(repo, revs):
709 def revrange(repo, revs):
710 """Yield revision as strings from a list of revision specifications."""
710 """Yield revision as strings from a list of revision specifications."""
711
711
712 def revfix(repo, val, defval):
712 def revfix(repo, val, defval):
713 if not val and val != 0 and defval is not None:
713 if not val and val != 0 and defval is not None:
714 return defval
714 return defval
715 return repo[val].rev()
715 return repo[val].rev()
716
716
717 subsets = []
717 subsets = []
718
718
719 revsetaliases = [alias for (alias, _) in
719 revsetaliases = [alias for (alias, _) in
720 repo.ui.configitems("revsetalias")]
720 repo.ui.configitems("revsetalias")]
721
721
722 for spec in revs:
722 for spec in revs:
723 # attempt to parse old-style ranges first to deal with
723 # attempt to parse old-style ranges first to deal with
724 # things like old-tag which contain query metacharacters
724 # things like old-tag which contain query metacharacters
725 try:
725 try:
726 # ... except for revset aliases without arguments. These
726 # ... except for revset aliases without arguments. These
727 # should be parsed as soon as possible, because they might
727 # should be parsed as soon as possible, because they might
728 # clash with a hash prefix.
728 # clash with a hash prefix.
729 if spec in revsetaliases:
729 if spec in revsetaliases:
730 raise error.RepoLookupError
730 raise error.RepoLookupError
731
731
732 if isinstance(spec, int):
732 if isinstance(spec, int):
733 subsets.append(revset.baseset([spec]))
733 subsets.append(revset.baseset([spec]))
734 continue
734 continue
735
735
736 if _revrangesep in spec:
736 if _revrangesep in spec:
737 start, end = spec.split(_revrangesep, 1)
737 start, end = spec.split(_revrangesep, 1)
738 if start in revsetaliases or end in revsetaliases:
738 if start in revsetaliases or end in revsetaliases:
739 raise error.RepoLookupError
739 raise error.RepoLookupError
740
740
741 start = revfix(repo, start, 0)
741 start = revfix(repo, start, 0)
742 end = revfix(repo, end, len(repo) - 1)
742 end = revfix(repo, end, len(repo) - 1)
743 if end == nullrev and start < 0:
743 if end == nullrev and start < 0:
744 start = nullrev
744 start = nullrev
745 if start < end:
745 if start < end:
746 l = revset.spanset(repo, start, end + 1)
746 l = revset.spanset(repo, start, end + 1)
747 else:
747 else:
748 l = revset.spanset(repo, start, end - 1)
748 l = revset.spanset(repo, start, end - 1)
749 subsets.append(l)
749 subsets.append(l)
750 continue
750 continue
751 elif spec and spec in repo: # single unquoted rev
751 elif spec and spec in repo: # single unquoted rev
752 rev = revfix(repo, spec, None)
752 rev = revfix(repo, spec, None)
753 subsets.append(revset.baseset([rev]))
753 subsets.append(revset.baseset([rev]))
754 continue
754 continue
755 except error.RepoLookupError:
755 except error.RepoLookupError:
756 pass
756 pass
757
757
758 # fall through to new-style queries if old-style fails
758 # fall through to new-style queries if old-style fails
759 m = revset.match(repo.ui, spec, repo)
759 m = revset.match(repo.ui, spec, repo)
760 subsets.append(m(repo))
760 subsets.append(m(repo))
761
761
762 return revset._combinesets(subsets)
762 return revset._combinesets(subsets)
763
763
764 def expandpats(pats):
764 def expandpats(pats):
765 '''Expand bare globs when running on windows.
765 '''Expand bare globs when running on windows.
766 On posix we assume it already has already been done by sh.'''
766 On posix we assume it already has already been done by sh.'''
767 if not util.expandglobs:
767 if not util.expandglobs:
768 return list(pats)
768 return list(pats)
769 ret = []
769 ret = []
770 for kindpat in pats:
770 for kindpat in pats:
771 kind, pat = matchmod._patsplit(kindpat, None)
771 kind, pat = matchmod._patsplit(kindpat, None)
772 if kind is None:
772 if kind is None:
773 try:
773 try:
774 globbed = glob.glob(pat)
774 globbed = glob.glob(pat)
775 except re.error:
775 except re.error:
776 globbed = [pat]
776 globbed = [pat]
777 if globbed:
777 if globbed:
778 ret.extend(globbed)
778 ret.extend(globbed)
779 continue
779 continue
780 ret.append(kindpat)
780 ret.append(kindpat)
781 return ret
781 return ret
782
782
783 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath',
783 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath',
784 badfn=None):
784 badfn=None):
785 '''Return a matcher and the patterns that were used.
785 '''Return a matcher and the patterns that were used.
786 The matcher will warn about bad matches, unless an alternate badfn callback
786 The matcher will warn about bad matches, unless an alternate badfn callback
787 is provided.'''
787 is provided.'''
788 if pats == ("",):
788 if pats == ("",):
789 pats = []
789 pats = []
790 if not globbed and default == 'relpath':
790 if not globbed and default == 'relpath':
791 pats = expandpats(pats or [])
791 pats = expandpats(pats or [])
792
792
793 def bad(f, msg):
793 def bad(f, msg):
794 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
794 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
795
795
796 if badfn is None:
796 if badfn is None:
797 badfn = bad
797 badfn = bad
798
798
799 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
799 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
800 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
800 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
801
801
802 if m.always():
802 if m.always():
803 pats = []
803 pats = []
804 return m, pats
804 return m, pats
805
805
806 def match(ctx, pats=[], opts={}, globbed=False, default='relpath', badfn=None):
806 def match(ctx, pats=[], opts={}, globbed=False, default='relpath', badfn=None):
807 '''Return a matcher that will warn about bad matches.'''
807 '''Return a matcher that will warn about bad matches.'''
808 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
808 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
809
809
810 def matchall(repo):
810 def matchall(repo):
811 '''Return a matcher that will efficiently match everything.'''
811 '''Return a matcher that will efficiently match everything.'''
812 return matchmod.always(repo.root, repo.getcwd())
812 return matchmod.always(repo.root, repo.getcwd())
813
813
814 def matchfiles(repo, files, badfn=None):
814 def matchfiles(repo, files, badfn=None):
815 '''Return a matcher that will efficiently match exactly these files.'''
815 '''Return a matcher that will efficiently match exactly these files.'''
816 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
816 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
817
817
818 def addremove(repo, matcher, prefix, opts={}, dry_run=None, similarity=None):
818 def addremove(repo, matcher, prefix, opts={}, dry_run=None, similarity=None):
819 m = matcher
819 m = matcher
820 if dry_run is None:
820 if dry_run is None:
821 dry_run = opts.get('dry_run')
821 dry_run = opts.get('dry_run')
822 if similarity is None:
822 if similarity is None:
823 similarity = float(opts.get('similarity') or 0)
823 similarity = float(opts.get('similarity') or 0)
824
824
825 ret = 0
825 ret = 0
826 join = lambda f: os.path.join(prefix, f)
826 join = lambda f: os.path.join(prefix, f)
827
827
828 def matchessubrepo(matcher, subpath):
828 def matchessubrepo(matcher, subpath):
829 if matcher.exact(subpath):
829 if matcher.exact(subpath):
830 return True
830 return True
831 for f in matcher.files():
831 for f in matcher.files():
832 if f.startswith(subpath):
832 if f.startswith(subpath):
833 return True
833 return True
834 return False
834 return False
835
835
836 wctx = repo[None]
836 wctx = repo[None]
837 for subpath in sorted(wctx.substate):
837 for subpath in sorted(wctx.substate):
838 if opts.get('subrepos') or matchessubrepo(m, subpath):
838 if opts.get('subrepos') or matchessubrepo(m, subpath):
839 sub = wctx.sub(subpath)
839 sub = wctx.sub(subpath)
840 try:
840 try:
841 submatch = matchmod.narrowmatcher(subpath, m)
841 submatch = matchmod.narrowmatcher(subpath, m)
842 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
842 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
843 ret = 1
843 ret = 1
844 except error.LookupError:
844 except error.LookupError:
845 repo.ui.status(_("skipping missing subrepository: %s\n")
845 repo.ui.status(_("skipping missing subrepository: %s\n")
846 % join(subpath))
846 % join(subpath))
847
847
848 rejected = []
848 rejected = []
849 def badfn(f, msg):
849 def badfn(f, msg):
850 if f in m.files():
850 if f in m.files():
851 m.bad(f, msg)
851 m.bad(f, msg)
852 rejected.append(f)
852 rejected.append(f)
853
853
854 badmatch = matchmod.badmatch(m, badfn)
854 badmatch = matchmod.badmatch(m, badfn)
855 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
855 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
856 badmatch)
856 badmatch)
857
857
858 unknownset = set(unknown + forgotten)
858 unknownset = set(unknown + forgotten)
859 toprint = unknownset.copy()
859 toprint = unknownset.copy()
860 toprint.update(deleted)
860 toprint.update(deleted)
861 for abs in sorted(toprint):
861 for abs in sorted(toprint):
862 if repo.ui.verbose or not m.exact(abs):
862 if repo.ui.verbose or not m.exact(abs):
863 if abs in unknownset:
863 if abs in unknownset:
864 status = _('adding %s\n') % m.uipath(abs)
864 status = _('adding %s\n') % m.uipath(abs)
865 else:
865 else:
866 status = _('removing %s\n') % m.uipath(abs)
866 status = _('removing %s\n') % m.uipath(abs)
867 repo.ui.status(status)
867 repo.ui.status(status)
868
868
869 renames = _findrenames(repo, m, added + unknown, removed + deleted,
869 renames = _findrenames(repo, m, added + unknown, removed + deleted,
870 similarity)
870 similarity)
871
871
872 if not dry_run:
872 if not dry_run:
873 _markchanges(repo, unknown + forgotten, deleted, renames)
873 _markchanges(repo, unknown + forgotten, deleted, renames)
874
874
875 for f in rejected:
875 for f in rejected:
876 if f in m.files():
876 if f in m.files():
877 return 1
877 return 1
878 return ret
878 return ret
879
879
880 def marktouched(repo, files, similarity=0.0):
880 def marktouched(repo, files, similarity=0.0):
881 '''Assert that files have somehow been operated upon. files are relative to
881 '''Assert that files have somehow been operated upon. files are relative to
882 the repo root.'''
882 the repo root.'''
883 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
883 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
884 rejected = []
884 rejected = []
885
885
886 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
886 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
887
887
888 if repo.ui.verbose:
888 if repo.ui.verbose:
889 unknownset = set(unknown + forgotten)
889 unknownset = set(unknown + forgotten)
890 toprint = unknownset.copy()
890 toprint = unknownset.copy()
891 toprint.update(deleted)
891 toprint.update(deleted)
892 for abs in sorted(toprint):
892 for abs in sorted(toprint):
893 if abs in unknownset:
893 if abs in unknownset:
894 status = _('adding %s\n') % abs
894 status = _('adding %s\n') % abs
895 else:
895 else:
896 status = _('removing %s\n') % abs
896 status = _('removing %s\n') % abs
897 repo.ui.status(status)
897 repo.ui.status(status)
898
898
899 renames = _findrenames(repo, m, added + unknown, removed + deleted,
899 renames = _findrenames(repo, m, added + unknown, removed + deleted,
900 similarity)
900 similarity)
901
901
902 _markchanges(repo, unknown + forgotten, deleted, renames)
902 _markchanges(repo, unknown + forgotten, deleted, renames)
903
903
904 for f in rejected:
904 for f in rejected:
905 if f in m.files():
905 if f in m.files():
906 return 1
906 return 1
907 return 0
907 return 0
908
908
909 def _interestingfiles(repo, matcher):
909 def _interestingfiles(repo, matcher):
910 '''Walk dirstate with matcher, looking for files that addremove would care
910 '''Walk dirstate with matcher, looking for files that addremove would care
911 about.
911 about.
912
912
913 This is different from dirstate.status because it doesn't care about
913 This is different from dirstate.status because it doesn't care about
914 whether files are modified or clean.'''
914 whether files are modified or clean.'''
915 added, unknown, deleted, removed, forgotten = [], [], [], [], []
915 added, unknown, deleted, removed, forgotten = [], [], [], [], []
916 audit_path = pathutil.pathauditor(repo.root)
916 audit_path = pathutil.pathauditor(repo.root)
917
917
918 ctx = repo[None]
918 ctx = repo[None]
919 dirstate = repo.dirstate
919 dirstate = repo.dirstate
920 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
920 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
921 full=False)
921 full=False)
922 for abs, st in walkresults.iteritems():
922 for abs, st in walkresults.iteritems():
923 dstate = dirstate[abs]
923 dstate = dirstate[abs]
924 if dstate == '?' and audit_path.check(abs):
924 if dstate == '?' and audit_path.check(abs):
925 unknown.append(abs)
925 unknown.append(abs)
926 elif dstate != 'r' and not st:
926 elif dstate != 'r' and not st:
927 deleted.append(abs)
927 deleted.append(abs)
928 elif dstate == 'r' and st:
928 elif dstate == 'r' and st:
929 forgotten.append(abs)
929 forgotten.append(abs)
930 # for finding renames
930 # for finding renames
931 elif dstate == 'r' and not st:
931 elif dstate == 'r' and not st:
932 removed.append(abs)
932 removed.append(abs)
933 elif dstate == 'a':
933 elif dstate == 'a':
934 added.append(abs)
934 added.append(abs)
935
935
936 return added, unknown, deleted, removed, forgotten
936 return added, unknown, deleted, removed, forgotten
937
937
938 def _findrenames(repo, matcher, added, removed, similarity):
938 def _findrenames(repo, matcher, added, removed, similarity):
939 '''Find renames from removed files to added ones.'''
939 '''Find renames from removed files to added ones.'''
940 renames = {}
940 renames = {}
941 if similarity > 0:
941 if similarity > 0:
942 for old, new, score in similar.findrenames(repo, added, removed,
942 for old, new, score in similar.findrenames(repo, added, removed,
943 similarity):
943 similarity):
944 if (repo.ui.verbose or not matcher.exact(old)
944 if (repo.ui.verbose or not matcher.exact(old)
945 or not matcher.exact(new)):
945 or not matcher.exact(new)):
946 repo.ui.status(_('recording removal of %s as rename to %s '
946 repo.ui.status(_('recording removal of %s as rename to %s '
947 '(%d%% similar)\n') %
947 '(%d%% similar)\n') %
948 (matcher.rel(old), matcher.rel(new),
948 (matcher.rel(old), matcher.rel(new),
949 score * 100))
949 score * 100))
950 renames[new] = old
950 renames[new] = old
951 return renames
951 return renames
952
952
953 def _markchanges(repo, unknown, deleted, renames):
953 def _markchanges(repo, unknown, deleted, renames):
954 '''Marks the files in unknown as added, the files in deleted as removed,
954 '''Marks the files in unknown as added, the files in deleted as removed,
955 and the files in renames as copied.'''
955 and the files in renames as copied.'''
956 wctx = repo[None]
956 wctx = repo[None]
957 wlock = repo.wlock()
957 wlock = repo.wlock()
958 try:
958 try:
959 wctx.forget(deleted)
959 wctx.forget(deleted)
960 wctx.add(unknown)
960 wctx.add(unknown)
961 for new, old in renames.iteritems():
961 for new, old in renames.iteritems():
962 wctx.copy(old, new)
962 wctx.copy(old, new)
963 finally:
963 finally:
964 wlock.release()
964 wlock.release()
965
965
966 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
966 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
967 """Update the dirstate to reflect the intent of copying src to dst. For
967 """Update the dirstate to reflect the intent of copying src to dst. For
968 different reasons it might not end with dst being marked as copied from src.
968 different reasons it might not end with dst being marked as copied from src.
969 """
969 """
970 origsrc = repo.dirstate.copied(src) or src
970 origsrc = repo.dirstate.copied(src) or src
971 if dst == origsrc: # copying back a copy?
971 if dst == origsrc: # copying back a copy?
972 if repo.dirstate[dst] not in 'mn' and not dryrun:
972 if repo.dirstate[dst] not in 'mn' and not dryrun:
973 repo.dirstate.normallookup(dst)
973 repo.dirstate.normallookup(dst)
974 else:
974 else:
975 if repo.dirstate[origsrc] == 'a' and origsrc == src:
975 if repo.dirstate[origsrc] == 'a' and origsrc == src:
976 if not ui.quiet:
976 if not ui.quiet:
977 ui.warn(_("%s has not been committed yet, so no copy "
977 ui.warn(_("%s has not been committed yet, so no copy "
978 "data will be stored for %s.\n")
978 "data will be stored for %s.\n")
979 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
979 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
980 if repo.dirstate[dst] in '?r' and not dryrun:
980 if repo.dirstate[dst] in '?r' and not dryrun:
981 wctx.add([dst])
981 wctx.add([dst])
982 elif not dryrun:
982 elif not dryrun:
983 wctx.copy(origsrc, dst)
983 wctx.copy(origsrc, dst)
984
984
985 def readrequires(opener, supported):
985 def readrequires(opener, supported):
986 '''Reads and parses .hg/requires and checks if all entries found
986 '''Reads and parses .hg/requires and checks if all entries found
987 are in the list of supported features.'''
987 are in the list of supported features.'''
988 requirements = set(opener.read("requires").splitlines())
988 requirements = set(opener.read("requires").splitlines())
989 missings = []
989 missings = []
990 for r in requirements:
990 for r in requirements:
991 if r not in supported:
991 if r not in supported:
992 if not r or not r[0].isalnum():
992 if not r or not r[0].isalnum():
993 raise error.RequirementError(_(".hg/requires file is corrupt"))
993 raise error.RequirementError(_(".hg/requires file is corrupt"))
994 missings.append(r)
994 missings.append(r)
995 missings.sort()
995 missings.sort()
996 if missings:
996 if missings:
997 raise error.RequirementError(
997 raise error.RequirementError(
998 _("repository requires features unknown to this Mercurial: %s")
998 _("repository requires features unknown to this Mercurial: %s")
999 % " ".join(missings),
999 % " ".join(missings),
1000 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
1000 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
1001 " for more information"))
1001 " for more information"))
1002 return requirements
1002 return requirements
1003
1003
1004 def writerequires(opener, requirements):
1004 def writerequires(opener, requirements):
1005 reqfile = opener("requires", "w")
1005 reqfile = opener("requires", "w")
1006 for r in sorted(requirements):
1006 for r in sorted(requirements):
1007 reqfile.write("%s\n" % r)
1007 reqfile.write("%s\n" % r)
1008 reqfile.close()
1008 reqfile.close()
1009
1009
1010 class filecachesubentry(object):
1010 class filecachesubentry(object):
1011 def __init__(self, path, stat):
1011 def __init__(self, path, stat):
1012 self.path = path
1012 self.path = path
1013 self.cachestat = None
1013 self.cachestat = None
1014 self._cacheable = None
1014 self._cacheable = None
1015
1015
1016 if stat:
1016 if stat:
1017 self.cachestat = filecachesubentry.stat(self.path)
1017 self.cachestat = filecachesubentry.stat(self.path)
1018
1018
1019 if self.cachestat:
1019 if self.cachestat:
1020 self._cacheable = self.cachestat.cacheable()
1020 self._cacheable = self.cachestat.cacheable()
1021 else:
1021 else:
1022 # None means we don't know yet
1022 # None means we don't know yet
1023 self._cacheable = None
1023 self._cacheable = None
1024
1024
1025 def refresh(self):
1025 def refresh(self):
1026 if self.cacheable():
1026 if self.cacheable():
1027 self.cachestat = filecachesubentry.stat(self.path)
1027 self.cachestat = filecachesubentry.stat(self.path)
1028
1028
1029 def cacheable(self):
1029 def cacheable(self):
1030 if self._cacheable is not None:
1030 if self._cacheable is not None:
1031 return self._cacheable
1031 return self._cacheable
1032
1032
1033 # we don't know yet, assume it is for now
1033 # we don't know yet, assume it is for now
1034 return True
1034 return True
1035
1035
1036 def changed(self):
1036 def changed(self):
1037 # no point in going further if we can't cache it
1037 # no point in going further if we can't cache it
1038 if not self.cacheable():
1038 if not self.cacheable():
1039 return True
1039 return True
1040
1040
1041 newstat = filecachesubentry.stat(self.path)
1041 newstat = filecachesubentry.stat(self.path)
1042
1042
1043 # we may not know if it's cacheable yet, check again now
1043 # we may not know if it's cacheable yet, check again now
1044 if newstat and self._cacheable is None:
1044 if newstat and self._cacheable is None:
1045 self._cacheable = newstat.cacheable()
1045 self._cacheable = newstat.cacheable()
1046
1046
1047 # check again
1047 # check again
1048 if not self._cacheable:
1048 if not self._cacheable:
1049 return True
1049 return True
1050
1050
1051 if self.cachestat != newstat:
1051 if self.cachestat != newstat:
1052 self.cachestat = newstat
1052 self.cachestat = newstat
1053 return True
1053 return True
1054 else:
1054 else:
1055 return False
1055 return False
1056
1056
1057 @staticmethod
1057 @staticmethod
1058 def stat(path):
1058 def stat(path):
1059 try:
1059 try:
1060 return util.cachestat(path)
1060 return util.cachestat(path)
1061 except OSError as e:
1061 except OSError as e:
1062 if e.errno != errno.ENOENT:
1062 if e.errno != errno.ENOENT:
1063 raise
1063 raise
1064
1064
1065 class filecacheentry(object):
1065 class filecacheentry(object):
1066 def __init__(self, paths, stat=True):
1066 def __init__(self, paths, stat=True):
1067 self._entries = []
1067 self._entries = []
1068 for path in paths:
1068 for path in paths:
1069 self._entries.append(filecachesubentry(path, stat))
1069 self._entries.append(filecachesubentry(path, stat))
1070
1070
1071 def changed(self):
1071 def changed(self):
1072 '''true if any entry has changed'''
1072 '''true if any entry has changed'''
1073 for entry in self._entries:
1073 for entry in self._entries:
1074 if entry.changed():
1074 if entry.changed():
1075 return True
1075 return True
1076 return False
1076 return False
1077
1077
1078 def refresh(self):
1078 def refresh(self):
1079 for entry in self._entries:
1079 for entry in self._entries:
1080 entry.refresh()
1080 entry.refresh()
1081
1081
1082 class filecache(object):
1082 class filecache(object):
1083 '''A property like decorator that tracks files under .hg/ for updates.
1083 '''A property like decorator that tracks files under .hg/ for updates.
1084
1084
1085 Records stat info when called in _filecache.
1085 Records stat info when called in _filecache.
1086
1086
1087 On subsequent calls, compares old stat info with new info, and recreates the
1087 On subsequent calls, compares old stat info with new info, and recreates the
1088 object when any of the files changes, updating the new stat info in
1088 object when any of the files changes, updating the new stat info in
1089 _filecache.
1089 _filecache.
1090
1090
1091 Mercurial either atomic renames or appends for files under .hg,
1091 Mercurial either atomic renames or appends for files under .hg,
1092 so to ensure the cache is reliable we need the filesystem to be able
1092 so to ensure the cache is reliable we need the filesystem to be able
1093 to tell us if a file has been replaced. If it can't, we fallback to
1093 to tell us if a file has been replaced. If it can't, we fallback to
1094 recreating the object on every call (essentially the same behaviour as
1094 recreating the object on every call (essentially the same behaviour as
1095 propertycache).
1095 propertycache).
1096
1096
1097 '''
1097 '''
1098 def __init__(self, *paths):
1098 def __init__(self, *paths):
1099 self.paths = paths
1099 self.paths = paths
1100
1100
1101 def join(self, obj, fname):
1101 def join(self, obj, fname):
1102 """Used to compute the runtime path of a cached file.
1102 """Used to compute the runtime path of a cached file.
1103
1103
1104 Users should subclass filecache and provide their own version of this
1104 Users should subclass filecache and provide their own version of this
1105 function to call the appropriate join function on 'obj' (an instance
1105 function to call the appropriate join function on 'obj' (an instance
1106 of the class that its member function was decorated).
1106 of the class that its member function was decorated).
1107 """
1107 """
1108 return obj.join(fname)
1108 return obj.join(fname)
1109
1109
1110 def __call__(self, func):
1110 def __call__(self, func):
1111 self.func = func
1111 self.func = func
1112 self.name = func.__name__
1112 self.name = func.__name__
1113 return self
1113 return self
1114
1114
1115 def __get__(self, obj, type=None):
1115 def __get__(self, obj, type=None):
1116 # do we need to check if the file changed?
1116 # do we need to check if the file changed?
1117 if self.name in obj.__dict__:
1117 if self.name in obj.__dict__:
1118 assert self.name in obj._filecache, self.name
1118 assert self.name in obj._filecache, self.name
1119 return obj.__dict__[self.name]
1119 return obj.__dict__[self.name]
1120
1120
1121 entry = obj._filecache.get(self.name)
1121 entry = obj._filecache.get(self.name)
1122
1122
1123 if entry:
1123 if entry:
1124 if entry.changed():
1124 if entry.changed():
1125 entry.obj = self.func(obj)
1125 entry.obj = self.func(obj)
1126 else:
1126 else:
1127 paths = [self.join(obj, path) for path in self.paths]
1127 paths = [self.join(obj, path) for path in self.paths]
1128
1128
1129 # We stat -before- creating the object so our cache doesn't lie if
1129 # We stat -before- creating the object so our cache doesn't lie if
1130 # a writer modified between the time we read and stat
1130 # a writer modified between the time we read and stat
1131 entry = filecacheentry(paths, True)
1131 entry = filecacheentry(paths, True)
1132 entry.obj = self.func(obj)
1132 entry.obj = self.func(obj)
1133
1133
1134 obj._filecache[self.name] = entry
1134 obj._filecache[self.name] = entry
1135
1135
1136 obj.__dict__[self.name] = entry.obj
1136 obj.__dict__[self.name] = entry.obj
1137 return entry.obj
1137 return entry.obj
1138
1138
1139 def __set__(self, obj, value):
1139 def __set__(self, obj, value):
1140 if self.name not in obj._filecache:
1140 if self.name not in obj._filecache:
1141 # we add an entry for the missing value because X in __dict__
1141 # we add an entry for the missing value because X in __dict__
1142 # implies X in _filecache
1142 # implies X in _filecache
1143 paths = [self.join(obj, path) for path in self.paths]
1143 paths = [self.join(obj, path) for path in self.paths]
1144 ce = filecacheentry(paths, False)
1144 ce = filecacheentry(paths, False)
1145 obj._filecache[self.name] = ce
1145 obj._filecache[self.name] = ce
1146 else:
1146 else:
1147 ce = obj._filecache[self.name]
1147 ce = obj._filecache[self.name]
1148
1148
1149 ce.obj = value # update cached copy
1149 ce.obj = value # update cached copy
1150 obj.__dict__[self.name] = value # update copy returned by obj.x
1150 obj.__dict__[self.name] = value # update copy returned by obj.x
1151
1151
1152 def __delete__(self, obj):
1152 def __delete__(self, obj):
1153 try:
1153 try:
1154 del obj.__dict__[self.name]
1154 del obj.__dict__[self.name]
1155 except KeyError:
1155 except KeyError:
1156 raise AttributeError(self.name)
1156 raise AttributeError(self.name)
@@ -1,2052 +1,2053 b''
1 Log on empty repository: checking consistency
1 Log on empty repository: checking consistency
2
2
3 $ hg init empty
3 $ hg init empty
4 $ cd empty
4 $ cd empty
5 $ hg log
5 $ hg log
6 $ hg log -r 1
6 $ hg log -r 1
7 abort: unknown revision '1'!
7 abort: unknown revision '1'!
8 [255]
8 [255]
9 $ hg log -r -1:0
9 $ hg log -r -1:0
10 abort: unknown revision '-1'!
10 abort: unknown revision '-1'!
11 [255]
11 [255]
12 $ hg log -r 'branch(name)'
12 $ hg log -r 'branch(name)'
13 abort: unknown revision 'name'!
13 abort: unknown revision 'name'!
14 [255]
14 [255]
15 $ hg log -r null -q
15 $ hg log -r null -q
16 -1:000000000000
16 -1:000000000000
17
17
18 The g is crafted to have 2 filelog topological heads in a linear
18 The g is crafted to have 2 filelog topological heads in a linear
19 changeset graph
19 changeset graph
20
20
21 $ hg init a
21 $ hg init a
22 $ cd a
22 $ cd a
23 $ echo a > a
23 $ echo a > a
24 $ echo f > f
24 $ echo f > f
25 $ hg ci -Ama -d '1 0'
25 $ hg ci -Ama -d '1 0'
26 adding a
26 adding a
27 adding f
27 adding f
28
28
29 $ hg cp a b
29 $ hg cp a b
30 $ hg cp f g
30 $ hg cp f g
31 $ hg ci -mb -d '2 0'
31 $ hg ci -mb -d '2 0'
32
32
33 $ mkdir dir
33 $ mkdir dir
34 $ hg mv b dir
34 $ hg mv b dir
35 $ echo g >> g
35 $ echo g >> g
36 $ echo f >> f
36 $ echo f >> f
37 $ hg ci -mc -d '3 0'
37 $ hg ci -mc -d '3 0'
38
38
39 $ hg mv a b
39 $ hg mv a b
40 $ hg cp -f f g
40 $ hg cp -f f g
41 $ echo a > d
41 $ echo a > d
42 $ hg add d
42 $ hg add d
43 $ hg ci -md -d '4 0'
43 $ hg ci -md -d '4 0'
44
44
45 $ hg mv dir/b e
45 $ hg mv dir/b e
46 $ hg ci -me -d '5 0'
46 $ hg ci -me -d '5 0'
47
47
48 Make sure largefiles doesn't interfere with logging a regular file
48 Make sure largefiles doesn't interfere with logging a regular file
49 $ hg --debug log a -T '{rev}: {desc}\n' --config extensions.largefiles=
49 $ hg --debug log a -T '{rev}: {desc}\n' --config extensions.largefiles=
50 updated patterns: ['.hglf/a', 'a']
50 updated patterns: ['.hglf/a', 'a']
51 0: a
51 0: a
52 $ hg log a
52 $ hg log a
53 changeset: 0:9161b9aeaf16
53 changeset: 0:9161b9aeaf16
54 user: test
54 user: test
55 date: Thu Jan 01 00:00:01 1970 +0000
55 date: Thu Jan 01 00:00:01 1970 +0000
56 summary: a
56 summary: a
57
57
58 $ hg log glob:a*
58 $ hg log glob:a*
59 changeset: 3:2ca5ba701980
59 changeset: 3:2ca5ba701980
60 user: test
60 user: test
61 date: Thu Jan 01 00:00:04 1970 +0000
61 date: Thu Jan 01 00:00:04 1970 +0000
62 summary: d
62 summary: d
63
63
64 changeset: 0:9161b9aeaf16
64 changeset: 0:9161b9aeaf16
65 user: test
65 user: test
66 date: Thu Jan 01 00:00:01 1970 +0000
66 date: Thu Jan 01 00:00:01 1970 +0000
67 summary: a
67 summary: a
68
68
69 $ hg --debug log glob:a* -T '{rev}: {desc}\n' --config extensions.largefiles=
69 $ hg --debug log glob:a* -T '{rev}: {desc}\n' --config extensions.largefiles=
70 updated patterns: ['glob:.hglf/a*', 'glob:a*']
70 updated patterns: ['glob:.hglf/a*', 'glob:a*']
71 3: d
71 3: d
72 0: a
72 0: a
73
73
74 log on directory
74 log on directory
75
75
76 $ hg log dir
76 $ hg log dir
77 changeset: 4:7e4639b4691b
77 changeset: 4:7e4639b4691b
78 tag: tip
78 tag: tip
79 user: test
79 user: test
80 date: Thu Jan 01 00:00:05 1970 +0000
80 date: Thu Jan 01 00:00:05 1970 +0000
81 summary: e
81 summary: e
82
82
83 changeset: 2:f8954cd4dc1f
83 changeset: 2:f8954cd4dc1f
84 user: test
84 user: test
85 date: Thu Jan 01 00:00:03 1970 +0000
85 date: Thu Jan 01 00:00:03 1970 +0000
86 summary: c
86 summary: c
87
87
88 $ hg log somethingthatdoesntexist dir
88 $ hg log somethingthatdoesntexist dir
89 changeset: 4:7e4639b4691b
89 changeset: 4:7e4639b4691b
90 tag: tip
90 tag: tip
91 user: test
91 user: test
92 date: Thu Jan 01 00:00:05 1970 +0000
92 date: Thu Jan 01 00:00:05 1970 +0000
93 summary: e
93 summary: e
94
94
95 changeset: 2:f8954cd4dc1f
95 changeset: 2:f8954cd4dc1f
96 user: test
96 user: test
97 date: Thu Jan 01 00:00:03 1970 +0000
97 date: Thu Jan 01 00:00:03 1970 +0000
98 summary: c
98 summary: c
99
99
100
100
101 -f, non-existent directory
101 -f, non-existent directory
102
102
103 $ hg log -f dir
103 $ hg log -f dir
104 abort: cannot follow file not in parent revision: "dir"
104 abort: cannot follow file not in parent revision: "dir"
105 [255]
105 [255]
106
106
107 -f, directory
107 -f, directory
108
108
109 $ hg up -q 3
109 $ hg up -q 3
110 $ hg log -f dir
110 $ hg log -f dir
111 changeset: 2:f8954cd4dc1f
111 changeset: 2:f8954cd4dc1f
112 user: test
112 user: test
113 date: Thu Jan 01 00:00:03 1970 +0000
113 date: Thu Jan 01 00:00:03 1970 +0000
114 summary: c
114 summary: c
115
115
116 -f, directory with --patch
116 -f, directory with --patch
117
117
118 $ hg log -f dir -p
118 $ hg log -f dir -p
119 changeset: 2:f8954cd4dc1f
119 changeset: 2:f8954cd4dc1f
120 user: test
120 user: test
121 date: Thu Jan 01 00:00:03 1970 +0000
121 date: Thu Jan 01 00:00:03 1970 +0000
122 summary: c
122 summary: c
123
123
124 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
124 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
125 --- /dev/null* (glob)
125 --- /dev/null* (glob)
126 +++ b/dir/b* (glob)
126 +++ b/dir/b* (glob)
127 @@ -0,0 +1,1 @@
127 @@ -0,0 +1,1 @@
128 +a
128 +a
129
129
130
130
131 -f, pattern
131 -f, pattern
132
132
133 $ hg log -f -I 'dir**' -p
133 $ hg log -f -I 'dir**' -p
134 changeset: 2:f8954cd4dc1f
134 changeset: 2:f8954cd4dc1f
135 user: test
135 user: test
136 date: Thu Jan 01 00:00:03 1970 +0000
136 date: Thu Jan 01 00:00:03 1970 +0000
137 summary: c
137 summary: c
138
138
139 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
139 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
140 --- /dev/null* (glob)
140 --- /dev/null* (glob)
141 +++ b/dir/b* (glob)
141 +++ b/dir/b* (glob)
142 @@ -0,0 +1,1 @@
142 @@ -0,0 +1,1 @@
143 +a
143 +a
144
144
145 $ hg up -q 4
145 $ hg up -q 4
146
146
147 -f, a wrong style
147 -f, a wrong style
148
148
149 $ hg log -f -l1 --style something
149 $ hg log -f -l1 --style something
150 abort: style 'something' not found
150 abort: style 'something' not found
151 (available styles: bisect, changelog, compact, default, phases, status, xml)
151 (available styles: bisect, changelog, compact, default, phases, status, xml)
152 [255]
152 [255]
153
153
154 -f, phases style
154 -f, phases style
155
155
156
156
157 $ hg log -f -l1 --style phases
157 $ hg log -f -l1 --style phases
158 changeset: 4:7e4639b4691b
158 changeset: 4:7e4639b4691b
159 tag: tip
159 tag: tip
160 phase: draft
160 phase: draft
161 user: test
161 user: test
162 date: Thu Jan 01 00:00:05 1970 +0000
162 date: Thu Jan 01 00:00:05 1970 +0000
163 summary: e
163 summary: e
164
164
165
165
166 $ hg log -f -l1 --style phases -q
166 $ hg log -f -l1 --style phases -q
167 4:7e4639b4691b
167 4:7e4639b4691b
168
168
169 -f, but no args
169 -f, but no args
170
170
171 $ hg log -f
171 $ hg log -f
172 changeset: 4:7e4639b4691b
172 changeset: 4:7e4639b4691b
173 tag: tip
173 tag: tip
174 user: test
174 user: test
175 date: Thu Jan 01 00:00:05 1970 +0000
175 date: Thu Jan 01 00:00:05 1970 +0000
176 summary: e
176 summary: e
177
177
178 changeset: 3:2ca5ba701980
178 changeset: 3:2ca5ba701980
179 user: test
179 user: test
180 date: Thu Jan 01 00:00:04 1970 +0000
180 date: Thu Jan 01 00:00:04 1970 +0000
181 summary: d
181 summary: d
182
182
183 changeset: 2:f8954cd4dc1f
183 changeset: 2:f8954cd4dc1f
184 user: test
184 user: test
185 date: Thu Jan 01 00:00:03 1970 +0000
185 date: Thu Jan 01 00:00:03 1970 +0000
186 summary: c
186 summary: c
187
187
188 changeset: 1:d89b0a12d229
188 changeset: 1:d89b0a12d229
189 user: test
189 user: test
190 date: Thu Jan 01 00:00:02 1970 +0000
190 date: Thu Jan 01 00:00:02 1970 +0000
191 summary: b
191 summary: b
192
192
193 changeset: 0:9161b9aeaf16
193 changeset: 0:9161b9aeaf16
194 user: test
194 user: test
195 date: Thu Jan 01 00:00:01 1970 +0000
195 date: Thu Jan 01 00:00:01 1970 +0000
196 summary: a
196 summary: a
197
197
198
198
199 one rename
199 one rename
200
200
201 $ hg up -q 2
201 $ hg up -q 2
202 $ hg log -vf a
202 $ hg log -vf a
203 changeset: 0:9161b9aeaf16
203 changeset: 0:9161b9aeaf16
204 user: test
204 user: test
205 date: Thu Jan 01 00:00:01 1970 +0000
205 date: Thu Jan 01 00:00:01 1970 +0000
206 files: a f
206 files: a f
207 description:
207 description:
208 a
208 a
209
209
210
210
211
211
212 many renames
212 many renames
213
213
214 $ hg up -q tip
214 $ hg up -q tip
215 $ hg log -vf e
215 $ hg log -vf e
216 changeset: 4:7e4639b4691b
216 changeset: 4:7e4639b4691b
217 tag: tip
217 tag: tip
218 user: test
218 user: test
219 date: Thu Jan 01 00:00:05 1970 +0000
219 date: Thu Jan 01 00:00:05 1970 +0000
220 files: dir/b e
220 files: dir/b e
221 description:
221 description:
222 e
222 e
223
223
224
224
225 changeset: 2:f8954cd4dc1f
225 changeset: 2:f8954cd4dc1f
226 user: test
226 user: test
227 date: Thu Jan 01 00:00:03 1970 +0000
227 date: Thu Jan 01 00:00:03 1970 +0000
228 files: b dir/b f g
228 files: b dir/b f g
229 description:
229 description:
230 c
230 c
231
231
232
232
233 changeset: 1:d89b0a12d229
233 changeset: 1:d89b0a12d229
234 user: test
234 user: test
235 date: Thu Jan 01 00:00:02 1970 +0000
235 date: Thu Jan 01 00:00:02 1970 +0000
236 files: b g
236 files: b g
237 description:
237 description:
238 b
238 b
239
239
240
240
241 changeset: 0:9161b9aeaf16
241 changeset: 0:9161b9aeaf16
242 user: test
242 user: test
243 date: Thu Jan 01 00:00:01 1970 +0000
243 date: Thu Jan 01 00:00:01 1970 +0000
244 files: a f
244 files: a f
245 description:
245 description:
246 a
246 a
247
247
248
248
249
249
250
250
251 log -pf dir/b
251 log -pf dir/b
252
252
253 $ hg up -q 3
253 $ hg up -q 3
254 $ hg log -pf dir/b
254 $ hg log -pf dir/b
255 changeset: 2:f8954cd4dc1f
255 changeset: 2:f8954cd4dc1f
256 user: test
256 user: test
257 date: Thu Jan 01 00:00:03 1970 +0000
257 date: Thu Jan 01 00:00:03 1970 +0000
258 summary: c
258 summary: c
259
259
260 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
260 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
261 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
261 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
262 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
262 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
263 @@ -0,0 +1,1 @@
263 @@ -0,0 +1,1 @@
264 +a
264 +a
265
265
266 changeset: 1:d89b0a12d229
266 changeset: 1:d89b0a12d229
267 user: test
267 user: test
268 date: Thu Jan 01 00:00:02 1970 +0000
268 date: Thu Jan 01 00:00:02 1970 +0000
269 summary: b
269 summary: b
270
270
271 diff -r 9161b9aeaf16 -r d89b0a12d229 b
271 diff -r 9161b9aeaf16 -r d89b0a12d229 b
272 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
272 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
273 +++ b/b Thu Jan 01 00:00:02 1970 +0000
273 +++ b/b Thu Jan 01 00:00:02 1970 +0000
274 @@ -0,0 +1,1 @@
274 @@ -0,0 +1,1 @@
275 +a
275 +a
276
276
277 changeset: 0:9161b9aeaf16
277 changeset: 0:9161b9aeaf16
278 user: test
278 user: test
279 date: Thu Jan 01 00:00:01 1970 +0000
279 date: Thu Jan 01 00:00:01 1970 +0000
280 summary: a
280 summary: a
281
281
282 diff -r 000000000000 -r 9161b9aeaf16 a
282 diff -r 000000000000 -r 9161b9aeaf16 a
283 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
283 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
284 +++ b/a Thu Jan 01 00:00:01 1970 +0000
284 +++ b/a Thu Jan 01 00:00:01 1970 +0000
285 @@ -0,0 +1,1 @@
285 @@ -0,0 +1,1 @@
286 +a
286 +a
287
287
288
288
289 log -pf b inside dir
289 log -pf b inside dir
290
290
291 $ hg --cwd=dir log -pf b
291 $ hg --cwd=dir log -pf b
292 changeset: 2:f8954cd4dc1f
292 changeset: 2:f8954cd4dc1f
293 user: test
293 user: test
294 date: Thu Jan 01 00:00:03 1970 +0000
294 date: Thu Jan 01 00:00:03 1970 +0000
295 summary: c
295 summary: c
296
296
297 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
297 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
298 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
298 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
299 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
299 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
300 @@ -0,0 +1,1 @@
300 @@ -0,0 +1,1 @@
301 +a
301 +a
302
302
303 changeset: 1:d89b0a12d229
303 changeset: 1:d89b0a12d229
304 user: test
304 user: test
305 date: Thu Jan 01 00:00:02 1970 +0000
305 date: Thu Jan 01 00:00:02 1970 +0000
306 summary: b
306 summary: b
307
307
308 diff -r 9161b9aeaf16 -r d89b0a12d229 b
308 diff -r 9161b9aeaf16 -r d89b0a12d229 b
309 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
309 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
310 +++ b/b Thu Jan 01 00:00:02 1970 +0000
310 +++ b/b Thu Jan 01 00:00:02 1970 +0000
311 @@ -0,0 +1,1 @@
311 @@ -0,0 +1,1 @@
312 +a
312 +a
313
313
314 changeset: 0:9161b9aeaf16
314 changeset: 0:9161b9aeaf16
315 user: test
315 user: test
316 date: Thu Jan 01 00:00:01 1970 +0000
316 date: Thu Jan 01 00:00:01 1970 +0000
317 summary: a
317 summary: a
318
318
319 diff -r 000000000000 -r 9161b9aeaf16 a
319 diff -r 000000000000 -r 9161b9aeaf16 a
320 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
320 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
321 +++ b/a Thu Jan 01 00:00:01 1970 +0000
321 +++ b/a Thu Jan 01 00:00:01 1970 +0000
322 @@ -0,0 +1,1 @@
322 @@ -0,0 +1,1 @@
323 +a
323 +a
324
324
325
325
326 log -pf, but no args
326 log -pf, but no args
327
327
328 $ hg log -pf
328 $ hg log -pf
329 changeset: 3:2ca5ba701980
329 changeset: 3:2ca5ba701980
330 user: test
330 user: test
331 date: Thu Jan 01 00:00:04 1970 +0000
331 date: Thu Jan 01 00:00:04 1970 +0000
332 summary: d
332 summary: d
333
333
334 diff -r f8954cd4dc1f -r 2ca5ba701980 a
334 diff -r f8954cd4dc1f -r 2ca5ba701980 a
335 --- a/a Thu Jan 01 00:00:03 1970 +0000
335 --- a/a Thu Jan 01 00:00:03 1970 +0000
336 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
336 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
337 @@ -1,1 +0,0 @@
337 @@ -1,1 +0,0 @@
338 -a
338 -a
339 diff -r f8954cd4dc1f -r 2ca5ba701980 b
339 diff -r f8954cd4dc1f -r 2ca5ba701980 b
340 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
340 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
341 +++ b/b Thu Jan 01 00:00:04 1970 +0000
341 +++ b/b Thu Jan 01 00:00:04 1970 +0000
342 @@ -0,0 +1,1 @@
342 @@ -0,0 +1,1 @@
343 +a
343 +a
344 diff -r f8954cd4dc1f -r 2ca5ba701980 d
344 diff -r f8954cd4dc1f -r 2ca5ba701980 d
345 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
345 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
346 +++ b/d Thu Jan 01 00:00:04 1970 +0000
346 +++ b/d Thu Jan 01 00:00:04 1970 +0000
347 @@ -0,0 +1,1 @@
347 @@ -0,0 +1,1 @@
348 +a
348 +a
349 diff -r f8954cd4dc1f -r 2ca5ba701980 g
349 diff -r f8954cd4dc1f -r 2ca5ba701980 g
350 --- a/g Thu Jan 01 00:00:03 1970 +0000
350 --- a/g Thu Jan 01 00:00:03 1970 +0000
351 +++ b/g Thu Jan 01 00:00:04 1970 +0000
351 +++ b/g Thu Jan 01 00:00:04 1970 +0000
352 @@ -1,2 +1,2 @@
352 @@ -1,2 +1,2 @@
353 f
353 f
354 -g
354 -g
355 +f
355 +f
356
356
357 changeset: 2:f8954cd4dc1f
357 changeset: 2:f8954cd4dc1f
358 user: test
358 user: test
359 date: Thu Jan 01 00:00:03 1970 +0000
359 date: Thu Jan 01 00:00:03 1970 +0000
360 summary: c
360 summary: c
361
361
362 diff -r d89b0a12d229 -r f8954cd4dc1f b
362 diff -r d89b0a12d229 -r f8954cd4dc1f b
363 --- a/b Thu Jan 01 00:00:02 1970 +0000
363 --- a/b Thu Jan 01 00:00:02 1970 +0000
364 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
364 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
365 @@ -1,1 +0,0 @@
365 @@ -1,1 +0,0 @@
366 -a
366 -a
367 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
367 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
369 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
369 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
370 @@ -0,0 +1,1 @@
370 @@ -0,0 +1,1 @@
371 +a
371 +a
372 diff -r d89b0a12d229 -r f8954cd4dc1f f
372 diff -r d89b0a12d229 -r f8954cd4dc1f f
373 --- a/f Thu Jan 01 00:00:02 1970 +0000
373 --- a/f Thu Jan 01 00:00:02 1970 +0000
374 +++ b/f Thu Jan 01 00:00:03 1970 +0000
374 +++ b/f Thu Jan 01 00:00:03 1970 +0000
375 @@ -1,1 +1,2 @@
375 @@ -1,1 +1,2 @@
376 f
376 f
377 +f
377 +f
378 diff -r d89b0a12d229 -r f8954cd4dc1f g
378 diff -r d89b0a12d229 -r f8954cd4dc1f g
379 --- a/g Thu Jan 01 00:00:02 1970 +0000
379 --- a/g Thu Jan 01 00:00:02 1970 +0000
380 +++ b/g Thu Jan 01 00:00:03 1970 +0000
380 +++ b/g Thu Jan 01 00:00:03 1970 +0000
381 @@ -1,1 +1,2 @@
381 @@ -1,1 +1,2 @@
382 f
382 f
383 +g
383 +g
384
384
385 changeset: 1:d89b0a12d229
385 changeset: 1:d89b0a12d229
386 user: test
386 user: test
387 date: Thu Jan 01 00:00:02 1970 +0000
387 date: Thu Jan 01 00:00:02 1970 +0000
388 summary: b
388 summary: b
389
389
390 diff -r 9161b9aeaf16 -r d89b0a12d229 b
390 diff -r 9161b9aeaf16 -r d89b0a12d229 b
391 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
391 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
392 +++ b/b Thu Jan 01 00:00:02 1970 +0000
392 +++ b/b Thu Jan 01 00:00:02 1970 +0000
393 @@ -0,0 +1,1 @@
393 @@ -0,0 +1,1 @@
394 +a
394 +a
395 diff -r 9161b9aeaf16 -r d89b0a12d229 g
395 diff -r 9161b9aeaf16 -r d89b0a12d229 g
396 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
396 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
397 +++ b/g Thu Jan 01 00:00:02 1970 +0000
397 +++ b/g Thu Jan 01 00:00:02 1970 +0000
398 @@ -0,0 +1,1 @@
398 @@ -0,0 +1,1 @@
399 +f
399 +f
400
400
401 changeset: 0:9161b9aeaf16
401 changeset: 0:9161b9aeaf16
402 user: test
402 user: test
403 date: Thu Jan 01 00:00:01 1970 +0000
403 date: Thu Jan 01 00:00:01 1970 +0000
404 summary: a
404 summary: a
405
405
406 diff -r 000000000000 -r 9161b9aeaf16 a
406 diff -r 000000000000 -r 9161b9aeaf16 a
407 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
407 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
408 +++ b/a Thu Jan 01 00:00:01 1970 +0000
408 +++ b/a Thu Jan 01 00:00:01 1970 +0000
409 @@ -0,0 +1,1 @@
409 @@ -0,0 +1,1 @@
410 +a
410 +a
411 diff -r 000000000000 -r 9161b9aeaf16 f
411 diff -r 000000000000 -r 9161b9aeaf16 f
412 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
412 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
413 +++ b/f Thu Jan 01 00:00:01 1970 +0000
413 +++ b/f Thu Jan 01 00:00:01 1970 +0000
414 @@ -0,0 +1,1 @@
414 @@ -0,0 +1,1 @@
415 +f
415 +f
416
416
417
417
418 log -vf dir/b
418 log -vf dir/b
419
419
420 $ hg log -vf dir/b
420 $ hg log -vf dir/b
421 changeset: 2:f8954cd4dc1f
421 changeset: 2:f8954cd4dc1f
422 user: test
422 user: test
423 date: Thu Jan 01 00:00:03 1970 +0000
423 date: Thu Jan 01 00:00:03 1970 +0000
424 files: b dir/b f g
424 files: b dir/b f g
425 description:
425 description:
426 c
426 c
427
427
428
428
429 changeset: 1:d89b0a12d229
429 changeset: 1:d89b0a12d229
430 user: test
430 user: test
431 date: Thu Jan 01 00:00:02 1970 +0000
431 date: Thu Jan 01 00:00:02 1970 +0000
432 files: b g
432 files: b g
433 description:
433 description:
434 b
434 b
435
435
436
436
437 changeset: 0:9161b9aeaf16
437 changeset: 0:9161b9aeaf16
438 user: test
438 user: test
439 date: Thu Jan 01 00:00:01 1970 +0000
439 date: Thu Jan 01 00:00:01 1970 +0000
440 files: a f
440 files: a f
441 description:
441 description:
442 a
442 a
443
443
444
444
445
445
446
446
447 -f and multiple filelog heads
447 -f and multiple filelog heads
448
448
449 $ hg up -q 2
449 $ hg up -q 2
450 $ hg log -f g --template '{rev}\n'
450 $ hg log -f g --template '{rev}\n'
451 2
451 2
452 1
452 1
453 0
453 0
454 $ hg up -q tip
454 $ hg up -q tip
455 $ hg log -f g --template '{rev}\n'
455 $ hg log -f g --template '{rev}\n'
456 3
456 3
457 2
457 2
458 0
458 0
459
459
460
460
461 log copies with --copies
461 log copies with --copies
462
462
463 $ hg log -vC --template '{rev} {file_copies}\n'
463 $ hg log -vC --template '{rev} {file_copies}\n'
464 4 e (dir/b)
464 4 e (dir/b)
465 3 b (a)g (f)
465 3 b (a)g (f)
466 2 dir/b (b)
466 2 dir/b (b)
467 1 b (a)g (f)
467 1 b (a)g (f)
468 0
468 0
469
469
470 log copies switch without --copies, with old filecopy template
470 log copies switch without --copies, with old filecopy template
471
471
472 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
472 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
473 4
473 4
474 3
474 3
475 2
475 2
476 1
476 1
477 0
477 0
478
478
479 log copies switch with --copies
479 log copies switch with --copies
480
480
481 $ hg log -vC --template '{rev} {file_copies_switch}\n'
481 $ hg log -vC --template '{rev} {file_copies_switch}\n'
482 4 e (dir/b)
482 4 e (dir/b)
483 3 b (a)g (f)
483 3 b (a)g (f)
484 2 dir/b (b)
484 2 dir/b (b)
485 1 b (a)g (f)
485 1 b (a)g (f)
486 0
486 0
487
487
488
488
489 log copies with hardcoded style and with --style=default
489 log copies with hardcoded style and with --style=default
490
490
491 $ hg log -vC -r4
491 $ hg log -vC -r4
492 changeset: 4:7e4639b4691b
492 changeset: 4:7e4639b4691b
493 tag: tip
493 tag: tip
494 user: test
494 user: test
495 date: Thu Jan 01 00:00:05 1970 +0000
495 date: Thu Jan 01 00:00:05 1970 +0000
496 files: dir/b e
496 files: dir/b e
497 copies: e (dir/b)
497 copies: e (dir/b)
498 description:
498 description:
499 e
499 e
500
500
501
501
502 $ hg log -vC -r4 --style=default
502 $ hg log -vC -r4 --style=default
503 changeset: 4:7e4639b4691b
503 changeset: 4:7e4639b4691b
504 tag: tip
504 tag: tip
505 user: test
505 user: test
506 date: Thu Jan 01 00:00:05 1970 +0000
506 date: Thu Jan 01 00:00:05 1970 +0000
507 files: dir/b e
507 files: dir/b e
508 copies: e (dir/b)
508 copies: e (dir/b)
509 description:
509 description:
510 e
510 e
511
511
512
512
513 $ hg log -vC -r4 -Tjson
513 $ hg log -vC -r4 -Tjson
514 [
514 [
515 {
515 {
516 "rev": 4,
516 "rev": 4,
517 "node": "7e4639b4691b9f84b81036a8d4fb218ce3c5e3a3",
517 "node": "7e4639b4691b9f84b81036a8d4fb218ce3c5e3a3",
518 "branch": "default",
518 "branch": "default",
519 "phase": "draft",
519 "phase": "draft",
520 "user": "test",
520 "user": "test",
521 "date": [5, 0],
521 "date": [5, 0],
522 "desc": "e",
522 "desc": "e",
523 "bookmarks": [],
523 "bookmarks": [],
524 "tags": ["tip"],
524 "tags": ["tip"],
525 "parents": ["2ca5ba7019804f1f597249caddf22a64d34df0ba"],
525 "parents": ["2ca5ba7019804f1f597249caddf22a64d34df0ba"],
526 "files": ["dir/b", "e"],
526 "files": ["dir/b", "e"],
527 "copies": {"e": "dir/b"}
527 "copies": {"e": "dir/b"}
528 }
528 }
529 ]
529 ]
530
530
531 log copies, non-linear manifest
531 log copies, non-linear manifest
532
532
533 $ hg up -C 3
533 $ hg up -C 3
534 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
534 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
535 $ hg mv dir/b e
535 $ hg mv dir/b e
536 $ echo foo > foo
536 $ echo foo > foo
537 $ hg ci -Ame2 -d '6 0'
537 $ hg ci -Ame2 -d '6 0'
538 adding foo
538 adding foo
539 created new head
539 created new head
540 $ hg log -v --template '{rev} {file_copies}\n' -r 5
540 $ hg log -v --template '{rev} {file_copies}\n' -r 5
541 5 e (dir/b)
541 5 e (dir/b)
542
542
543
543
544 log copies, execute bit set
544 log copies, execute bit set
545
545
546 #if execbit
546 #if execbit
547 $ chmod +x e
547 $ chmod +x e
548 $ hg ci -me3 -d '7 0'
548 $ hg ci -me3 -d '7 0'
549 $ hg log -v --template '{rev} {file_copies}\n' -r 6
549 $ hg log -v --template '{rev} {file_copies}\n' -r 6
550 6
550 6
551 #endif
551 #endif
552
552
553
553
554 log -p d
554 log -p d
555
555
556 $ hg log -pv d
556 $ hg log -pv d
557 changeset: 3:2ca5ba701980
557 changeset: 3:2ca5ba701980
558 user: test
558 user: test
559 date: Thu Jan 01 00:00:04 1970 +0000
559 date: Thu Jan 01 00:00:04 1970 +0000
560 files: a b d g
560 files: a b d g
561 description:
561 description:
562 d
562 d
563
563
564
564
565 diff -r f8954cd4dc1f -r 2ca5ba701980 d
565 diff -r f8954cd4dc1f -r 2ca5ba701980 d
566 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
566 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
567 +++ b/d Thu Jan 01 00:00:04 1970 +0000
567 +++ b/d Thu Jan 01 00:00:04 1970 +0000
568 @@ -0,0 +1,1 @@
568 @@ -0,0 +1,1 @@
569 +a
569 +a
570
570
571
571
572
572
573 log --removed file
573 log --removed file
574
574
575 $ hg log --removed -v a
575 $ hg log --removed -v a
576 changeset: 3:2ca5ba701980
576 changeset: 3:2ca5ba701980
577 user: test
577 user: test
578 date: Thu Jan 01 00:00:04 1970 +0000
578 date: Thu Jan 01 00:00:04 1970 +0000
579 files: a b d g
579 files: a b d g
580 description:
580 description:
581 d
581 d
582
582
583
583
584 changeset: 0:9161b9aeaf16
584 changeset: 0:9161b9aeaf16
585 user: test
585 user: test
586 date: Thu Jan 01 00:00:01 1970 +0000
586 date: Thu Jan 01 00:00:01 1970 +0000
587 files: a f
587 files: a f
588 description:
588 description:
589 a
589 a
590
590
591
591
592
592
593 log --removed revrange file
593 log --removed revrange file
594
594
595 $ hg log --removed -v -r0:2 a
595 $ hg log --removed -v -r0:2 a
596 changeset: 0:9161b9aeaf16
596 changeset: 0:9161b9aeaf16
597 user: test
597 user: test
598 date: Thu Jan 01 00:00:01 1970 +0000
598 date: Thu Jan 01 00:00:01 1970 +0000
599 files: a f
599 files: a f
600 description:
600 description:
601 a
601 a
602
602
603
603
604 $ cd ..
604 $ cd ..
605
605
606 log --follow tests
606 log --follow tests
607
607
608 $ hg init follow
608 $ hg init follow
609 $ cd follow
609 $ cd follow
610
610
611 $ echo base > base
611 $ echo base > base
612 $ hg ci -Ambase -d '1 0'
612 $ hg ci -Ambase -d '1 0'
613 adding base
613 adding base
614
614
615 $ echo r1 >> base
615 $ echo r1 >> base
616 $ hg ci -Amr1 -d '1 0'
616 $ hg ci -Amr1 -d '1 0'
617 $ echo r2 >> base
617 $ echo r2 >> base
618 $ hg ci -Amr2 -d '1 0'
618 $ hg ci -Amr2 -d '1 0'
619
619
620 $ hg up -C 1
620 $ hg up -C 1
621 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
621 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
622 $ echo b1 > b1
622 $ echo b1 > b1
623 $ hg ci -Amb1 -d '1 0'
623 $ hg ci -Amb1 -d '1 0'
624 adding b1
624 adding b1
625 created new head
625 created new head
626
626
627
627
628 log -f
628 log -f
629
629
630 $ hg log -f
630 $ hg log -f
631 changeset: 3:e62f78d544b4
631 changeset: 3:e62f78d544b4
632 tag: tip
632 tag: tip
633 parent: 1:3d5bf5654eda
633 parent: 1:3d5bf5654eda
634 user: test
634 user: test
635 date: Thu Jan 01 00:00:01 1970 +0000
635 date: Thu Jan 01 00:00:01 1970 +0000
636 summary: b1
636 summary: b1
637
637
638 changeset: 1:3d5bf5654eda
638 changeset: 1:3d5bf5654eda
639 user: test
639 user: test
640 date: Thu Jan 01 00:00:01 1970 +0000
640 date: Thu Jan 01 00:00:01 1970 +0000
641 summary: r1
641 summary: r1
642
642
643 changeset: 0:67e992f2c4f3
643 changeset: 0:67e992f2c4f3
644 user: test
644 user: test
645 date: Thu Jan 01 00:00:01 1970 +0000
645 date: Thu Jan 01 00:00:01 1970 +0000
646 summary: base
646 summary: base
647
647
648
648
649
649
650 log -f -r '1 + 4'
650 log -f -r '1 + 4'
651
651
652 $ hg up -C 0
652 $ hg up -C 0
653 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
653 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
654 $ echo b2 > b2
654 $ echo b2 > b2
655 $ hg ci -Amb2 -d '1 0'
655 $ hg ci -Amb2 -d '1 0'
656 adding b2
656 adding b2
657 created new head
657 created new head
658 $ hg log -f -r '1 + 4'
658 $ hg log -f -r '1 + 4'
659 changeset: 4:ddb82e70d1a1
659 changeset: 4:ddb82e70d1a1
660 tag: tip
660 tag: tip
661 parent: 0:67e992f2c4f3
661 parent: 0:67e992f2c4f3
662 user: test
662 user: test
663 date: Thu Jan 01 00:00:01 1970 +0000
663 date: Thu Jan 01 00:00:01 1970 +0000
664 summary: b2
664 summary: b2
665
665
666 changeset: 1:3d5bf5654eda
666 changeset: 1:3d5bf5654eda
667 user: test
667 user: test
668 date: Thu Jan 01 00:00:01 1970 +0000
668 date: Thu Jan 01 00:00:01 1970 +0000
669 summary: r1
669 summary: r1
670
670
671 changeset: 0:67e992f2c4f3
671 changeset: 0:67e992f2c4f3
672 user: test
672 user: test
673 date: Thu Jan 01 00:00:01 1970 +0000
673 date: Thu Jan 01 00:00:01 1970 +0000
674 summary: base
674 summary: base
675
675
676 log -f -r null
676 log -f -r null
677
677
678 $ hg log -f -r null
678 $ hg log -f -r null
679 changeset: -1:000000000000
679 changeset: -1:000000000000
680 user:
680 user:
681 date: Thu Jan 01 00:00:00 1970 +0000
681 date: Thu Jan 01 00:00:00 1970 +0000
682
682
683 $ hg log -f -r null -G
683 $ hg log -f -r null -G
684 o changeset: -1:000000000000
684 o changeset: -1:000000000000
685 user:
685 user:
686 date: Thu Jan 01 00:00:00 1970 +0000
686 date: Thu Jan 01 00:00:00 1970 +0000
687
687
688
688
689
689
690 log -f with null parent
690 log -f with null parent
691
691
692 $ hg up -C null
692 $ hg up -C null
693 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
693 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
694 $ hg log -f
694 $ hg log -f
695
695
696
696
697 log -r . with two parents
697 log -r . with two parents
698
698
699 $ hg up -C 3
699 $ hg up -C 3
700 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
700 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
701 $ hg merge tip
701 $ hg merge tip
702 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
702 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
703 (branch merge, don't forget to commit)
703 (branch merge, don't forget to commit)
704 $ hg log -r .
704 $ hg log -r .
705 changeset: 3:e62f78d544b4
705 changeset: 3:e62f78d544b4
706 parent: 1:3d5bf5654eda
706 parent: 1:3d5bf5654eda
707 user: test
707 user: test
708 date: Thu Jan 01 00:00:01 1970 +0000
708 date: Thu Jan 01 00:00:01 1970 +0000
709 summary: b1
709 summary: b1
710
710
711
711
712
712
713 log -r . with one parent
713 log -r . with one parent
714
714
715 $ hg ci -mm12 -d '1 0'
715 $ hg ci -mm12 -d '1 0'
716 $ hg log -r .
716 $ hg log -r .
717 changeset: 5:302e9dd6890d
717 changeset: 5:302e9dd6890d
718 tag: tip
718 tag: tip
719 parent: 3:e62f78d544b4
719 parent: 3:e62f78d544b4
720 parent: 4:ddb82e70d1a1
720 parent: 4:ddb82e70d1a1
721 user: test
721 user: test
722 date: Thu Jan 01 00:00:01 1970 +0000
722 date: Thu Jan 01 00:00:01 1970 +0000
723 summary: m12
723 summary: m12
724
724
725
725
726 $ echo postm >> b1
726 $ echo postm >> b1
727 $ hg ci -Amb1.1 -d'1 0'
727 $ hg ci -Amb1.1 -d'1 0'
728
728
729
729
730 log --follow-first
730 log --follow-first
731
731
732 $ hg log --follow-first
732 $ hg log --follow-first
733 changeset: 6:2404bbcab562
733 changeset: 6:2404bbcab562
734 tag: tip
734 tag: tip
735 user: test
735 user: test
736 date: Thu Jan 01 00:00:01 1970 +0000
736 date: Thu Jan 01 00:00:01 1970 +0000
737 summary: b1.1
737 summary: b1.1
738
738
739 changeset: 5:302e9dd6890d
739 changeset: 5:302e9dd6890d
740 parent: 3:e62f78d544b4
740 parent: 3:e62f78d544b4
741 parent: 4:ddb82e70d1a1
741 parent: 4:ddb82e70d1a1
742 user: test
742 user: test
743 date: Thu Jan 01 00:00:01 1970 +0000
743 date: Thu Jan 01 00:00:01 1970 +0000
744 summary: m12
744 summary: m12
745
745
746 changeset: 3:e62f78d544b4
746 changeset: 3:e62f78d544b4
747 parent: 1:3d5bf5654eda
747 parent: 1:3d5bf5654eda
748 user: test
748 user: test
749 date: Thu Jan 01 00:00:01 1970 +0000
749 date: Thu Jan 01 00:00:01 1970 +0000
750 summary: b1
750 summary: b1
751
751
752 changeset: 1:3d5bf5654eda
752 changeset: 1:3d5bf5654eda
753 user: test
753 user: test
754 date: Thu Jan 01 00:00:01 1970 +0000
754 date: Thu Jan 01 00:00:01 1970 +0000
755 summary: r1
755 summary: r1
756
756
757 changeset: 0:67e992f2c4f3
757 changeset: 0:67e992f2c4f3
758 user: test
758 user: test
759 date: Thu Jan 01 00:00:01 1970 +0000
759 date: Thu Jan 01 00:00:01 1970 +0000
760 summary: base
760 summary: base
761
761
762
762
763
763
764 log -P 2
764 log -P 2
765
765
766 $ hg log -P 2
766 $ hg log -P 2
767 changeset: 6:2404bbcab562
767 changeset: 6:2404bbcab562
768 tag: tip
768 tag: tip
769 user: test
769 user: test
770 date: Thu Jan 01 00:00:01 1970 +0000
770 date: Thu Jan 01 00:00:01 1970 +0000
771 summary: b1.1
771 summary: b1.1
772
772
773 changeset: 5:302e9dd6890d
773 changeset: 5:302e9dd6890d
774 parent: 3:e62f78d544b4
774 parent: 3:e62f78d544b4
775 parent: 4:ddb82e70d1a1
775 parent: 4:ddb82e70d1a1
776 user: test
776 user: test
777 date: Thu Jan 01 00:00:01 1970 +0000
777 date: Thu Jan 01 00:00:01 1970 +0000
778 summary: m12
778 summary: m12
779
779
780 changeset: 4:ddb82e70d1a1
780 changeset: 4:ddb82e70d1a1
781 parent: 0:67e992f2c4f3
781 parent: 0:67e992f2c4f3
782 user: test
782 user: test
783 date: Thu Jan 01 00:00:01 1970 +0000
783 date: Thu Jan 01 00:00:01 1970 +0000
784 summary: b2
784 summary: b2
785
785
786 changeset: 3:e62f78d544b4
786 changeset: 3:e62f78d544b4
787 parent: 1:3d5bf5654eda
787 parent: 1:3d5bf5654eda
788 user: test
788 user: test
789 date: Thu Jan 01 00:00:01 1970 +0000
789 date: Thu Jan 01 00:00:01 1970 +0000
790 summary: b1
790 summary: b1
791
791
792
792
793
793
794 log -r tip -p --git
794 log -r tip -p --git
795
795
796 $ hg log -r tip -p --git
796 $ hg log -r tip -p --git
797 changeset: 6:2404bbcab562
797 changeset: 6:2404bbcab562
798 tag: tip
798 tag: tip
799 user: test
799 user: test
800 date: Thu Jan 01 00:00:01 1970 +0000
800 date: Thu Jan 01 00:00:01 1970 +0000
801 summary: b1.1
801 summary: b1.1
802
802
803 diff --git a/b1 b/b1
803 diff --git a/b1 b/b1
804 --- a/b1
804 --- a/b1
805 +++ b/b1
805 +++ b/b1
806 @@ -1,1 +1,2 @@
806 @@ -1,1 +1,2 @@
807 b1
807 b1
808 +postm
808 +postm
809
809
810
810
811
811
812 log -r ""
812 log -r ""
813
813
814 $ hg log -r ''
814 $ hg log -r ''
815 hg: parse error: empty query
815 hg: parse error: empty query
816 [255]
816 [255]
817
817
818 log -r <some unknown node id>
818 log -r <some unknown node id>
819
819
820 $ hg log -r 1000000000000000000000000000000000000000
820 $ hg log -r 1000000000000000000000000000000000000000
821 abort: unknown revision '1000000000000000000000000000000000000000'!
821 abort: unknown revision '1000000000000000000000000000000000000000'!
822 [255]
822 [255]
823
823
824 log -k r1
824 log -k r1
825
825
826 $ hg log -k r1
826 $ hg log -k r1
827 changeset: 1:3d5bf5654eda
827 changeset: 1:3d5bf5654eda
828 user: test
828 user: test
829 date: Thu Jan 01 00:00:01 1970 +0000
829 date: Thu Jan 01 00:00:01 1970 +0000
830 summary: r1
830 summary: r1
831
831
832 log -p -l2 --color=always
832 log -p -l2 --color=always
833
833
834 $ hg --config extensions.color= --config color.mode=ansi \
834 $ hg --config extensions.color= --config color.mode=ansi \
835 > log -p -l2 --color=always
835 > log -p -l2 --color=always
836 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
836 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
837 tag: tip
837 tag: tip
838 user: test
838 user: test
839 date: Thu Jan 01 00:00:01 1970 +0000
839 date: Thu Jan 01 00:00:01 1970 +0000
840 summary: b1.1
840 summary: b1.1
841
841
842 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
842 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
843 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
843 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
844 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
844 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
845 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
845 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
846 b1
846 b1
847 \x1b[0;32m+postm\x1b[0m (esc)
847 \x1b[0;32m+postm\x1b[0m (esc)
848
848
849 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
849 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
850 parent: 3:e62f78d544b4
850 parent: 3:e62f78d544b4
851 parent: 4:ddb82e70d1a1
851 parent: 4:ddb82e70d1a1
852 user: test
852 user: test
853 date: Thu Jan 01 00:00:01 1970 +0000
853 date: Thu Jan 01 00:00:01 1970 +0000
854 summary: m12
854 summary: m12
855
855
856 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
856 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
857 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
857 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
858 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
858 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
859 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
859 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
860 \x1b[0;32m+b2\x1b[0m (esc)
860 \x1b[0;32m+b2\x1b[0m (esc)
861
861
862
862
863
863
864 log -r tip --stat
864 log -r tip --stat
865
865
866 $ hg log -r tip --stat
866 $ hg log -r tip --stat
867 changeset: 6:2404bbcab562
867 changeset: 6:2404bbcab562
868 tag: tip
868 tag: tip
869 user: test
869 user: test
870 date: Thu Jan 01 00:00:01 1970 +0000
870 date: Thu Jan 01 00:00:01 1970 +0000
871 summary: b1.1
871 summary: b1.1
872
872
873 b1 | 1 +
873 b1 | 1 +
874 1 files changed, 1 insertions(+), 0 deletions(-)
874 1 files changed, 1 insertions(+), 0 deletions(-)
875
875
876
876
877 $ cd ..
877 $ cd ..
878
878
879
879
880 User
880 User
881
881
882 $ hg init usertest
882 $ hg init usertest
883 $ cd usertest
883 $ cd usertest
884
884
885 $ echo a > a
885 $ echo a > a
886 $ hg ci -A -m "a" -u "User One <user1@example.org>"
886 $ hg ci -A -m "a" -u "User One <user1@example.org>"
887 adding a
887 adding a
888 $ echo b > b
888 $ echo b > b
889 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
889 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
890 adding b
890 adding b
891
891
892 $ hg log -u "User One <user1@example.org>"
892 $ hg log -u "User One <user1@example.org>"
893 changeset: 0:29a4c94f1924
893 changeset: 0:29a4c94f1924
894 user: User One <user1@example.org>
894 user: User One <user1@example.org>
895 date: Thu Jan 01 00:00:00 1970 +0000
895 date: Thu Jan 01 00:00:00 1970 +0000
896 summary: a
896 summary: a
897
897
898 $ hg log -u "user1" -u "user2"
898 $ hg log -u "user1" -u "user2"
899 changeset: 1:e834b5e69c0e
899 changeset: 1:e834b5e69c0e
900 tag: tip
900 tag: tip
901 user: User Two <user2@example.org>
901 user: User Two <user2@example.org>
902 date: Thu Jan 01 00:00:00 1970 +0000
902 date: Thu Jan 01 00:00:00 1970 +0000
903 summary: b
903 summary: b
904
904
905 changeset: 0:29a4c94f1924
905 changeset: 0:29a4c94f1924
906 user: User One <user1@example.org>
906 user: User One <user1@example.org>
907 date: Thu Jan 01 00:00:00 1970 +0000
907 date: Thu Jan 01 00:00:00 1970 +0000
908 summary: a
908 summary: a
909
909
910 $ hg log -u "user3"
910 $ hg log -u "user3"
911
911
912 $ cd ..
912 $ cd ..
913
913
914 $ hg init branches
914 $ hg init branches
915 $ cd branches
915 $ cd branches
916
916
917 $ echo a > a
917 $ echo a > a
918 $ hg ci -A -m "commit on default"
918 $ hg ci -A -m "commit on default"
919 adding a
919 adding a
920 $ hg branch test
920 $ hg branch test
921 marked working directory as branch test
921 marked working directory as branch test
922 (branches are permanent and global, did you want a bookmark?)
922 (branches are permanent and global, did you want a bookmark?)
923 $ echo b > b
923 $ echo b > b
924 $ hg ci -A -m "commit on test"
924 $ hg ci -A -m "commit on test"
925 adding b
925 adding b
926
926
927 $ hg up default
927 $ hg up default
928 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
928 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
929 $ echo c > c
929 $ echo c > c
930 $ hg ci -A -m "commit on default"
930 $ hg ci -A -m "commit on default"
931 adding c
931 adding c
932 $ hg up test
932 $ hg up test
933 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
933 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
934 $ echo c > c
934 $ echo c > c
935 $ hg ci -A -m "commit on test"
935 $ hg ci -A -m "commit on test"
936 adding c
936 adding c
937
937
938
938
939 log -b default
939 log -b default
940
940
941 $ hg log -b default
941 $ hg log -b default
942 changeset: 2:c3a4f03cc9a7
942 changeset: 2:c3a4f03cc9a7
943 parent: 0:24427303d56f
943 parent: 0:24427303d56f
944 user: test
944 user: test
945 date: Thu Jan 01 00:00:00 1970 +0000
945 date: Thu Jan 01 00:00:00 1970 +0000
946 summary: commit on default
946 summary: commit on default
947
947
948 changeset: 0:24427303d56f
948 changeset: 0:24427303d56f
949 user: test
949 user: test
950 date: Thu Jan 01 00:00:00 1970 +0000
950 date: Thu Jan 01 00:00:00 1970 +0000
951 summary: commit on default
951 summary: commit on default
952
952
953
953
954
954
955 log -b test
955 log -b test
956
956
957 $ hg log -b test
957 $ hg log -b test
958 changeset: 3:f5d8de11c2e2
958 changeset: 3:f5d8de11c2e2
959 branch: test
959 branch: test
960 tag: tip
960 tag: tip
961 parent: 1:d32277701ccb
961 parent: 1:d32277701ccb
962 user: test
962 user: test
963 date: Thu Jan 01 00:00:00 1970 +0000
963 date: Thu Jan 01 00:00:00 1970 +0000
964 summary: commit on test
964 summary: commit on test
965
965
966 changeset: 1:d32277701ccb
966 changeset: 1:d32277701ccb
967 branch: test
967 branch: test
968 user: test
968 user: test
969 date: Thu Jan 01 00:00:00 1970 +0000
969 date: Thu Jan 01 00:00:00 1970 +0000
970 summary: commit on test
970 summary: commit on test
971
971
972
972
973
973
974 log -b dummy
974 log -b dummy
975
975
976 $ hg log -b dummy
976 $ hg log -b dummy
977 abort: unknown revision 'dummy'!
977 abort: unknown revision 'dummy'!
978 [255]
978 [255]
979
979
980
980
981 log -b .
981 log -b .
982
982
983 $ hg log -b .
983 $ hg log -b .
984 changeset: 3:f5d8de11c2e2
984 changeset: 3:f5d8de11c2e2
985 branch: test
985 branch: test
986 tag: tip
986 tag: tip
987 parent: 1:d32277701ccb
987 parent: 1:d32277701ccb
988 user: test
988 user: test
989 date: Thu Jan 01 00:00:00 1970 +0000
989 date: Thu Jan 01 00:00:00 1970 +0000
990 summary: commit on test
990 summary: commit on test
991
991
992 changeset: 1:d32277701ccb
992 changeset: 1:d32277701ccb
993 branch: test
993 branch: test
994 user: test
994 user: test
995 date: Thu Jan 01 00:00:00 1970 +0000
995 date: Thu Jan 01 00:00:00 1970 +0000
996 summary: commit on test
996 summary: commit on test
997
997
998
998
999
999
1000 log -b default -b test
1000 log -b default -b test
1001
1001
1002 $ hg log -b default -b test
1002 $ hg log -b default -b test
1003 changeset: 3:f5d8de11c2e2
1003 changeset: 3:f5d8de11c2e2
1004 branch: test
1004 branch: test
1005 tag: tip
1005 tag: tip
1006 parent: 1:d32277701ccb
1006 parent: 1:d32277701ccb
1007 user: test
1007 user: test
1008 date: Thu Jan 01 00:00:00 1970 +0000
1008 date: Thu Jan 01 00:00:00 1970 +0000
1009 summary: commit on test
1009 summary: commit on test
1010
1010
1011 changeset: 2:c3a4f03cc9a7
1011 changeset: 2:c3a4f03cc9a7
1012 parent: 0:24427303d56f
1012 parent: 0:24427303d56f
1013 user: test
1013 user: test
1014 date: Thu Jan 01 00:00:00 1970 +0000
1014 date: Thu Jan 01 00:00:00 1970 +0000
1015 summary: commit on default
1015 summary: commit on default
1016
1016
1017 changeset: 1:d32277701ccb
1017 changeset: 1:d32277701ccb
1018 branch: test
1018 branch: test
1019 user: test
1019 user: test
1020 date: Thu Jan 01 00:00:00 1970 +0000
1020 date: Thu Jan 01 00:00:00 1970 +0000
1021 summary: commit on test
1021 summary: commit on test
1022
1022
1023 changeset: 0:24427303d56f
1023 changeset: 0:24427303d56f
1024 user: test
1024 user: test
1025 date: Thu Jan 01 00:00:00 1970 +0000
1025 date: Thu Jan 01 00:00:00 1970 +0000
1026 summary: commit on default
1026 summary: commit on default
1027
1027
1028
1028
1029
1029
1030 log -b default -b .
1030 log -b default -b .
1031
1031
1032 $ hg log -b default -b .
1032 $ hg log -b default -b .
1033 changeset: 3:f5d8de11c2e2
1033 changeset: 3:f5d8de11c2e2
1034 branch: test
1034 branch: test
1035 tag: tip
1035 tag: tip
1036 parent: 1:d32277701ccb
1036 parent: 1:d32277701ccb
1037 user: test
1037 user: test
1038 date: Thu Jan 01 00:00:00 1970 +0000
1038 date: Thu Jan 01 00:00:00 1970 +0000
1039 summary: commit on test
1039 summary: commit on test
1040
1040
1041 changeset: 2:c3a4f03cc9a7
1041 changeset: 2:c3a4f03cc9a7
1042 parent: 0:24427303d56f
1042 parent: 0:24427303d56f
1043 user: test
1043 user: test
1044 date: Thu Jan 01 00:00:00 1970 +0000
1044 date: Thu Jan 01 00:00:00 1970 +0000
1045 summary: commit on default
1045 summary: commit on default
1046
1046
1047 changeset: 1:d32277701ccb
1047 changeset: 1:d32277701ccb
1048 branch: test
1048 branch: test
1049 user: test
1049 user: test
1050 date: Thu Jan 01 00:00:00 1970 +0000
1050 date: Thu Jan 01 00:00:00 1970 +0000
1051 summary: commit on test
1051 summary: commit on test
1052
1052
1053 changeset: 0:24427303d56f
1053 changeset: 0:24427303d56f
1054 user: test
1054 user: test
1055 date: Thu Jan 01 00:00:00 1970 +0000
1055 date: Thu Jan 01 00:00:00 1970 +0000
1056 summary: commit on default
1056 summary: commit on default
1057
1057
1058
1058
1059
1059
1060 log -b . -b test
1060 log -b . -b test
1061
1061
1062 $ hg log -b . -b test
1062 $ hg log -b . -b test
1063 changeset: 3:f5d8de11c2e2
1063 changeset: 3:f5d8de11c2e2
1064 branch: test
1064 branch: test
1065 tag: tip
1065 tag: tip
1066 parent: 1:d32277701ccb
1066 parent: 1:d32277701ccb
1067 user: test
1067 user: test
1068 date: Thu Jan 01 00:00:00 1970 +0000
1068 date: Thu Jan 01 00:00:00 1970 +0000
1069 summary: commit on test
1069 summary: commit on test
1070
1070
1071 changeset: 1:d32277701ccb
1071 changeset: 1:d32277701ccb
1072 branch: test
1072 branch: test
1073 user: test
1073 user: test
1074 date: Thu Jan 01 00:00:00 1970 +0000
1074 date: Thu Jan 01 00:00:00 1970 +0000
1075 summary: commit on test
1075 summary: commit on test
1076
1076
1077
1077
1078
1078
1079 log -b 2
1079 log -b 2
1080
1080
1081 $ hg log -b 2
1081 $ hg log -b 2
1082 changeset: 2:c3a4f03cc9a7
1082 changeset: 2:c3a4f03cc9a7
1083 parent: 0:24427303d56f
1083 parent: 0:24427303d56f
1084 user: test
1084 user: test
1085 date: Thu Jan 01 00:00:00 1970 +0000
1085 date: Thu Jan 01 00:00:00 1970 +0000
1086 summary: commit on default
1086 summary: commit on default
1087
1087
1088 changeset: 0:24427303d56f
1088 changeset: 0:24427303d56f
1089 user: test
1089 user: test
1090 date: Thu Jan 01 00:00:00 1970 +0000
1090 date: Thu Jan 01 00:00:00 1970 +0000
1091 summary: commit on default
1091 summary: commit on default
1092
1092
1093 #if gettext
1093 #if gettext
1094
1094
1095 Test that all log names are translated (e.g. branches, bookmarks, tags):
1095 Test that all log names are translated (e.g. branches, bookmarks, tags):
1096
1096
1097 $ hg bookmark babar -r tip
1097 $ hg bookmark babar -r tip
1098
1098
1099 $ HGENCODING=UTF-8 LANGUAGE=de hg log -r tip
1099 $ HGENCODING=UTF-8 LANGUAGE=de hg log -r tip
1100 \xc3\x84nderung: 3:f5d8de11c2e2 (esc)
1100 \xc3\x84nderung: 3:f5d8de11c2e2 (esc)
1101 Zweig: test
1101 Zweig: test
1102 Lesezeichen: babar
1102 Lesezeichen: babar
1103 Marke: tip
1103 Marke: tip
1104 Vorg\xc3\xa4nger: 1:d32277701ccb (esc)
1104 Vorg\xc3\xa4nger: 1:d32277701ccb (esc)
1105 Nutzer: test
1105 Nutzer: test
1106 Datum: Thu Jan 01 00:00:00 1970 +0000
1106 Datum: Thu Jan 01 00:00:00 1970 +0000
1107 Zusammenfassung: commit on test
1107 Zusammenfassung: commit on test
1108
1108
1109 $ hg bookmark -d babar
1109 $ hg bookmark -d babar
1110
1110
1111 #endif
1111 #endif
1112
1112
1113 log -p --cwd dir (in subdir)
1113 log -p --cwd dir (in subdir)
1114
1114
1115 $ mkdir dir
1115 $ mkdir dir
1116 $ hg log -p --cwd dir
1116 $ hg log -p --cwd dir
1117 changeset: 3:f5d8de11c2e2
1117 changeset: 3:f5d8de11c2e2
1118 branch: test
1118 branch: test
1119 tag: tip
1119 tag: tip
1120 parent: 1:d32277701ccb
1120 parent: 1:d32277701ccb
1121 user: test
1121 user: test
1122 date: Thu Jan 01 00:00:00 1970 +0000
1122 date: Thu Jan 01 00:00:00 1970 +0000
1123 summary: commit on test
1123 summary: commit on test
1124
1124
1125 diff -r d32277701ccb -r f5d8de11c2e2 c
1125 diff -r d32277701ccb -r f5d8de11c2e2 c
1126 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1126 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1127 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1127 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1128 @@ -0,0 +1,1 @@
1128 @@ -0,0 +1,1 @@
1129 +c
1129 +c
1130
1130
1131 changeset: 2:c3a4f03cc9a7
1131 changeset: 2:c3a4f03cc9a7
1132 parent: 0:24427303d56f
1132 parent: 0:24427303d56f
1133 user: test
1133 user: test
1134 date: Thu Jan 01 00:00:00 1970 +0000
1134 date: Thu Jan 01 00:00:00 1970 +0000
1135 summary: commit on default
1135 summary: commit on default
1136
1136
1137 diff -r 24427303d56f -r c3a4f03cc9a7 c
1137 diff -r 24427303d56f -r c3a4f03cc9a7 c
1138 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1138 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1139 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1139 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1140 @@ -0,0 +1,1 @@
1140 @@ -0,0 +1,1 @@
1141 +c
1141 +c
1142
1142
1143 changeset: 1:d32277701ccb
1143 changeset: 1:d32277701ccb
1144 branch: test
1144 branch: test
1145 user: test
1145 user: test
1146 date: Thu Jan 01 00:00:00 1970 +0000
1146 date: Thu Jan 01 00:00:00 1970 +0000
1147 summary: commit on test
1147 summary: commit on test
1148
1148
1149 diff -r 24427303d56f -r d32277701ccb b
1149 diff -r 24427303d56f -r d32277701ccb b
1150 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1150 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1151 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1151 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1152 @@ -0,0 +1,1 @@
1152 @@ -0,0 +1,1 @@
1153 +b
1153 +b
1154
1154
1155 changeset: 0:24427303d56f
1155 changeset: 0:24427303d56f
1156 user: test
1156 user: test
1157 date: Thu Jan 01 00:00:00 1970 +0000
1157 date: Thu Jan 01 00:00:00 1970 +0000
1158 summary: commit on default
1158 summary: commit on default
1159
1159
1160 diff -r 000000000000 -r 24427303d56f a
1160 diff -r 000000000000 -r 24427303d56f a
1161 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1161 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1162 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1162 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1163 @@ -0,0 +1,1 @@
1163 @@ -0,0 +1,1 @@
1164 +a
1164 +a
1165
1165
1166
1166
1167
1167
1168 log -p -R repo
1168 log -p -R repo
1169
1169
1170 $ cd dir
1170 $ cd dir
1171 $ hg log -p -R .. ../a
1171 $ hg log -p -R .. ../a
1172 changeset: 0:24427303d56f
1172 changeset: 0:24427303d56f
1173 user: test
1173 user: test
1174 date: Thu Jan 01 00:00:00 1970 +0000
1174 date: Thu Jan 01 00:00:00 1970 +0000
1175 summary: commit on default
1175 summary: commit on default
1176
1176
1177 diff -r 000000000000 -r 24427303d56f a
1177 diff -r 000000000000 -r 24427303d56f a
1178 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1178 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1179 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1179 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1180 @@ -0,0 +1,1 @@
1180 @@ -0,0 +1,1 @@
1181 +a
1181 +a
1182
1182
1183
1183
1184 $ cd ../..
1184 $ cd ../..
1185
1185
1186 $ hg init follow2
1186 $ hg init follow2
1187 $ cd follow2
1187 $ cd follow2
1188
1188
1189 # Build the following history:
1189 # Build the following history:
1190 # tip - o - x - o - x - x
1190 # tip - o - x - o - x - x
1191 # \ /
1191 # \ /
1192 # o - o - o - x
1192 # o - o - o - x
1193 # \ /
1193 # \ /
1194 # o
1194 # o
1195 #
1195 #
1196 # Where "o" is a revision containing "foo" and
1196 # Where "o" is a revision containing "foo" and
1197 # "x" is a revision without "foo"
1197 # "x" is a revision without "foo"
1198
1198
1199 $ touch init
1199 $ touch init
1200 $ hg ci -A -m "init, unrelated"
1200 $ hg ci -A -m "init, unrelated"
1201 adding init
1201 adding init
1202 $ echo 'foo' > init
1202 $ echo 'foo' > init
1203 $ hg ci -m "change, unrelated"
1203 $ hg ci -m "change, unrelated"
1204 $ echo 'foo' > foo
1204 $ echo 'foo' > foo
1205 $ hg ci -A -m "add unrelated old foo"
1205 $ hg ci -A -m "add unrelated old foo"
1206 adding foo
1206 adding foo
1207 $ hg rm foo
1207 $ hg rm foo
1208 $ hg ci -m "delete foo, unrelated"
1208 $ hg ci -m "delete foo, unrelated"
1209 $ echo 'related' > foo
1209 $ echo 'related' > foo
1210 $ hg ci -A -m "add foo, related"
1210 $ hg ci -A -m "add foo, related"
1211 adding foo
1211 adding foo
1212
1212
1213 $ hg up 0
1213 $ hg up 0
1214 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1214 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1215 $ touch branch
1215 $ touch branch
1216 $ hg ci -A -m "first branch, unrelated"
1216 $ hg ci -A -m "first branch, unrelated"
1217 adding branch
1217 adding branch
1218 created new head
1218 created new head
1219 $ touch foo
1219 $ touch foo
1220 $ hg ci -A -m "create foo, related"
1220 $ hg ci -A -m "create foo, related"
1221 adding foo
1221 adding foo
1222 $ echo 'change' > foo
1222 $ echo 'change' > foo
1223 $ hg ci -m "change foo, related"
1223 $ hg ci -m "change foo, related"
1224
1224
1225 $ hg up 6
1225 $ hg up 6
1226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1227 $ echo 'change foo in branch' > foo
1227 $ echo 'change foo in branch' > foo
1228 $ hg ci -m "change foo in branch, related"
1228 $ hg ci -m "change foo in branch, related"
1229 created new head
1229 created new head
1230 $ hg merge 7
1230 $ hg merge 7
1231 merging foo
1231 merging foo
1232 warning: conflicts during merge.
1232 warning: conflicts during merge.
1233 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
1233 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
1234 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1234 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1235 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1235 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1236 [1]
1236 [1]
1237 $ echo 'merge 1' > foo
1237 $ echo 'merge 1' > foo
1238 $ hg resolve -m foo
1238 $ hg resolve -m foo
1239 (no more unresolved files)
1239 (no more unresolved files)
1240 $ hg ci -m "First merge, related"
1240 $ hg ci -m "First merge, related"
1241
1241
1242 $ hg merge 4
1242 $ hg merge 4
1243 merging foo
1243 merging foo
1244 warning: conflicts during merge.
1244 warning: conflicts during merge.
1245 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
1245 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
1246 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
1246 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
1247 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1247 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1248 [1]
1248 [1]
1249 $ echo 'merge 2' > foo
1249 $ echo 'merge 2' > foo
1250 $ hg resolve -m foo
1250 $ hg resolve -m foo
1251 (no more unresolved files)
1251 (no more unresolved files)
1252 $ hg ci -m "Last merge, related"
1252 $ hg ci -m "Last merge, related"
1253
1253
1254 $ hg log --graph
1254 $ hg log --graph
1255 @ changeset: 10:4dae8563d2c5
1255 @ changeset: 10:4dae8563d2c5
1256 |\ tag: tip
1256 |\ tag: tip
1257 | | parent: 9:7b35701b003e
1257 | | parent: 9:7b35701b003e
1258 | | parent: 4:88176d361b69
1258 | | parent: 4:88176d361b69
1259 | | user: test
1259 | | user: test
1260 | | date: Thu Jan 01 00:00:00 1970 +0000
1260 | | date: Thu Jan 01 00:00:00 1970 +0000
1261 | | summary: Last merge, related
1261 | | summary: Last merge, related
1262 | |
1262 | |
1263 | o changeset: 9:7b35701b003e
1263 | o changeset: 9:7b35701b003e
1264 | |\ parent: 8:e5416ad8a855
1264 | |\ parent: 8:e5416ad8a855
1265 | | | parent: 7:87fe3144dcfa
1265 | | | parent: 7:87fe3144dcfa
1266 | | | user: test
1266 | | | user: test
1267 | | | date: Thu Jan 01 00:00:00 1970 +0000
1267 | | | date: Thu Jan 01 00:00:00 1970 +0000
1268 | | | summary: First merge, related
1268 | | | summary: First merge, related
1269 | | |
1269 | | |
1270 | | o changeset: 8:e5416ad8a855
1270 | | o changeset: 8:e5416ad8a855
1271 | | | parent: 6:dc6c325fe5ee
1271 | | | parent: 6:dc6c325fe5ee
1272 | | | user: test
1272 | | | user: test
1273 | | | date: Thu Jan 01 00:00:00 1970 +0000
1273 | | | date: Thu Jan 01 00:00:00 1970 +0000
1274 | | | summary: change foo in branch, related
1274 | | | summary: change foo in branch, related
1275 | | |
1275 | | |
1276 | o | changeset: 7:87fe3144dcfa
1276 | o | changeset: 7:87fe3144dcfa
1277 | |/ user: test
1277 | |/ user: test
1278 | | date: Thu Jan 01 00:00:00 1970 +0000
1278 | | date: Thu Jan 01 00:00:00 1970 +0000
1279 | | summary: change foo, related
1279 | | summary: change foo, related
1280 | |
1280 | |
1281 | o changeset: 6:dc6c325fe5ee
1281 | o changeset: 6:dc6c325fe5ee
1282 | | user: test
1282 | | user: test
1283 | | date: Thu Jan 01 00:00:00 1970 +0000
1283 | | date: Thu Jan 01 00:00:00 1970 +0000
1284 | | summary: create foo, related
1284 | | summary: create foo, related
1285 | |
1285 | |
1286 | o changeset: 5:73db34516eb9
1286 | o changeset: 5:73db34516eb9
1287 | | parent: 0:e87515fd044a
1287 | | parent: 0:e87515fd044a
1288 | | user: test
1288 | | user: test
1289 | | date: Thu Jan 01 00:00:00 1970 +0000
1289 | | date: Thu Jan 01 00:00:00 1970 +0000
1290 | | summary: first branch, unrelated
1290 | | summary: first branch, unrelated
1291 | |
1291 | |
1292 o | changeset: 4:88176d361b69
1292 o | changeset: 4:88176d361b69
1293 | | user: test
1293 | | user: test
1294 | | date: Thu Jan 01 00:00:00 1970 +0000
1294 | | date: Thu Jan 01 00:00:00 1970 +0000
1295 | | summary: add foo, related
1295 | | summary: add foo, related
1296 | |
1296 | |
1297 o | changeset: 3:dd78ae4afb56
1297 o | changeset: 3:dd78ae4afb56
1298 | | user: test
1298 | | user: test
1299 | | date: Thu Jan 01 00:00:00 1970 +0000
1299 | | date: Thu Jan 01 00:00:00 1970 +0000
1300 | | summary: delete foo, unrelated
1300 | | summary: delete foo, unrelated
1301 | |
1301 | |
1302 o | changeset: 2:c4c64aedf0f7
1302 o | changeset: 2:c4c64aedf0f7
1303 | | user: test
1303 | | user: test
1304 | | date: Thu Jan 01 00:00:00 1970 +0000
1304 | | date: Thu Jan 01 00:00:00 1970 +0000
1305 | | summary: add unrelated old foo
1305 | | summary: add unrelated old foo
1306 | |
1306 | |
1307 o | changeset: 1:e5faa7440653
1307 o | changeset: 1:e5faa7440653
1308 |/ user: test
1308 |/ user: test
1309 | date: Thu Jan 01 00:00:00 1970 +0000
1309 | date: Thu Jan 01 00:00:00 1970 +0000
1310 | summary: change, unrelated
1310 | summary: change, unrelated
1311 |
1311 |
1312 o changeset: 0:e87515fd044a
1312 o changeset: 0:e87515fd044a
1313 user: test
1313 user: test
1314 date: Thu Jan 01 00:00:00 1970 +0000
1314 date: Thu Jan 01 00:00:00 1970 +0000
1315 summary: init, unrelated
1315 summary: init, unrelated
1316
1316
1317
1317
1318 $ hg --traceback log -f foo
1318 $ hg --traceback log -f foo
1319 changeset: 10:4dae8563d2c5
1319 changeset: 10:4dae8563d2c5
1320 tag: tip
1320 tag: tip
1321 parent: 9:7b35701b003e
1321 parent: 9:7b35701b003e
1322 parent: 4:88176d361b69
1322 parent: 4:88176d361b69
1323 user: test
1323 user: test
1324 date: Thu Jan 01 00:00:00 1970 +0000
1324 date: Thu Jan 01 00:00:00 1970 +0000
1325 summary: Last merge, related
1325 summary: Last merge, related
1326
1326
1327 changeset: 9:7b35701b003e
1327 changeset: 9:7b35701b003e
1328 parent: 8:e5416ad8a855
1328 parent: 8:e5416ad8a855
1329 parent: 7:87fe3144dcfa
1329 parent: 7:87fe3144dcfa
1330 user: test
1330 user: test
1331 date: Thu Jan 01 00:00:00 1970 +0000
1331 date: Thu Jan 01 00:00:00 1970 +0000
1332 summary: First merge, related
1332 summary: First merge, related
1333
1333
1334 changeset: 8:e5416ad8a855
1334 changeset: 8:e5416ad8a855
1335 parent: 6:dc6c325fe5ee
1335 parent: 6:dc6c325fe5ee
1336 user: test
1336 user: test
1337 date: Thu Jan 01 00:00:00 1970 +0000
1337 date: Thu Jan 01 00:00:00 1970 +0000
1338 summary: change foo in branch, related
1338 summary: change foo in branch, related
1339
1339
1340 changeset: 7:87fe3144dcfa
1340 changeset: 7:87fe3144dcfa
1341 user: test
1341 user: test
1342 date: Thu Jan 01 00:00:00 1970 +0000
1342 date: Thu Jan 01 00:00:00 1970 +0000
1343 summary: change foo, related
1343 summary: change foo, related
1344
1344
1345 changeset: 6:dc6c325fe5ee
1345 changeset: 6:dc6c325fe5ee
1346 user: test
1346 user: test
1347 date: Thu Jan 01 00:00:00 1970 +0000
1347 date: Thu Jan 01 00:00:00 1970 +0000
1348 summary: create foo, related
1348 summary: create foo, related
1349
1349
1350 changeset: 4:88176d361b69
1350 changeset: 4:88176d361b69
1351 user: test
1351 user: test
1352 date: Thu Jan 01 00:00:00 1970 +0000
1352 date: Thu Jan 01 00:00:00 1970 +0000
1353 summary: add foo, related
1353 summary: add foo, related
1354
1354
1355
1355
1356 Also check when maxrev < lastrevfilelog
1356 Also check when maxrev < lastrevfilelog
1357
1357
1358 $ hg --traceback log -f -r4 foo
1358 $ hg --traceback log -f -r4 foo
1359 changeset: 4:88176d361b69
1359 changeset: 4:88176d361b69
1360 user: test
1360 user: test
1361 date: Thu Jan 01 00:00:00 1970 +0000
1361 date: Thu Jan 01 00:00:00 1970 +0000
1362 summary: add foo, related
1362 summary: add foo, related
1363
1363
1364 changeset: 2:c4c64aedf0f7
1364 changeset: 2:c4c64aedf0f7
1365 user: test
1365 user: test
1366 date: Thu Jan 01 00:00:00 1970 +0000
1366 date: Thu Jan 01 00:00:00 1970 +0000
1367 summary: add unrelated old foo
1367 summary: add unrelated old foo
1368
1368
1369 $ cd ..
1369 $ cd ..
1370
1370
1371 Issue2383: hg log showing _less_ differences than hg diff
1371 Issue2383: hg log showing _less_ differences than hg diff
1372
1372
1373 $ hg init issue2383
1373 $ hg init issue2383
1374 $ cd issue2383
1374 $ cd issue2383
1375
1375
1376 Create a test repo:
1376 Create a test repo:
1377
1377
1378 $ echo a > a
1378 $ echo a > a
1379 $ hg ci -Am0
1379 $ hg ci -Am0
1380 adding a
1380 adding a
1381 $ echo b > b
1381 $ echo b > b
1382 $ hg ci -Am1
1382 $ hg ci -Am1
1383 adding b
1383 adding b
1384 $ hg co 0
1384 $ hg co 0
1385 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1385 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1386 $ echo b > a
1386 $ echo b > a
1387 $ hg ci -m2
1387 $ hg ci -m2
1388 created new head
1388 created new head
1389
1389
1390 Merge:
1390 Merge:
1391
1391
1392 $ hg merge
1392 $ hg merge
1393 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1393 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1394 (branch merge, don't forget to commit)
1394 (branch merge, don't forget to commit)
1395
1395
1396 Make sure there's a file listed in the merge to trigger the bug:
1396 Make sure there's a file listed in the merge to trigger the bug:
1397
1397
1398 $ echo c > a
1398 $ echo c > a
1399 $ hg ci -m3
1399 $ hg ci -m3
1400
1400
1401 Two files shown here in diff:
1401 Two files shown here in diff:
1402
1402
1403 $ hg diff --rev 2:3
1403 $ hg diff --rev 2:3
1404 diff -r b09be438c43a -r 8e07aafe1edc a
1404 diff -r b09be438c43a -r 8e07aafe1edc a
1405 --- a/a Thu Jan 01 00:00:00 1970 +0000
1405 --- a/a Thu Jan 01 00:00:00 1970 +0000
1406 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1406 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1407 @@ -1,1 +1,1 @@
1407 @@ -1,1 +1,1 @@
1408 -b
1408 -b
1409 +c
1409 +c
1410 diff -r b09be438c43a -r 8e07aafe1edc b
1410 diff -r b09be438c43a -r 8e07aafe1edc b
1411 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1411 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1412 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1412 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1413 @@ -0,0 +1,1 @@
1413 @@ -0,0 +1,1 @@
1414 +b
1414 +b
1415
1415
1416 Diff here should be the same:
1416 Diff here should be the same:
1417
1417
1418 $ hg log -vpr 3
1418 $ hg log -vpr 3
1419 changeset: 3:8e07aafe1edc
1419 changeset: 3:8e07aafe1edc
1420 tag: tip
1420 tag: tip
1421 parent: 2:b09be438c43a
1421 parent: 2:b09be438c43a
1422 parent: 1:925d80f479bb
1422 parent: 1:925d80f479bb
1423 user: test
1423 user: test
1424 date: Thu Jan 01 00:00:00 1970 +0000
1424 date: Thu Jan 01 00:00:00 1970 +0000
1425 files: a
1425 files: a
1426 description:
1426 description:
1427 3
1427 3
1428
1428
1429
1429
1430 diff -r b09be438c43a -r 8e07aafe1edc a
1430 diff -r b09be438c43a -r 8e07aafe1edc a
1431 --- a/a Thu Jan 01 00:00:00 1970 +0000
1431 --- a/a Thu Jan 01 00:00:00 1970 +0000
1432 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1432 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1433 @@ -1,1 +1,1 @@
1433 @@ -1,1 +1,1 @@
1434 -b
1434 -b
1435 +c
1435 +c
1436 diff -r b09be438c43a -r 8e07aafe1edc b
1436 diff -r b09be438c43a -r 8e07aafe1edc b
1437 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1437 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1438 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1438 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1439 @@ -0,0 +1,1 @@
1439 @@ -0,0 +1,1 @@
1440 +b
1440 +b
1441
1441
1442 $ cd ..
1442 $ cd ..
1443
1443
1444 'hg log -r rev fn' when last(filelog(fn)) != rev
1444 'hg log -r rev fn' when last(filelog(fn)) != rev
1445
1445
1446 $ hg init simplelog
1446 $ hg init simplelog
1447 $ cd simplelog
1447 $ cd simplelog
1448 $ echo f > a
1448 $ echo f > a
1449 $ hg ci -Am'a' -d '0 0'
1449 $ hg ci -Am'a' -d '0 0'
1450 adding a
1450 adding a
1451 $ echo f >> a
1451 $ echo f >> a
1452 $ hg ci -Am'a bis' -d '1 0'
1452 $ hg ci -Am'a bis' -d '1 0'
1453
1453
1454 $ hg log -r0 a
1454 $ hg log -r0 a
1455 changeset: 0:9f758d63dcde
1455 changeset: 0:9f758d63dcde
1456 user: test
1456 user: test
1457 date: Thu Jan 01 00:00:00 1970 +0000
1457 date: Thu Jan 01 00:00:00 1970 +0000
1458 summary: a
1458 summary: a
1459
1459
1460 enable obsolete to test hidden feature
1460 enable obsolete to test hidden feature
1461
1461
1462 $ cat >> $HGRCPATH << EOF
1462 $ cat >> $HGRCPATH << EOF
1463 > [experimental]
1463 > [experimental]
1464 > evolution=createmarkers
1464 > evolution=createmarkers
1465 > EOF
1465 > EOF
1466
1466
1467 $ hg log --template='{rev}:{node}\n'
1467 $ hg log --template='{rev}:{node}\n'
1468 1:a765632148dc55d38c35c4f247c618701886cb2f
1468 1:a765632148dc55d38c35c4f247c618701886cb2f
1469 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1469 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1470 $ hg debugobsolete a765632148dc55d38c35c4f247c618701886cb2f
1470 $ hg debugobsolete a765632148dc55d38c35c4f247c618701886cb2f
1471 $ hg up null -q
1471 $ hg up null -q
1472 $ hg log --template='{rev}:{node}\n'
1472 $ hg log --template='{rev}:{node}\n'
1473 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1473 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1474 $ hg log --template='{rev}:{node}\n' --hidden
1474 $ hg log --template='{rev}:{node}\n' --hidden
1475 1:a765632148dc55d38c35c4f247c618701886cb2f
1475 1:a765632148dc55d38c35c4f247c618701886cb2f
1476 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1476 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1477 $ hg log -r a
1477 $ hg log -r a
1478 abort: hidden revision 'a'!
1478 abort: hidden revision 'a'!
1479 (use --hidden to access hidden revisions)
1479 (use --hidden to access hidden revisions)
1480 [255]
1480 [255]
1481
1481
1482 test that parent prevent a changeset to be hidden
1482 test that parent prevent a changeset to be hidden
1483
1483
1484 $ hg up 1 -q --hidden
1484 $ hg up 1 -q --hidden
1485 $ hg log --template='{rev}:{node}\n'
1485 $ hg log --template='{rev}:{node}\n'
1486 1:a765632148dc55d38c35c4f247c618701886cb2f
1486 1:a765632148dc55d38c35c4f247c618701886cb2f
1487 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1487 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1488
1488
1489 test that second parent prevent a changeset to be hidden too
1489 test that second parent prevent a changeset to be hidden too
1490
1490
1491 $ hg debugsetparents 0 1 # nothing suitable to merge here
1491 $ hg debugsetparents 0 1 # nothing suitable to merge here
1492 $ hg log --template='{rev}:{node}\n'
1492 $ hg log --template='{rev}:{node}\n'
1493 1:a765632148dc55d38c35c4f247c618701886cb2f
1493 1:a765632148dc55d38c35c4f247c618701886cb2f
1494 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1494 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1495 $ hg debugsetparents 1
1495 $ hg debugsetparents 1
1496 $ hg up -q null
1496 $ hg up -q null
1497
1497
1498 bookmarks prevent a changeset being hidden
1498 bookmarks prevent a changeset being hidden
1499
1499
1500 $ hg bookmark --hidden -r 1 X
1500 $ hg bookmark --hidden -r 1 X
1501 $ hg log --template '{rev}:{node}\n'
1501 $ hg log --template '{rev}:{node}\n'
1502 1:a765632148dc55d38c35c4f247c618701886cb2f
1502 1:a765632148dc55d38c35c4f247c618701886cb2f
1503 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1503 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1504 $ hg bookmark -d X
1504 $ hg bookmark -d X
1505
1505
1506 divergent bookmarks are not hidden
1506 divergent bookmarks are not hidden
1507
1507
1508 $ hg bookmark --hidden -r 1 X@foo
1508 $ hg bookmark --hidden -r 1 X@foo
1509 $ hg log --template '{rev}:{node}\n'
1509 $ hg log --template '{rev}:{node}\n'
1510 1:a765632148dc55d38c35c4f247c618701886cb2f
1510 1:a765632148dc55d38c35c4f247c618701886cb2f
1511 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1511 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1512
1512
1513 clear extensions configuration
1513 clear extensions configuration
1514 $ echo '[extensions]' >> $HGRCPATH
1514 $ echo '[extensions]' >> $HGRCPATH
1515 $ echo "obs=!" >> $HGRCPATH
1515 $ echo "obs=!" >> $HGRCPATH
1516 $ cd ..
1516 $ cd ..
1517
1517
1518 test -u/-k for problematic encoding
1518 test -u/-k for problematic encoding
1519 # unicode: cp932:
1519 # unicode: cp932:
1520 # u30A2 0x83 0x41(= 'A')
1520 # u30A2 0x83 0x41(= 'A')
1521 # u30C2 0x83 0x61(= 'a')
1521 # u30C2 0x83 0x61(= 'a')
1522
1522
1523 $ hg init problematicencoding
1523 $ hg init problematicencoding
1524 $ cd problematicencoding
1524 $ cd problematicencoding
1525
1525
1526 $ python > setup.sh <<EOF
1526 $ python > setup.sh <<EOF
1527 > print u'''
1527 > print u'''
1528 > echo a > text
1528 > echo a > text
1529 > hg add text
1529 > hg add text
1530 > hg --encoding utf-8 commit -u '\u30A2' -m none
1530 > hg --encoding utf-8 commit -u '\u30A2' -m none
1531 > echo b > text
1531 > echo b > text
1532 > hg --encoding utf-8 commit -u '\u30C2' -m none
1532 > hg --encoding utf-8 commit -u '\u30C2' -m none
1533 > echo c > text
1533 > echo c > text
1534 > hg --encoding utf-8 commit -u none -m '\u30A2'
1534 > hg --encoding utf-8 commit -u none -m '\u30A2'
1535 > echo d > text
1535 > echo d > text
1536 > hg --encoding utf-8 commit -u none -m '\u30C2'
1536 > hg --encoding utf-8 commit -u none -m '\u30C2'
1537 > '''.encode('utf-8')
1537 > '''.encode('utf-8')
1538 > EOF
1538 > EOF
1539 $ sh < setup.sh
1539 $ sh < setup.sh
1540
1540
1541 test in problematic encoding
1541 test in problematic encoding
1542 $ python > test.sh <<EOF
1542 $ python > test.sh <<EOF
1543 > print u'''
1543 > print u'''
1544 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
1544 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
1545 > echo ====
1545 > echo ====
1546 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
1546 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
1547 > echo ====
1547 > echo ====
1548 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
1548 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
1549 > echo ====
1549 > echo ====
1550 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
1550 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
1551 > '''.encode('cp932')
1551 > '''.encode('cp932')
1552 > EOF
1552 > EOF
1553 $ sh < test.sh
1553 $ sh < test.sh
1554 0
1554 0
1555 ====
1555 ====
1556 1
1556 1
1557 ====
1557 ====
1558 2
1558 2
1559 0
1559 0
1560 ====
1560 ====
1561 3
1561 3
1562 1
1562 1
1563
1563
1564 $ cd ..
1564 $ cd ..
1565
1565
1566 test hg log on non-existent files and on directories
1566 test hg log on non-existent files and on directories
1567 $ hg init issue1340
1567 $ hg init issue1340
1568 $ cd issue1340
1568 $ cd issue1340
1569 $ mkdir d1; mkdir D2; mkdir D3.i; mkdir d4.hg; mkdir d5.d; mkdir .d6
1569 $ mkdir d1; mkdir D2; mkdir D3.i; mkdir d4.hg; mkdir d5.d; mkdir .d6
1570 $ echo 1 > d1/f1
1570 $ echo 1 > d1/f1
1571 $ echo 1 > D2/f1
1571 $ echo 1 > D2/f1
1572 $ echo 1 > D3.i/f1
1572 $ echo 1 > D3.i/f1
1573 $ echo 1 > d4.hg/f1
1573 $ echo 1 > d4.hg/f1
1574 $ echo 1 > d5.d/f1
1574 $ echo 1 > d5.d/f1
1575 $ echo 1 > .d6/f1
1575 $ echo 1 > .d6/f1
1576 $ hg -q add .
1576 $ hg -q add .
1577 $ hg commit -m "a bunch of weird directories"
1577 $ hg commit -m "a bunch of weird directories"
1578 $ hg log -l1 d1/f1 | grep changeset
1578 $ hg log -l1 d1/f1 | grep changeset
1579 changeset: 0:65624cd9070a
1579 changeset: 0:65624cd9070a
1580 $ hg log -l1 f1
1580 $ hg log -l1 f1
1581 $ hg log -l1 . | grep changeset
1581 $ hg log -l1 . | grep changeset
1582 changeset: 0:65624cd9070a
1582 changeset: 0:65624cd9070a
1583 $ hg log -l1 ./ | grep changeset
1583 $ hg log -l1 ./ | grep changeset
1584 changeset: 0:65624cd9070a
1584 changeset: 0:65624cd9070a
1585 $ hg log -l1 d1 | grep changeset
1585 $ hg log -l1 d1 | grep changeset
1586 changeset: 0:65624cd9070a
1586 changeset: 0:65624cd9070a
1587 $ hg log -l1 D2 | grep changeset
1587 $ hg log -l1 D2 | grep changeset
1588 changeset: 0:65624cd9070a
1588 changeset: 0:65624cd9070a
1589 $ hg log -l1 D2/f1 | grep changeset
1589 $ hg log -l1 D2/f1 | grep changeset
1590 changeset: 0:65624cd9070a
1590 changeset: 0:65624cd9070a
1591 $ hg log -l1 D3.i | grep changeset
1591 $ hg log -l1 D3.i | grep changeset
1592 changeset: 0:65624cd9070a
1592 changeset: 0:65624cd9070a
1593 $ hg log -l1 D3.i/f1 | grep changeset
1593 $ hg log -l1 D3.i/f1 | grep changeset
1594 changeset: 0:65624cd9070a
1594 changeset: 0:65624cd9070a
1595 $ hg log -l1 d4.hg | grep changeset
1595 $ hg log -l1 d4.hg | grep changeset
1596 changeset: 0:65624cd9070a
1596 changeset: 0:65624cd9070a
1597 $ hg log -l1 d4.hg/f1 | grep changeset
1597 $ hg log -l1 d4.hg/f1 | grep changeset
1598 changeset: 0:65624cd9070a
1598 changeset: 0:65624cd9070a
1599 $ hg log -l1 d5.d | grep changeset
1599 $ hg log -l1 d5.d | grep changeset
1600 changeset: 0:65624cd9070a
1600 changeset: 0:65624cd9070a
1601 $ hg log -l1 d5.d/f1 | grep changeset
1601 $ hg log -l1 d5.d/f1 | grep changeset
1602 changeset: 0:65624cd9070a
1602 changeset: 0:65624cd9070a
1603 $ hg log -l1 .d6 | grep changeset
1603 $ hg log -l1 .d6 | grep changeset
1604 changeset: 0:65624cd9070a
1604 changeset: 0:65624cd9070a
1605 $ hg log -l1 .d6/f1 | grep changeset
1605 $ hg log -l1 .d6/f1 | grep changeset
1606 changeset: 0:65624cd9070a
1606 changeset: 0:65624cd9070a
1607
1607
1608 issue3772: hg log -r :null showing revision 0 as well
1608 issue3772: hg log -r :null showing revision 0 as well
1609
1609
1610 $ hg log -r :null
1610 $ hg log -r :null
1611 changeset: 0:65624cd9070a
1611 changeset: 0:65624cd9070a
1612 tag: tip
1612 tag: tip
1613 user: test
1613 user: test
1614 date: Thu Jan 01 00:00:00 1970 +0000
1614 date: Thu Jan 01 00:00:00 1970 +0000
1615 summary: a bunch of weird directories
1615 summary: a bunch of weird directories
1616
1616
1617 changeset: -1:000000000000
1617 changeset: -1:000000000000
1618 user:
1618 user:
1619 date: Thu Jan 01 00:00:00 1970 +0000
1619 date: Thu Jan 01 00:00:00 1970 +0000
1620
1620
1621 $ hg log -r null:null
1621 $ hg log -r null:null
1622 changeset: -1:000000000000
1622 changeset: -1:000000000000
1623 user:
1623 user:
1624 date: Thu Jan 01 00:00:00 1970 +0000
1624 date: Thu Jan 01 00:00:00 1970 +0000
1625
1625
1626 working-directory revision requires special treatment
1626 working-directory revision requires special treatment
1627
1627
1628 $ hg log -r 'wdir()'
1628 $ hg log -r 'wdir()'
1629 changeset: 0:65624cd9070a+
1629 changeset: 0:65624cd9070a+
1630 parent: 0:65624cd9070a
1630 user: test
1631 user: test
1631 date: [A-Za-z0-9:+ ]+ (re)
1632 date: [A-Za-z0-9:+ ]+ (re)
1632
1633
1633 $ hg log -r 'wdir()' -q
1634 $ hg log -r 'wdir()' -q
1634 0:65624cd9070a+
1635 0:65624cd9070a+
1635
1636
1636 $ hg log -r 'wdir()' --debug
1637 $ hg log -r 'wdir()' --debug
1637 changeset: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08+
1638 changeset: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08+
1638 phase: draft
1639 phase: draft
1639 parent: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08
1640 parent: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08
1640 parent: -1:0000000000000000000000000000000000000000
1641 parent: -1:0000000000000000000000000000000000000000
1641 user: test
1642 user: test
1642 date: [A-Za-z0-9:+ ]+ (re)
1643 date: [A-Za-z0-9:+ ]+ (re)
1643 extra: branch=default
1644 extra: branch=default
1644
1645
1645 $ hg log -r 'wdir()' -Tjson
1646 $ hg log -r 'wdir()' -Tjson
1646 [
1647 [
1647 {
1648 {
1648 "rev": null,
1649 "rev": null,
1649 "node": null,
1650 "node": null,
1650 "branch": "default",
1651 "branch": "default",
1651 "phase": "draft",
1652 "phase": "draft",
1652 "user": "test",
1653 "user": "test",
1653 "date": [*, 0], (glob)
1654 "date": [*, 0], (glob)
1654 "desc": "",
1655 "desc": "",
1655 "bookmarks": [],
1656 "bookmarks": [],
1656 "tags": [],
1657 "tags": [],
1657 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"]
1658 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"]
1658 }
1659 }
1659 ]
1660 ]
1660
1661
1661 $ hg log -r 'wdir()' -Tjson -q
1662 $ hg log -r 'wdir()' -Tjson -q
1662 [
1663 [
1663 {
1664 {
1664 "rev": null,
1665 "rev": null,
1665 "node": null
1666 "node": null
1666 }
1667 }
1667 ]
1668 ]
1668
1669
1669 $ hg log -r 'wdir()' -Tjson --debug
1670 $ hg log -r 'wdir()' -Tjson --debug
1670 [
1671 [
1671 {
1672 {
1672 "rev": null,
1673 "rev": null,
1673 "node": null,
1674 "node": null,
1674 "branch": "default",
1675 "branch": "default",
1675 "phase": "draft",
1676 "phase": "draft",
1676 "user": "test",
1677 "user": "test",
1677 "date": [*, 0], (glob)
1678 "date": [*, 0], (glob)
1678 "desc": "",
1679 "desc": "",
1679 "bookmarks": [],
1680 "bookmarks": [],
1680 "tags": [],
1681 "tags": [],
1681 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
1682 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
1682 "manifest": null,
1683 "manifest": null,
1683 "extra": {"branch": "default"},
1684 "extra": {"branch": "default"},
1684 "modified": [],
1685 "modified": [],
1685 "added": [],
1686 "added": [],
1686 "removed": []
1687 "removed": []
1687 }
1688 }
1688 ]
1689 ]
1689
1690
1690 Check that adding an arbitrary name shows up in log automatically
1691 Check that adding an arbitrary name shows up in log automatically
1691
1692
1692 $ cat > ../names.py <<EOF
1693 $ cat > ../names.py <<EOF
1693 > """A small extension to test adding arbitrary names to a repo"""
1694 > """A small extension to test adding arbitrary names to a repo"""
1694 > from mercurial.namespaces import namespace
1695 > from mercurial.namespaces import namespace
1695 >
1696 >
1696 > def reposetup(ui, repo):
1697 > def reposetup(ui, repo):
1697 > foo = {'foo': repo[0].node()}
1698 > foo = {'foo': repo[0].node()}
1698 > names = lambda r: foo.keys()
1699 > names = lambda r: foo.keys()
1699 > namemap = lambda r, name: foo.get(name)
1700 > namemap = lambda r, name: foo.get(name)
1700 > nodemap = lambda r, node: [name for name, n in foo.iteritems()
1701 > nodemap = lambda r, node: [name for name, n in foo.iteritems()
1701 > if n == node]
1702 > if n == node]
1702 > ns = namespace("bars", templatename="bar", logname="barlog",
1703 > ns = namespace("bars", templatename="bar", logname="barlog",
1703 > colorname="barcolor", listnames=names, namemap=namemap,
1704 > colorname="barcolor", listnames=names, namemap=namemap,
1704 > nodemap=nodemap)
1705 > nodemap=nodemap)
1705 >
1706 >
1706 > repo.names.addnamespace(ns)
1707 > repo.names.addnamespace(ns)
1707 > EOF
1708 > EOF
1708
1709
1709 $ hg --config extensions.names=../names.py log -r 0
1710 $ hg --config extensions.names=../names.py log -r 0
1710 changeset: 0:65624cd9070a
1711 changeset: 0:65624cd9070a
1711 tag: tip
1712 tag: tip
1712 barlog: foo
1713 barlog: foo
1713 user: test
1714 user: test
1714 date: Thu Jan 01 00:00:00 1970 +0000
1715 date: Thu Jan 01 00:00:00 1970 +0000
1715 summary: a bunch of weird directories
1716 summary: a bunch of weird directories
1716
1717
1717 $ hg --config extensions.names=../names.py \
1718 $ hg --config extensions.names=../names.py \
1718 > --config extensions.color= --config color.log.barcolor=red \
1719 > --config extensions.color= --config color.log.barcolor=red \
1719 > --color=always log -r 0
1720 > --color=always log -r 0
1720 \x1b[0;33mchangeset: 0:65624cd9070a\x1b[0m (esc)
1721 \x1b[0;33mchangeset: 0:65624cd9070a\x1b[0m (esc)
1721 tag: tip
1722 tag: tip
1722 \x1b[0;31mbarlog: foo\x1b[0m (esc)
1723 \x1b[0;31mbarlog: foo\x1b[0m (esc)
1723 user: test
1724 user: test
1724 date: Thu Jan 01 00:00:00 1970 +0000
1725 date: Thu Jan 01 00:00:00 1970 +0000
1725 summary: a bunch of weird directories
1726 summary: a bunch of weird directories
1726
1727
1727 $ hg --config extensions.names=../names.py log -r 0 --template '{bars}\n'
1728 $ hg --config extensions.names=../names.py log -r 0 --template '{bars}\n'
1728 foo
1729 foo
1729
1730
1730 $ cd ..
1731 $ cd ..
1731
1732
1732 hg log -f dir across branches
1733 hg log -f dir across branches
1733
1734
1734 $ hg init acrossbranches
1735 $ hg init acrossbranches
1735 $ cd acrossbranches
1736 $ cd acrossbranches
1736 $ mkdir d
1737 $ mkdir d
1737 $ echo a > d/a && hg ci -Aqm a
1738 $ echo a > d/a && hg ci -Aqm a
1738 $ echo b > d/a && hg ci -Aqm b
1739 $ echo b > d/a && hg ci -Aqm b
1739 $ hg up -q 0
1740 $ hg up -q 0
1740 $ echo b > d/a && hg ci -Aqm c
1741 $ echo b > d/a && hg ci -Aqm c
1741 $ hg log -f d -T '{desc}' -G
1742 $ hg log -f d -T '{desc}' -G
1742 @ c
1743 @ c
1743 |
1744 |
1744 o a
1745 o a
1745
1746
1746 Ensure that largefiles doesn't interfere with following a normal file
1747 Ensure that largefiles doesn't interfere with following a normal file
1747 $ hg --config extensions.largefiles= log -f d -T '{desc}' -G
1748 $ hg --config extensions.largefiles= log -f d -T '{desc}' -G
1748 @ c
1749 @ c
1749 |
1750 |
1750 o a
1751 o a
1751
1752
1752 $ hg log -f d/a -T '{desc}' -G
1753 $ hg log -f d/a -T '{desc}' -G
1753 @ c
1754 @ c
1754 |
1755 |
1755 o a
1756 o a
1756
1757
1757 $ cd ..
1758 $ cd ..
1758
1759
1759 hg log -f with linkrev pointing to another branch
1760 hg log -f with linkrev pointing to another branch
1760 -------------------------------------------------
1761 -------------------------------------------------
1761
1762
1762 create history with a filerev whose linkrev points to another branch
1763 create history with a filerev whose linkrev points to another branch
1763
1764
1764 $ hg init branchedlinkrev
1765 $ hg init branchedlinkrev
1765 $ cd branchedlinkrev
1766 $ cd branchedlinkrev
1766 $ echo 1 > a
1767 $ echo 1 > a
1767 $ hg commit -Am 'content1'
1768 $ hg commit -Am 'content1'
1768 adding a
1769 adding a
1769 $ echo 2 > a
1770 $ echo 2 > a
1770 $ hg commit -m 'content2'
1771 $ hg commit -m 'content2'
1771 $ hg up --rev 'desc(content1)'
1772 $ hg up --rev 'desc(content1)'
1772 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1773 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1773 $ echo unrelated > unrelated
1774 $ echo unrelated > unrelated
1774 $ hg commit -Am 'unrelated'
1775 $ hg commit -Am 'unrelated'
1775 adding unrelated
1776 adding unrelated
1776 created new head
1777 created new head
1777 $ hg graft -r 'desc(content2)'
1778 $ hg graft -r 'desc(content2)'
1778 grafting 1:2294ae80ad84 "content2"
1779 grafting 1:2294ae80ad84 "content2"
1779 $ echo 3 > a
1780 $ echo 3 > a
1780 $ hg commit -m 'content3'
1781 $ hg commit -m 'content3'
1781 $ hg log -G
1782 $ hg log -G
1782 @ changeset: 4:50b9b36e9c5d
1783 @ changeset: 4:50b9b36e9c5d
1783 | tag: tip
1784 | tag: tip
1784 | user: test
1785 | user: test
1785 | date: Thu Jan 01 00:00:00 1970 +0000
1786 | date: Thu Jan 01 00:00:00 1970 +0000
1786 | summary: content3
1787 | summary: content3
1787 |
1788 |
1788 o changeset: 3:15b2327059e5
1789 o changeset: 3:15b2327059e5
1789 | user: test
1790 | user: test
1790 | date: Thu Jan 01 00:00:00 1970 +0000
1791 | date: Thu Jan 01 00:00:00 1970 +0000
1791 | summary: content2
1792 | summary: content2
1792 |
1793 |
1793 o changeset: 2:2029acd1168c
1794 o changeset: 2:2029acd1168c
1794 | parent: 0:ae0a3c9f9e95
1795 | parent: 0:ae0a3c9f9e95
1795 | user: test
1796 | user: test
1796 | date: Thu Jan 01 00:00:00 1970 +0000
1797 | date: Thu Jan 01 00:00:00 1970 +0000
1797 | summary: unrelated
1798 | summary: unrelated
1798 |
1799 |
1799 | o changeset: 1:2294ae80ad84
1800 | o changeset: 1:2294ae80ad84
1800 |/ user: test
1801 |/ user: test
1801 | date: Thu Jan 01 00:00:00 1970 +0000
1802 | date: Thu Jan 01 00:00:00 1970 +0000
1802 | summary: content2
1803 | summary: content2
1803 |
1804 |
1804 o changeset: 0:ae0a3c9f9e95
1805 o changeset: 0:ae0a3c9f9e95
1805 user: test
1806 user: test
1806 date: Thu Jan 01 00:00:00 1970 +0000
1807 date: Thu Jan 01 00:00:00 1970 +0000
1807 summary: content1
1808 summary: content1
1808
1809
1809
1810
1810 log -f on the file should list the graft result.
1811 log -f on the file should list the graft result.
1811
1812
1812 $ hg log -Gf a
1813 $ hg log -Gf a
1813 @ changeset: 4:50b9b36e9c5d
1814 @ changeset: 4:50b9b36e9c5d
1814 | tag: tip
1815 | tag: tip
1815 | user: test
1816 | user: test
1816 | date: Thu Jan 01 00:00:00 1970 +0000
1817 | date: Thu Jan 01 00:00:00 1970 +0000
1817 | summary: content3
1818 | summary: content3
1818 |
1819 |
1819 o changeset: 3:15b2327059e5
1820 o changeset: 3:15b2327059e5
1820 | user: test
1821 | user: test
1821 | date: Thu Jan 01 00:00:00 1970 +0000
1822 | date: Thu Jan 01 00:00:00 1970 +0000
1822 | summary: content2
1823 | summary: content2
1823 |
1824 |
1824 o changeset: 0:ae0a3c9f9e95
1825 o changeset: 0:ae0a3c9f9e95
1825 user: test
1826 user: test
1826 date: Thu Jan 01 00:00:00 1970 +0000
1827 date: Thu Jan 01 00:00:00 1970 +0000
1827 summary: content1
1828 summary: content1
1828
1829
1829
1830
1830 plain log lists the original version
1831 plain log lists the original version
1831 (XXX we should probably list both)
1832 (XXX we should probably list both)
1832
1833
1833 $ hg log -G a
1834 $ hg log -G a
1834 @ changeset: 4:50b9b36e9c5d
1835 @ changeset: 4:50b9b36e9c5d
1835 | tag: tip
1836 | tag: tip
1836 | user: test
1837 | user: test
1837 | date: Thu Jan 01 00:00:00 1970 +0000
1838 | date: Thu Jan 01 00:00:00 1970 +0000
1838 | summary: content3
1839 | summary: content3
1839 |
1840 |
1840 | o changeset: 1:2294ae80ad84
1841 | o changeset: 1:2294ae80ad84
1841 |/ user: test
1842 |/ user: test
1842 | date: Thu Jan 01 00:00:00 1970 +0000
1843 | date: Thu Jan 01 00:00:00 1970 +0000
1843 | summary: content2
1844 | summary: content2
1844 |
1845 |
1845 o changeset: 0:ae0a3c9f9e95
1846 o changeset: 0:ae0a3c9f9e95
1846 user: test
1847 user: test
1847 date: Thu Jan 01 00:00:00 1970 +0000
1848 date: Thu Jan 01 00:00:00 1970 +0000
1848 summary: content1
1849 summary: content1
1849
1850
1850
1851
1851 hg log -f from the grafted changeset
1852 hg log -f from the grafted changeset
1852 (The bootstrap should properly take the topology in account)
1853 (The bootstrap should properly take the topology in account)
1853
1854
1854 $ hg up 'desc(content3)^'
1855 $ hg up 'desc(content3)^'
1855 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1856 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1856 $ hg log -Gf a
1857 $ hg log -Gf a
1857 @ changeset: 3:15b2327059e5
1858 @ changeset: 3:15b2327059e5
1858 | user: test
1859 | user: test
1859 | date: Thu Jan 01 00:00:00 1970 +0000
1860 | date: Thu Jan 01 00:00:00 1970 +0000
1860 | summary: content2
1861 | summary: content2
1861 |
1862 |
1862 o changeset: 0:ae0a3c9f9e95
1863 o changeset: 0:ae0a3c9f9e95
1863 user: test
1864 user: test
1864 date: Thu Jan 01 00:00:00 1970 +0000
1865 date: Thu Jan 01 00:00:00 1970 +0000
1865 summary: content1
1866 summary: content1
1866
1867
1867
1868
1868 Test that we use the first non-hidden changeset in that case.
1869 Test that we use the first non-hidden changeset in that case.
1869
1870
1870 (hide the changeset)
1871 (hide the changeset)
1871
1872
1872 $ hg log -T '{node}\n' -r 1
1873 $ hg log -T '{node}\n' -r 1
1873 2294ae80ad8447bc78383182eeac50cb049df623
1874 2294ae80ad8447bc78383182eeac50cb049df623
1874 $ hg debugobsolete 2294ae80ad8447bc78383182eeac50cb049df623
1875 $ hg debugobsolete 2294ae80ad8447bc78383182eeac50cb049df623
1875 $ hg log -G
1876 $ hg log -G
1876 o changeset: 4:50b9b36e9c5d
1877 o changeset: 4:50b9b36e9c5d
1877 | tag: tip
1878 | tag: tip
1878 | user: test
1879 | user: test
1879 | date: Thu Jan 01 00:00:00 1970 +0000
1880 | date: Thu Jan 01 00:00:00 1970 +0000
1880 | summary: content3
1881 | summary: content3
1881 |
1882 |
1882 @ changeset: 3:15b2327059e5
1883 @ changeset: 3:15b2327059e5
1883 | user: test
1884 | user: test
1884 | date: Thu Jan 01 00:00:00 1970 +0000
1885 | date: Thu Jan 01 00:00:00 1970 +0000
1885 | summary: content2
1886 | summary: content2
1886 |
1887 |
1887 o changeset: 2:2029acd1168c
1888 o changeset: 2:2029acd1168c
1888 | parent: 0:ae0a3c9f9e95
1889 | parent: 0:ae0a3c9f9e95
1889 | user: test
1890 | user: test
1890 | date: Thu Jan 01 00:00:00 1970 +0000
1891 | date: Thu Jan 01 00:00:00 1970 +0000
1891 | summary: unrelated
1892 | summary: unrelated
1892 |
1893 |
1893 o changeset: 0:ae0a3c9f9e95
1894 o changeset: 0:ae0a3c9f9e95
1894 user: test
1895 user: test
1895 date: Thu Jan 01 00:00:00 1970 +0000
1896 date: Thu Jan 01 00:00:00 1970 +0000
1896 summary: content1
1897 summary: content1
1897
1898
1898
1899
1899 Check that log on the file does not drop the file revision.
1900 Check that log on the file does not drop the file revision.
1900
1901
1901 $ hg log -G a
1902 $ hg log -G a
1902 o changeset: 4:50b9b36e9c5d
1903 o changeset: 4:50b9b36e9c5d
1903 | tag: tip
1904 | tag: tip
1904 | user: test
1905 | user: test
1905 | date: Thu Jan 01 00:00:00 1970 +0000
1906 | date: Thu Jan 01 00:00:00 1970 +0000
1906 | summary: content3
1907 | summary: content3
1907 |
1908 |
1908 @ changeset: 3:15b2327059e5
1909 @ changeset: 3:15b2327059e5
1909 | user: test
1910 | user: test
1910 | date: Thu Jan 01 00:00:00 1970 +0000
1911 | date: Thu Jan 01 00:00:00 1970 +0000
1911 | summary: content2
1912 | summary: content2
1912 |
1913 |
1913 o changeset: 0:ae0a3c9f9e95
1914 o changeset: 0:ae0a3c9f9e95
1914 user: test
1915 user: test
1915 date: Thu Jan 01 00:00:00 1970 +0000
1916 date: Thu Jan 01 00:00:00 1970 +0000
1916 summary: content1
1917 summary: content1
1917
1918
1918
1919
1919 Even when a head revision is linkrev-shadowed.
1920 Even when a head revision is linkrev-shadowed.
1920
1921
1921 $ hg log -T '{node}\n' -r 4
1922 $ hg log -T '{node}\n' -r 4
1922 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
1923 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
1923 $ hg debugobsolete 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
1924 $ hg debugobsolete 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
1924 $ hg log -G a
1925 $ hg log -G a
1925 @ changeset: 3:15b2327059e5
1926 @ changeset: 3:15b2327059e5
1926 | tag: tip
1927 | tag: tip
1927 | user: test
1928 | user: test
1928 | date: Thu Jan 01 00:00:00 1970 +0000
1929 | date: Thu Jan 01 00:00:00 1970 +0000
1929 | summary: content2
1930 | summary: content2
1930 |
1931 |
1931 o changeset: 0:ae0a3c9f9e95
1932 o changeset: 0:ae0a3c9f9e95
1932 user: test
1933 user: test
1933 date: Thu Jan 01 00:00:00 1970 +0000
1934 date: Thu Jan 01 00:00:00 1970 +0000
1934 summary: content1
1935 summary: content1
1935
1936
1936
1937
1937 $ cd ..
1938 $ cd ..
1938
1939
1939 Even when the file revision is missing from some head:
1940 Even when the file revision is missing from some head:
1940
1941
1941 $ hg init issue4490
1942 $ hg init issue4490
1942 $ cd issue4490
1943 $ cd issue4490
1943 $ echo '[experimental]' >> .hg/hgrc
1944 $ echo '[experimental]' >> .hg/hgrc
1944 $ echo 'evolution=createmarkers' >> .hg/hgrc
1945 $ echo 'evolution=createmarkers' >> .hg/hgrc
1945 $ echo a > a
1946 $ echo a > a
1946 $ hg ci -Am0
1947 $ hg ci -Am0
1947 adding a
1948 adding a
1948 $ echo b > b
1949 $ echo b > b
1949 $ hg ci -Am1
1950 $ hg ci -Am1
1950 adding b
1951 adding b
1951 $ echo B > b
1952 $ echo B > b
1952 $ hg ci --amend -m 1
1953 $ hg ci --amend -m 1
1953 $ hg up 0
1954 $ hg up 0
1954 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1955 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1955 $ echo c > c
1956 $ echo c > c
1956 $ hg ci -Am2
1957 $ hg ci -Am2
1957 adding c
1958 adding c
1958 created new head
1959 created new head
1959 $ hg up 'head() and not .'
1960 $ hg up 'head() and not .'
1960 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1961 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1961 $ hg log -G
1962 $ hg log -G
1962 o changeset: 4:db815d6d32e6
1963 o changeset: 4:db815d6d32e6
1963 | tag: tip
1964 | tag: tip
1964 | parent: 0:f7b1eb17ad24
1965 | parent: 0:f7b1eb17ad24
1965 | user: test
1966 | user: test
1966 | date: Thu Jan 01 00:00:00 1970 +0000
1967 | date: Thu Jan 01 00:00:00 1970 +0000
1967 | summary: 2
1968 | summary: 2
1968 |
1969 |
1969 | @ changeset: 3:9bc8ce7f9356
1970 | @ changeset: 3:9bc8ce7f9356
1970 |/ parent: 0:f7b1eb17ad24
1971 |/ parent: 0:f7b1eb17ad24
1971 | user: test
1972 | user: test
1972 | date: Thu Jan 01 00:00:00 1970 +0000
1973 | date: Thu Jan 01 00:00:00 1970 +0000
1973 | summary: 1
1974 | summary: 1
1974 |
1975 |
1975 o changeset: 0:f7b1eb17ad24
1976 o changeset: 0:f7b1eb17ad24
1976 user: test
1977 user: test
1977 date: Thu Jan 01 00:00:00 1970 +0000
1978 date: Thu Jan 01 00:00:00 1970 +0000
1978 summary: 0
1979 summary: 0
1979
1980
1980 $ hg log -f -G b
1981 $ hg log -f -G b
1981 @ changeset: 3:9bc8ce7f9356
1982 @ changeset: 3:9bc8ce7f9356
1982 | parent: 0:f7b1eb17ad24
1983 | parent: 0:f7b1eb17ad24
1983 | user: test
1984 | user: test
1984 | date: Thu Jan 01 00:00:00 1970 +0000
1985 | date: Thu Jan 01 00:00:00 1970 +0000
1985 | summary: 1
1986 | summary: 1
1986 |
1987 |
1987 $ hg log -G b
1988 $ hg log -G b
1988 @ changeset: 3:9bc8ce7f9356
1989 @ changeset: 3:9bc8ce7f9356
1989 | parent: 0:f7b1eb17ad24
1990 | parent: 0:f7b1eb17ad24
1990 | user: test
1991 | user: test
1991 | date: Thu Jan 01 00:00:00 1970 +0000
1992 | date: Thu Jan 01 00:00:00 1970 +0000
1992 | summary: 1
1993 | summary: 1
1993 |
1994 |
1994 $ cd ..
1995 $ cd ..
1995
1996
1996 Check proper report when the manifest changes but not the file issue4499
1997 Check proper report when the manifest changes but not the file issue4499
1997 ------------------------------------------------------------------------
1998 ------------------------------------------------------------------------
1998
1999
1999 $ hg init issue4499
2000 $ hg init issue4499
2000 $ cd issue4499
2001 $ cd issue4499
2001 $ for f in A B C D F E G H I J K L M N O P Q R S T U; do
2002 $ for f in A B C D F E G H I J K L M N O P Q R S T U; do
2002 > echo 1 > $f;
2003 > echo 1 > $f;
2003 > hg add $f;
2004 > hg add $f;
2004 > done
2005 > done
2005 $ hg commit -m 'A1B1C1'
2006 $ hg commit -m 'A1B1C1'
2006 $ echo 2 > A
2007 $ echo 2 > A
2007 $ echo 2 > B
2008 $ echo 2 > B
2008 $ echo 2 > C
2009 $ echo 2 > C
2009 $ hg commit -m 'A2B2C2'
2010 $ hg commit -m 'A2B2C2'
2010 $ hg up 0
2011 $ hg up 0
2011 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
2012 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
2012 $ echo 3 > A
2013 $ echo 3 > A
2013 $ echo 2 > B
2014 $ echo 2 > B
2014 $ echo 2 > C
2015 $ echo 2 > C
2015 $ hg commit -m 'A3B2C2'
2016 $ hg commit -m 'A3B2C2'
2016 created new head
2017 created new head
2017
2018
2018 $ hg log -G
2019 $ hg log -G
2019 @ changeset: 2:fe5fc3d0eb17
2020 @ changeset: 2:fe5fc3d0eb17
2020 | tag: tip
2021 | tag: tip
2021 | parent: 0:abf4f0e38563
2022 | parent: 0:abf4f0e38563
2022 | user: test
2023 | user: test
2023 | date: Thu Jan 01 00:00:00 1970 +0000
2024 | date: Thu Jan 01 00:00:00 1970 +0000
2024 | summary: A3B2C2
2025 | summary: A3B2C2
2025 |
2026 |
2026 | o changeset: 1:07dcc6b312c0
2027 | o changeset: 1:07dcc6b312c0
2027 |/ user: test
2028 |/ user: test
2028 | date: Thu Jan 01 00:00:00 1970 +0000
2029 | date: Thu Jan 01 00:00:00 1970 +0000
2029 | summary: A2B2C2
2030 | summary: A2B2C2
2030 |
2031 |
2031 o changeset: 0:abf4f0e38563
2032 o changeset: 0:abf4f0e38563
2032 user: test
2033 user: test
2033 date: Thu Jan 01 00:00:00 1970 +0000
2034 date: Thu Jan 01 00:00:00 1970 +0000
2034 summary: A1B1C1
2035 summary: A1B1C1
2035
2036
2036
2037
2037 Log -f on B should reports current changesets
2038 Log -f on B should reports current changesets
2038
2039
2039 $ hg log -fG B
2040 $ hg log -fG B
2040 @ changeset: 2:fe5fc3d0eb17
2041 @ changeset: 2:fe5fc3d0eb17
2041 | tag: tip
2042 | tag: tip
2042 | parent: 0:abf4f0e38563
2043 | parent: 0:abf4f0e38563
2043 | user: test
2044 | user: test
2044 | date: Thu Jan 01 00:00:00 1970 +0000
2045 | date: Thu Jan 01 00:00:00 1970 +0000
2045 | summary: A3B2C2
2046 | summary: A3B2C2
2046 |
2047 |
2047 o changeset: 0:abf4f0e38563
2048 o changeset: 0:abf4f0e38563
2048 user: test
2049 user: test
2049 date: Thu Jan 01 00:00:00 1970 +0000
2050 date: Thu Jan 01 00:00:00 1970 +0000
2050 summary: A1B1C1
2051 summary: A1B1C1
2051
2052
2052 $ cd ..
2053 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now