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