##// END OF EJS Templates
record: fix record with change on moved file crashes (issue4619)...
Laurent Charignon -
r24837:edf907bd stable
parent child Browse files
Show More
@@ -1,3254 +1,3243 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile, cStringIO, shutil
10 import os, sys, errno, re, tempfile, cStringIO, shutil
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import encoding
16 import encoding
17 import crecord as crecordmod
17 import crecord as crecordmod
18 import lock as lockmod
18 import lock as lockmod
19
19
20 def parsealiases(cmd):
20 def parsealiases(cmd):
21 return cmd.lstrip("^").split("|")
21 return cmd.lstrip("^").split("|")
22
22
23 def setupwrapcolorwrite(ui):
23 def setupwrapcolorwrite(ui):
24 # wrap ui.write so diff output can be labeled/colorized
24 # wrap ui.write so diff output can be labeled/colorized
25 def wrapwrite(orig, *args, **kw):
25 def wrapwrite(orig, *args, **kw):
26 label = kw.pop('label', '')
26 label = kw.pop('label', '')
27 for chunk, l in patch.difflabel(lambda: args):
27 for chunk, l in patch.difflabel(lambda: args):
28 orig(chunk, label=label + l)
28 orig(chunk, label=label + l)
29
29
30 oldwrite = ui.write
30 oldwrite = ui.write
31 def wrap(*args, **kwargs):
31 def wrap(*args, **kwargs):
32 return wrapwrite(oldwrite, *args, **kwargs)
32 return wrapwrite(oldwrite, *args, **kwargs)
33 setattr(ui, 'write', wrap)
33 setattr(ui, 'write', wrap)
34 return oldwrite
34 return oldwrite
35
35
36 def filterchunks(ui, originalhunks, usecurses, testfile):
36 def filterchunks(ui, originalhunks, usecurses, testfile):
37 if usecurses:
37 if usecurses:
38 if testfile:
38 if testfile:
39 recordfn = crecordmod.testdecorator(testfile,
39 recordfn = crecordmod.testdecorator(testfile,
40 crecordmod.testchunkselector)
40 crecordmod.testchunkselector)
41 else:
41 else:
42 recordfn = crecordmod.chunkselector
42 recordfn = crecordmod.chunkselector
43
43
44 return crecordmod.filterpatch(ui, originalhunks, recordfn)
44 return crecordmod.filterpatch(ui, originalhunks, recordfn)
45
45
46 else:
46 else:
47 return patch.filterpatch(ui, originalhunks)
47 return patch.filterpatch(ui, originalhunks)
48
48
49 def recordfilter(ui, originalhunks):
49 def recordfilter(ui, originalhunks):
50 usecurses = ui.configbool('experimental', 'crecord', False)
50 usecurses = ui.configbool('experimental', 'crecord', False)
51 testfile = ui.config('experimental', 'crecordtest', None)
51 testfile = ui.config('experimental', 'crecordtest', None)
52 oldwrite = setupwrapcolorwrite(ui)
52 oldwrite = setupwrapcolorwrite(ui)
53 try:
53 try:
54 newchunks = filterchunks(ui, originalhunks, usecurses, testfile)
54 newchunks = filterchunks(ui, originalhunks, usecurses, testfile)
55 finally:
55 finally:
56 ui.write = oldwrite
56 ui.write = oldwrite
57 return newchunks
57 return newchunks
58
58
59 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
59 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
60 filterfn, *pats, **opts):
60 filterfn, *pats, **opts):
61 import merge as mergemod
61 import merge as mergemod
62 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
63 ishunk = lambda x: isinstance(x, hunkclasses)
64
62
65 if not ui.interactive():
63 if not ui.interactive():
66 raise util.Abort(_('running non-interactively, use %s instead') %
64 raise util.Abort(_('running non-interactively, use %s instead') %
67 cmdsuggest)
65 cmdsuggest)
68
66
69 # make sure username is set before going interactive
67 # make sure username is set before going interactive
70 if not opts.get('user'):
68 if not opts.get('user'):
71 ui.username() # raise exception, username not provided
69 ui.username() # raise exception, username not provided
72
70
73 def recordfunc(ui, repo, message, match, opts):
71 def recordfunc(ui, repo, message, match, opts):
74 """This is generic record driver.
72 """This is generic record driver.
75
73
76 Its job is to interactively filter local changes, and
74 Its job is to interactively filter local changes, and
77 accordingly prepare working directory into a state in which the
75 accordingly prepare working directory into a state in which the
78 job can be delegated to a non-interactive commit command such as
76 job can be delegated to a non-interactive commit command such as
79 'commit' or 'qrefresh'.
77 'commit' or 'qrefresh'.
80
78
81 After the actual job is done by non-interactive command, the
79 After the actual job is done by non-interactive command, the
82 working directory is restored to its original state.
80 working directory is restored to its original state.
83
81
84 In the end we'll record interesting changes, and everything else
82 In the end we'll record interesting changes, and everything else
85 will be left in place, so the user can continue working.
83 will be left in place, so the user can continue working.
86 """
84 """
87
85
88 checkunfinished(repo, commit=True)
86 checkunfinished(repo, commit=True)
89 merge = len(repo[None].parents()) > 1
87 merge = len(repo[None].parents()) > 1
90 if merge:
88 if merge:
91 raise util.Abort(_('cannot partially commit a merge '
89 raise util.Abort(_('cannot partially commit a merge '
92 '(use "hg commit" instead)'))
90 '(use "hg commit" instead)'))
93
91
94 status = repo.status(match=match)
92 status = repo.status(match=match)
95 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
93 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
96 diffopts.nodates = True
94 diffopts.nodates = True
97 diffopts.git = True
95 diffopts.git = True
98 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
96 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
99 originalchunks = patch.parsepatch(originaldiff)
97 originalchunks = patch.parsepatch(originaldiff)
100
98
101 # 1. filter patch, so we have intending-to apply subset of it
99 # 1. filter patch, so we have intending-to apply subset of it
102 try:
100 try:
103 chunks = filterfn(ui, originalchunks)
101 chunks = filterfn(ui, originalchunks)
104 except patch.PatchError, err:
102 except patch.PatchError, err:
105 raise util.Abort(_('error parsing patch: %s') % err)
103 raise util.Abort(_('error parsing patch: %s') % err)
106
104
107 contenders = set()
105 contenders = set()
108 for h in chunks:
106 for h in chunks:
109 try:
107 try:
110 contenders.update(set(h.files()))
108 contenders.update(set(h.files()))
111 except AttributeError:
109 except AttributeError:
112 pass
110 pass
113
111
114 changed = status.modified + status.added + status.removed
112 changed = status.modified + status.added + status.removed
115 newfiles = [f for f in changed if f in contenders]
113 newfiles = [f for f in changed if f in contenders]
116 if not newfiles:
114 if not newfiles:
117 ui.status(_('no changes to record\n'))
115 ui.status(_('no changes to record\n'))
118 return 0
116 return 0
119
117
120 newandmodifiedfiles = set()
121 for h in chunks:
122 isnew = h.filename() in status.added
123 if ishunk(h) and isnew and not h in originalchunks:
124 newandmodifiedfiles.add(h.filename())
125
126 modified = set(status.modified)
118 modified = set(status.modified)
127
119
128 # 2. backup changed files, so we can restore them in the end
120 # 2. backup changed files, so we can restore them in the end
129
121
130 if backupall:
122 if backupall:
131 tobackup = changed
123 tobackup = changed
132 else:
124 else:
133 tobackup = [f for f in newfiles
125 tobackup = [f for f in newfiles if f in modified]
134 if f in modified or f in newandmodifiedfiles]
135
126
136 backups = {}
127 backups = {}
137 if tobackup:
128 if tobackup:
138 backupdir = repo.join('record-backups')
129 backupdir = repo.join('record-backups')
139 try:
130 try:
140 os.mkdir(backupdir)
131 os.mkdir(backupdir)
141 except OSError, err:
132 except OSError, err:
142 if err.errno != errno.EEXIST:
133 if err.errno != errno.EEXIST:
143 raise
134 raise
144 try:
135 try:
145 # backup continues
136 # backup continues
146 for f in tobackup:
137 for f in tobackup:
147 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
138 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
148 dir=backupdir)
139 dir=backupdir)
149 os.close(fd)
140 os.close(fd)
150 ui.debug('backup %r as %r\n' % (f, tmpname))
141 ui.debug('backup %r as %r\n' % (f, tmpname))
151 util.copyfile(repo.wjoin(f), tmpname)
142 util.copyfile(repo.wjoin(f), tmpname)
152 shutil.copystat(repo.wjoin(f), tmpname)
143 shutil.copystat(repo.wjoin(f), tmpname)
153 backups[f] = tmpname
144 backups[f] = tmpname
154
145
155 fp = cStringIO.StringIO()
146 fp = cStringIO.StringIO()
156 for c in chunks:
147 for c in chunks:
157 fname = c.filename()
148 fname = c.filename()
158 if fname in backups or fname in newandmodifiedfiles:
149 if fname in backups:
159 c.write(fp)
150 c.write(fp)
160 dopatch = fp.tell()
151 dopatch = fp.tell()
161 fp.seek(0)
152 fp.seek(0)
162
153
163 [os.unlink(c) for c in newandmodifiedfiles]
164
165 # 3a. apply filtered patch to clean repo (clean)
154 # 3a. apply filtered patch to clean repo (clean)
166 if backups:
155 if backups:
167 # Equivalent to hg.revert
156 # Equivalent to hg.revert
168 choices = lambda key: key in backups
157 choices = lambda key: key in backups
169 mergemod.update(repo, repo.dirstate.p1(),
158 mergemod.update(repo, repo.dirstate.p1(),
170 False, True, choices)
159 False, True, choices)
171
160
172 # 3b. (apply)
161 # 3b. (apply)
173 if dopatch:
162 if dopatch:
174 try:
163 try:
175 ui.debug('applying patch\n')
164 ui.debug('applying patch\n')
176 ui.debug(fp.getvalue())
165 ui.debug(fp.getvalue())
177 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
166 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
178 except patch.PatchError, err:
167 except patch.PatchError, err:
179 raise util.Abort(str(err))
168 raise util.Abort(str(err))
180 del fp
169 del fp
181
170
182 # 4. We prepared working directory according to filtered
171 # 4. We prepared working directory according to filtered
183 # patch. Now is the time to delegate the job to
172 # patch. Now is the time to delegate the job to
184 # commit/qrefresh or the like!
173 # commit/qrefresh or the like!
185
174
186 # Make all of the pathnames absolute.
175 # Make all of the pathnames absolute.
187 newfiles = [repo.wjoin(nf) for nf in newfiles]
176 newfiles = [repo.wjoin(nf) for nf in newfiles]
188 return commitfunc(ui, repo, *newfiles, **opts)
177 return commitfunc(ui, repo, *newfiles, **opts)
189 finally:
178 finally:
190 # 5. finally restore backed-up files
179 # 5. finally restore backed-up files
191 try:
180 try:
192 for realname, tmpname in backups.iteritems():
181 for realname, tmpname in backups.iteritems():
193 ui.debug('restoring %r to %r\n' % (tmpname, realname))
182 ui.debug('restoring %r to %r\n' % (tmpname, realname))
194 util.copyfile(tmpname, repo.wjoin(realname))
183 util.copyfile(tmpname, repo.wjoin(realname))
195 # Our calls to copystat() here and above are a
184 # Our calls to copystat() here and above are a
196 # hack to trick any editors that have f open that
185 # hack to trick any editors that have f open that
197 # we haven't modified them.
186 # we haven't modified them.
198 #
187 #
199 # Also note that this racy as an editor could
188 # Also note that this racy as an editor could
200 # notice the file's mtime before we've finished
189 # notice the file's mtime before we've finished
201 # writing it.
190 # writing it.
202 shutil.copystat(tmpname, repo.wjoin(realname))
191 shutil.copystat(tmpname, repo.wjoin(realname))
203 os.unlink(tmpname)
192 os.unlink(tmpname)
204 if tobackup:
193 if tobackup:
205 os.rmdir(backupdir)
194 os.rmdir(backupdir)
206 except OSError:
195 except OSError:
207 pass
196 pass
208
197
209 return commit(ui, repo, recordfunc, pats, opts)
198 return commit(ui, repo, recordfunc, pats, opts)
210
199
211 def findpossible(cmd, table, strict=False):
200 def findpossible(cmd, table, strict=False):
212 """
201 """
213 Return cmd -> (aliases, command table entry)
202 Return cmd -> (aliases, command table entry)
214 for each matching command.
203 for each matching command.
215 Return debug commands (or their aliases) only if no normal command matches.
204 Return debug commands (or their aliases) only if no normal command matches.
216 """
205 """
217 choice = {}
206 choice = {}
218 debugchoice = {}
207 debugchoice = {}
219
208
220 if cmd in table:
209 if cmd in table:
221 # short-circuit exact matches, "log" alias beats "^log|history"
210 # short-circuit exact matches, "log" alias beats "^log|history"
222 keys = [cmd]
211 keys = [cmd]
223 else:
212 else:
224 keys = table.keys()
213 keys = table.keys()
225
214
226 allcmds = []
215 allcmds = []
227 for e in keys:
216 for e in keys:
228 aliases = parsealiases(e)
217 aliases = parsealiases(e)
229 allcmds.extend(aliases)
218 allcmds.extend(aliases)
230 found = None
219 found = None
231 if cmd in aliases:
220 if cmd in aliases:
232 found = cmd
221 found = cmd
233 elif not strict:
222 elif not strict:
234 for a in aliases:
223 for a in aliases:
235 if a.startswith(cmd):
224 if a.startswith(cmd):
236 found = a
225 found = a
237 break
226 break
238 if found is not None:
227 if found is not None:
239 if aliases[0].startswith("debug") or found.startswith("debug"):
228 if aliases[0].startswith("debug") or found.startswith("debug"):
240 debugchoice[found] = (aliases, table[e])
229 debugchoice[found] = (aliases, table[e])
241 else:
230 else:
242 choice[found] = (aliases, table[e])
231 choice[found] = (aliases, table[e])
243
232
244 if not choice and debugchoice:
233 if not choice and debugchoice:
245 choice = debugchoice
234 choice = debugchoice
246
235
247 return choice, allcmds
236 return choice, allcmds
248
237
249 def findcmd(cmd, table, strict=True):
238 def findcmd(cmd, table, strict=True):
250 """Return (aliases, command table entry) for command string."""
239 """Return (aliases, command table entry) for command string."""
251 choice, allcmds = findpossible(cmd, table, strict)
240 choice, allcmds = findpossible(cmd, table, strict)
252
241
253 if cmd in choice:
242 if cmd in choice:
254 return choice[cmd]
243 return choice[cmd]
255
244
256 if len(choice) > 1:
245 if len(choice) > 1:
257 clist = choice.keys()
246 clist = choice.keys()
258 clist.sort()
247 clist.sort()
259 raise error.AmbiguousCommand(cmd, clist)
248 raise error.AmbiguousCommand(cmd, clist)
260
249
261 if choice:
250 if choice:
262 return choice.values()[0]
251 return choice.values()[0]
263
252
264 raise error.UnknownCommand(cmd, allcmds)
253 raise error.UnknownCommand(cmd, allcmds)
265
254
266 def findrepo(p):
255 def findrepo(p):
267 while not os.path.isdir(os.path.join(p, ".hg")):
256 while not os.path.isdir(os.path.join(p, ".hg")):
268 oldp, p = p, os.path.dirname(p)
257 oldp, p = p, os.path.dirname(p)
269 if p == oldp:
258 if p == oldp:
270 return None
259 return None
271
260
272 return p
261 return p
273
262
274 def bailifchanged(repo, merge=True):
263 def bailifchanged(repo, merge=True):
275 if merge and repo.dirstate.p2() != nullid:
264 if merge and repo.dirstate.p2() != nullid:
276 raise util.Abort(_('outstanding uncommitted merge'))
265 raise util.Abort(_('outstanding uncommitted merge'))
277 modified, added, removed, deleted = repo.status()[:4]
266 modified, added, removed, deleted = repo.status()[:4]
278 if modified or added or removed or deleted:
267 if modified or added or removed or deleted:
279 raise util.Abort(_('uncommitted changes'))
268 raise util.Abort(_('uncommitted changes'))
280 ctx = repo[None]
269 ctx = repo[None]
281 for s in sorted(ctx.substate):
270 for s in sorted(ctx.substate):
282 ctx.sub(s).bailifchanged()
271 ctx.sub(s).bailifchanged()
283
272
284 def logmessage(ui, opts):
273 def logmessage(ui, opts):
285 """ get the log message according to -m and -l option """
274 """ get the log message according to -m and -l option """
286 message = opts.get('message')
275 message = opts.get('message')
287 logfile = opts.get('logfile')
276 logfile = opts.get('logfile')
288
277
289 if message and logfile:
278 if message and logfile:
290 raise util.Abort(_('options --message and --logfile are mutually '
279 raise util.Abort(_('options --message and --logfile are mutually '
291 'exclusive'))
280 'exclusive'))
292 if not message and logfile:
281 if not message and logfile:
293 try:
282 try:
294 if logfile == '-':
283 if logfile == '-':
295 message = ui.fin.read()
284 message = ui.fin.read()
296 else:
285 else:
297 message = '\n'.join(util.readfile(logfile).splitlines())
286 message = '\n'.join(util.readfile(logfile).splitlines())
298 except IOError, inst:
287 except IOError, inst:
299 raise util.Abort(_("can't read commit message '%s': %s") %
288 raise util.Abort(_("can't read commit message '%s': %s") %
300 (logfile, inst.strerror))
289 (logfile, inst.strerror))
301 return message
290 return message
302
291
303 def mergeeditform(ctxorbool, baseformname):
292 def mergeeditform(ctxorbool, baseformname):
304 """return appropriate editform name (referencing a committemplate)
293 """return appropriate editform name (referencing a committemplate)
305
294
306 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
295 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
307 merging is committed.
296 merging is committed.
308
297
309 This returns baseformname with '.merge' appended if it is a merge,
298 This returns baseformname with '.merge' appended if it is a merge,
310 otherwise '.normal' is appended.
299 otherwise '.normal' is appended.
311 """
300 """
312 if isinstance(ctxorbool, bool):
301 if isinstance(ctxorbool, bool):
313 if ctxorbool:
302 if ctxorbool:
314 return baseformname + ".merge"
303 return baseformname + ".merge"
315 elif 1 < len(ctxorbool.parents()):
304 elif 1 < len(ctxorbool.parents()):
316 return baseformname + ".merge"
305 return baseformname + ".merge"
317
306
318 return baseformname + ".normal"
307 return baseformname + ".normal"
319
308
320 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
309 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
321 editform='', **opts):
310 editform='', **opts):
322 """get appropriate commit message editor according to '--edit' option
311 """get appropriate commit message editor according to '--edit' option
323
312
324 'finishdesc' is a function to be called with edited commit message
313 'finishdesc' is a function to be called with edited commit message
325 (= 'description' of the new changeset) just after editing, but
314 (= 'description' of the new changeset) just after editing, but
326 before checking empty-ness. It should return actual text to be
315 before checking empty-ness. It should return actual text to be
327 stored into history. This allows to change description before
316 stored into history. This allows to change description before
328 storing.
317 storing.
329
318
330 'extramsg' is a extra message to be shown in the editor instead of
319 'extramsg' is a extra message to be shown in the editor instead of
331 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
320 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
332 is automatically added.
321 is automatically added.
333
322
334 'editform' is a dot-separated list of names, to distinguish
323 'editform' is a dot-separated list of names, to distinguish
335 the purpose of commit text editing.
324 the purpose of commit text editing.
336
325
337 'getcommiteditor' returns 'commitforceeditor' regardless of
326 'getcommiteditor' returns 'commitforceeditor' regardless of
338 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
327 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
339 they are specific for usage in MQ.
328 they are specific for usage in MQ.
340 """
329 """
341 if edit or finishdesc or extramsg:
330 if edit or finishdesc or extramsg:
342 return lambda r, c, s: commitforceeditor(r, c, s,
331 return lambda r, c, s: commitforceeditor(r, c, s,
343 finishdesc=finishdesc,
332 finishdesc=finishdesc,
344 extramsg=extramsg,
333 extramsg=extramsg,
345 editform=editform)
334 editform=editform)
346 elif editform:
335 elif editform:
347 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
336 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
348 else:
337 else:
349 return commiteditor
338 return commiteditor
350
339
351 def loglimit(opts):
340 def loglimit(opts):
352 """get the log limit according to option -l/--limit"""
341 """get the log limit according to option -l/--limit"""
353 limit = opts.get('limit')
342 limit = opts.get('limit')
354 if limit:
343 if limit:
355 try:
344 try:
356 limit = int(limit)
345 limit = int(limit)
357 except ValueError:
346 except ValueError:
358 raise util.Abort(_('limit must be a positive integer'))
347 raise util.Abort(_('limit must be a positive integer'))
359 if limit <= 0:
348 if limit <= 0:
360 raise util.Abort(_('limit must be positive'))
349 raise util.Abort(_('limit must be positive'))
361 else:
350 else:
362 limit = None
351 limit = None
363 return limit
352 return limit
364
353
365 def makefilename(repo, pat, node, desc=None,
354 def makefilename(repo, pat, node, desc=None,
366 total=None, seqno=None, revwidth=None, pathname=None):
355 total=None, seqno=None, revwidth=None, pathname=None):
367 node_expander = {
356 node_expander = {
368 'H': lambda: hex(node),
357 'H': lambda: hex(node),
369 'R': lambda: str(repo.changelog.rev(node)),
358 'R': lambda: str(repo.changelog.rev(node)),
370 'h': lambda: short(node),
359 'h': lambda: short(node),
371 'm': lambda: re.sub('[^\w]', '_', str(desc))
360 'm': lambda: re.sub('[^\w]', '_', str(desc))
372 }
361 }
373 expander = {
362 expander = {
374 '%': lambda: '%',
363 '%': lambda: '%',
375 'b': lambda: os.path.basename(repo.root),
364 'b': lambda: os.path.basename(repo.root),
376 }
365 }
377
366
378 try:
367 try:
379 if node:
368 if node:
380 expander.update(node_expander)
369 expander.update(node_expander)
381 if node:
370 if node:
382 expander['r'] = (lambda:
371 expander['r'] = (lambda:
383 str(repo.changelog.rev(node)).zfill(revwidth or 0))
372 str(repo.changelog.rev(node)).zfill(revwidth or 0))
384 if total is not None:
373 if total is not None:
385 expander['N'] = lambda: str(total)
374 expander['N'] = lambda: str(total)
386 if seqno is not None:
375 if seqno is not None:
387 expander['n'] = lambda: str(seqno)
376 expander['n'] = lambda: str(seqno)
388 if total is not None and seqno is not None:
377 if total is not None and seqno is not None:
389 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
378 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
390 if pathname is not None:
379 if pathname is not None:
391 expander['s'] = lambda: os.path.basename(pathname)
380 expander['s'] = lambda: os.path.basename(pathname)
392 expander['d'] = lambda: os.path.dirname(pathname) or '.'
381 expander['d'] = lambda: os.path.dirname(pathname) or '.'
393 expander['p'] = lambda: pathname
382 expander['p'] = lambda: pathname
394
383
395 newname = []
384 newname = []
396 patlen = len(pat)
385 patlen = len(pat)
397 i = 0
386 i = 0
398 while i < patlen:
387 while i < patlen:
399 c = pat[i]
388 c = pat[i]
400 if c == '%':
389 if c == '%':
401 i += 1
390 i += 1
402 c = pat[i]
391 c = pat[i]
403 c = expander[c]()
392 c = expander[c]()
404 newname.append(c)
393 newname.append(c)
405 i += 1
394 i += 1
406 return ''.join(newname)
395 return ''.join(newname)
407 except KeyError, inst:
396 except KeyError, inst:
408 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
397 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
409 inst.args[0])
398 inst.args[0])
410
399
411 def makefileobj(repo, pat, node=None, desc=None, total=None,
400 def makefileobj(repo, pat, node=None, desc=None, total=None,
412 seqno=None, revwidth=None, mode='wb', modemap=None,
401 seqno=None, revwidth=None, mode='wb', modemap=None,
413 pathname=None):
402 pathname=None):
414
403
415 writable = mode not in ('r', 'rb')
404 writable = mode not in ('r', 'rb')
416
405
417 if not pat or pat == '-':
406 if not pat or pat == '-':
418 if writable:
407 if writable:
419 fp = repo.ui.fout
408 fp = repo.ui.fout
420 else:
409 else:
421 fp = repo.ui.fin
410 fp = repo.ui.fin
422 if util.safehasattr(fp, 'fileno'):
411 if util.safehasattr(fp, 'fileno'):
423 return os.fdopen(os.dup(fp.fileno()), mode)
412 return os.fdopen(os.dup(fp.fileno()), mode)
424 else:
413 else:
425 # if this fp can't be duped properly, return
414 # if this fp can't be duped properly, return
426 # a dummy object that can be closed
415 # a dummy object that can be closed
427 class wrappedfileobj(object):
416 class wrappedfileobj(object):
428 noop = lambda x: None
417 noop = lambda x: None
429 def __init__(self, f):
418 def __init__(self, f):
430 self.f = f
419 self.f = f
431 def __getattr__(self, attr):
420 def __getattr__(self, attr):
432 if attr == 'close':
421 if attr == 'close':
433 return self.noop
422 return self.noop
434 else:
423 else:
435 return getattr(self.f, attr)
424 return getattr(self.f, attr)
436
425
437 return wrappedfileobj(fp)
426 return wrappedfileobj(fp)
438 if util.safehasattr(pat, 'write') and writable:
427 if util.safehasattr(pat, 'write') and writable:
439 return pat
428 return pat
440 if util.safehasattr(pat, 'read') and 'r' in mode:
429 if util.safehasattr(pat, 'read') and 'r' in mode:
441 return pat
430 return pat
442 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
431 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
443 if modemap is not None:
432 if modemap is not None:
444 mode = modemap.get(fn, mode)
433 mode = modemap.get(fn, mode)
445 if mode == 'wb':
434 if mode == 'wb':
446 modemap[fn] = 'ab'
435 modemap[fn] = 'ab'
447 return open(fn, mode)
436 return open(fn, mode)
448
437
449 def openrevlog(repo, cmd, file_, opts):
438 def openrevlog(repo, cmd, file_, opts):
450 """opens the changelog, manifest, a filelog or a given revlog"""
439 """opens the changelog, manifest, a filelog or a given revlog"""
451 cl = opts['changelog']
440 cl = opts['changelog']
452 mf = opts['manifest']
441 mf = opts['manifest']
453 msg = None
442 msg = None
454 if cl and mf:
443 if cl and mf:
455 msg = _('cannot specify --changelog and --manifest at the same time')
444 msg = _('cannot specify --changelog and --manifest at the same time')
456 elif cl or mf:
445 elif cl or mf:
457 if file_:
446 if file_:
458 msg = _('cannot specify filename with --changelog or --manifest')
447 msg = _('cannot specify filename with --changelog or --manifest')
459 elif not repo:
448 elif not repo:
460 msg = _('cannot specify --changelog or --manifest '
449 msg = _('cannot specify --changelog or --manifest '
461 'without a repository')
450 'without a repository')
462 if msg:
451 if msg:
463 raise util.Abort(msg)
452 raise util.Abort(msg)
464
453
465 r = None
454 r = None
466 if repo:
455 if repo:
467 if cl:
456 if cl:
468 r = repo.unfiltered().changelog
457 r = repo.unfiltered().changelog
469 elif mf:
458 elif mf:
470 r = repo.manifest
459 r = repo.manifest
471 elif file_:
460 elif file_:
472 filelog = repo.file(file_)
461 filelog = repo.file(file_)
473 if len(filelog):
462 if len(filelog):
474 r = filelog
463 r = filelog
475 if not r:
464 if not r:
476 if not file_:
465 if not file_:
477 raise error.CommandError(cmd, _('invalid arguments'))
466 raise error.CommandError(cmd, _('invalid arguments'))
478 if not os.path.isfile(file_):
467 if not os.path.isfile(file_):
479 raise util.Abort(_("revlog '%s' not found") % file_)
468 raise util.Abort(_("revlog '%s' not found") % file_)
480 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
469 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
481 file_[:-2] + ".i")
470 file_[:-2] + ".i")
482 return r
471 return r
483
472
484 def copy(ui, repo, pats, opts, rename=False):
473 def copy(ui, repo, pats, opts, rename=False):
485 # called with the repo lock held
474 # called with the repo lock held
486 #
475 #
487 # hgsep => pathname that uses "/" to separate directories
476 # hgsep => pathname that uses "/" to separate directories
488 # ossep => pathname that uses os.sep to separate directories
477 # ossep => pathname that uses os.sep to separate directories
489 cwd = repo.getcwd()
478 cwd = repo.getcwd()
490 targets = {}
479 targets = {}
491 after = opts.get("after")
480 after = opts.get("after")
492 dryrun = opts.get("dry_run")
481 dryrun = opts.get("dry_run")
493 wctx = repo[None]
482 wctx = repo[None]
494
483
495 def walkpat(pat):
484 def walkpat(pat):
496 srcs = []
485 srcs = []
497 if after:
486 if after:
498 badstates = '?'
487 badstates = '?'
499 else:
488 else:
500 badstates = '?r'
489 badstates = '?r'
501 m = scmutil.match(repo[None], [pat], opts, globbed=True)
490 m = scmutil.match(repo[None], [pat], opts, globbed=True)
502 for abs in repo.walk(m):
491 for abs in repo.walk(m):
503 state = repo.dirstate[abs]
492 state = repo.dirstate[abs]
504 rel = m.rel(abs)
493 rel = m.rel(abs)
505 exact = m.exact(abs)
494 exact = m.exact(abs)
506 if state in badstates:
495 if state in badstates:
507 if exact and state == '?':
496 if exact and state == '?':
508 ui.warn(_('%s: not copying - file is not managed\n') % rel)
497 ui.warn(_('%s: not copying - file is not managed\n') % rel)
509 if exact and state == 'r':
498 if exact and state == 'r':
510 ui.warn(_('%s: not copying - file has been marked for'
499 ui.warn(_('%s: not copying - file has been marked for'
511 ' remove\n') % rel)
500 ' remove\n') % rel)
512 continue
501 continue
513 # abs: hgsep
502 # abs: hgsep
514 # rel: ossep
503 # rel: ossep
515 srcs.append((abs, rel, exact))
504 srcs.append((abs, rel, exact))
516 return srcs
505 return srcs
517
506
518 # abssrc: hgsep
507 # abssrc: hgsep
519 # relsrc: ossep
508 # relsrc: ossep
520 # otarget: ossep
509 # otarget: ossep
521 def copyfile(abssrc, relsrc, otarget, exact):
510 def copyfile(abssrc, relsrc, otarget, exact):
522 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
511 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
523 if '/' in abstarget:
512 if '/' in abstarget:
524 # We cannot normalize abstarget itself, this would prevent
513 # We cannot normalize abstarget itself, this would prevent
525 # case only renames, like a => A.
514 # case only renames, like a => A.
526 abspath, absname = abstarget.rsplit('/', 1)
515 abspath, absname = abstarget.rsplit('/', 1)
527 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
516 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
528 reltarget = repo.pathto(abstarget, cwd)
517 reltarget = repo.pathto(abstarget, cwd)
529 target = repo.wjoin(abstarget)
518 target = repo.wjoin(abstarget)
530 src = repo.wjoin(abssrc)
519 src = repo.wjoin(abssrc)
531 state = repo.dirstate[abstarget]
520 state = repo.dirstate[abstarget]
532
521
533 scmutil.checkportable(ui, abstarget)
522 scmutil.checkportable(ui, abstarget)
534
523
535 # check for collisions
524 # check for collisions
536 prevsrc = targets.get(abstarget)
525 prevsrc = targets.get(abstarget)
537 if prevsrc is not None:
526 if prevsrc is not None:
538 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
527 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
539 (reltarget, repo.pathto(abssrc, cwd),
528 (reltarget, repo.pathto(abssrc, cwd),
540 repo.pathto(prevsrc, cwd)))
529 repo.pathto(prevsrc, cwd)))
541 return
530 return
542
531
543 # check for overwrites
532 # check for overwrites
544 exists = os.path.lexists(target)
533 exists = os.path.lexists(target)
545 samefile = False
534 samefile = False
546 if exists and abssrc != abstarget:
535 if exists and abssrc != abstarget:
547 if (repo.dirstate.normalize(abssrc) ==
536 if (repo.dirstate.normalize(abssrc) ==
548 repo.dirstate.normalize(abstarget)):
537 repo.dirstate.normalize(abstarget)):
549 if not rename:
538 if not rename:
550 ui.warn(_("%s: can't copy - same file\n") % reltarget)
539 ui.warn(_("%s: can't copy - same file\n") % reltarget)
551 return
540 return
552 exists = False
541 exists = False
553 samefile = True
542 samefile = True
554
543
555 if not after and exists or after and state in 'mn':
544 if not after and exists or after and state in 'mn':
556 if not opts['force']:
545 if not opts['force']:
557 ui.warn(_('%s: not overwriting - file exists\n') %
546 ui.warn(_('%s: not overwriting - file exists\n') %
558 reltarget)
547 reltarget)
559 return
548 return
560
549
561 if after:
550 if after:
562 if not exists:
551 if not exists:
563 if rename:
552 if rename:
564 ui.warn(_('%s: not recording move - %s does not exist\n') %
553 ui.warn(_('%s: not recording move - %s does not exist\n') %
565 (relsrc, reltarget))
554 (relsrc, reltarget))
566 else:
555 else:
567 ui.warn(_('%s: not recording copy - %s does not exist\n') %
556 ui.warn(_('%s: not recording copy - %s does not exist\n') %
568 (relsrc, reltarget))
557 (relsrc, reltarget))
569 return
558 return
570 elif not dryrun:
559 elif not dryrun:
571 try:
560 try:
572 if exists:
561 if exists:
573 os.unlink(target)
562 os.unlink(target)
574 targetdir = os.path.dirname(target) or '.'
563 targetdir = os.path.dirname(target) or '.'
575 if not os.path.isdir(targetdir):
564 if not os.path.isdir(targetdir):
576 os.makedirs(targetdir)
565 os.makedirs(targetdir)
577 if samefile:
566 if samefile:
578 tmp = target + "~hgrename"
567 tmp = target + "~hgrename"
579 os.rename(src, tmp)
568 os.rename(src, tmp)
580 os.rename(tmp, target)
569 os.rename(tmp, target)
581 else:
570 else:
582 util.copyfile(src, target)
571 util.copyfile(src, target)
583 srcexists = True
572 srcexists = True
584 except IOError, inst:
573 except IOError, inst:
585 if inst.errno == errno.ENOENT:
574 if inst.errno == errno.ENOENT:
586 ui.warn(_('%s: deleted in working directory\n') % relsrc)
575 ui.warn(_('%s: deleted in working directory\n') % relsrc)
587 srcexists = False
576 srcexists = False
588 else:
577 else:
589 ui.warn(_('%s: cannot copy - %s\n') %
578 ui.warn(_('%s: cannot copy - %s\n') %
590 (relsrc, inst.strerror))
579 (relsrc, inst.strerror))
591 return True # report a failure
580 return True # report a failure
592
581
593 if ui.verbose or not exact:
582 if ui.verbose or not exact:
594 if rename:
583 if rename:
595 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
584 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
596 else:
585 else:
597 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
586 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
598
587
599 targets[abstarget] = abssrc
588 targets[abstarget] = abssrc
600
589
601 # fix up dirstate
590 # fix up dirstate
602 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
591 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
603 dryrun=dryrun, cwd=cwd)
592 dryrun=dryrun, cwd=cwd)
604 if rename and not dryrun:
593 if rename and not dryrun:
605 if not after and srcexists and not samefile:
594 if not after and srcexists and not samefile:
606 util.unlinkpath(repo.wjoin(abssrc))
595 util.unlinkpath(repo.wjoin(abssrc))
607 wctx.forget([abssrc])
596 wctx.forget([abssrc])
608
597
609 # pat: ossep
598 # pat: ossep
610 # dest ossep
599 # dest ossep
611 # srcs: list of (hgsep, hgsep, ossep, bool)
600 # srcs: list of (hgsep, hgsep, ossep, bool)
612 # return: function that takes hgsep and returns ossep
601 # return: function that takes hgsep and returns ossep
613 def targetpathfn(pat, dest, srcs):
602 def targetpathfn(pat, dest, srcs):
614 if os.path.isdir(pat):
603 if os.path.isdir(pat):
615 abspfx = pathutil.canonpath(repo.root, cwd, pat)
604 abspfx = pathutil.canonpath(repo.root, cwd, pat)
616 abspfx = util.localpath(abspfx)
605 abspfx = util.localpath(abspfx)
617 if destdirexists:
606 if destdirexists:
618 striplen = len(os.path.split(abspfx)[0])
607 striplen = len(os.path.split(abspfx)[0])
619 else:
608 else:
620 striplen = len(abspfx)
609 striplen = len(abspfx)
621 if striplen:
610 if striplen:
622 striplen += len(os.sep)
611 striplen += len(os.sep)
623 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
612 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
624 elif destdirexists:
613 elif destdirexists:
625 res = lambda p: os.path.join(dest,
614 res = lambda p: os.path.join(dest,
626 os.path.basename(util.localpath(p)))
615 os.path.basename(util.localpath(p)))
627 else:
616 else:
628 res = lambda p: dest
617 res = lambda p: dest
629 return res
618 return res
630
619
631 # pat: ossep
620 # pat: ossep
632 # dest ossep
621 # dest ossep
633 # srcs: list of (hgsep, hgsep, ossep, bool)
622 # srcs: list of (hgsep, hgsep, ossep, bool)
634 # return: function that takes hgsep and returns ossep
623 # return: function that takes hgsep and returns ossep
635 def targetpathafterfn(pat, dest, srcs):
624 def targetpathafterfn(pat, dest, srcs):
636 if matchmod.patkind(pat):
625 if matchmod.patkind(pat):
637 # a mercurial pattern
626 # a mercurial pattern
638 res = lambda p: os.path.join(dest,
627 res = lambda p: os.path.join(dest,
639 os.path.basename(util.localpath(p)))
628 os.path.basename(util.localpath(p)))
640 else:
629 else:
641 abspfx = pathutil.canonpath(repo.root, cwd, pat)
630 abspfx = pathutil.canonpath(repo.root, cwd, pat)
642 if len(abspfx) < len(srcs[0][0]):
631 if len(abspfx) < len(srcs[0][0]):
643 # A directory. Either the target path contains the last
632 # A directory. Either the target path contains the last
644 # component of the source path or it does not.
633 # component of the source path or it does not.
645 def evalpath(striplen):
634 def evalpath(striplen):
646 score = 0
635 score = 0
647 for s in srcs:
636 for s in srcs:
648 t = os.path.join(dest, util.localpath(s[0])[striplen:])
637 t = os.path.join(dest, util.localpath(s[0])[striplen:])
649 if os.path.lexists(t):
638 if os.path.lexists(t):
650 score += 1
639 score += 1
651 return score
640 return score
652
641
653 abspfx = util.localpath(abspfx)
642 abspfx = util.localpath(abspfx)
654 striplen = len(abspfx)
643 striplen = len(abspfx)
655 if striplen:
644 if striplen:
656 striplen += len(os.sep)
645 striplen += len(os.sep)
657 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
646 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
658 score = evalpath(striplen)
647 score = evalpath(striplen)
659 striplen1 = len(os.path.split(abspfx)[0])
648 striplen1 = len(os.path.split(abspfx)[0])
660 if striplen1:
649 if striplen1:
661 striplen1 += len(os.sep)
650 striplen1 += len(os.sep)
662 if evalpath(striplen1) > score:
651 if evalpath(striplen1) > score:
663 striplen = striplen1
652 striplen = striplen1
664 res = lambda p: os.path.join(dest,
653 res = lambda p: os.path.join(dest,
665 util.localpath(p)[striplen:])
654 util.localpath(p)[striplen:])
666 else:
655 else:
667 # a file
656 # a file
668 if destdirexists:
657 if destdirexists:
669 res = lambda p: os.path.join(dest,
658 res = lambda p: os.path.join(dest,
670 os.path.basename(util.localpath(p)))
659 os.path.basename(util.localpath(p)))
671 else:
660 else:
672 res = lambda p: dest
661 res = lambda p: dest
673 return res
662 return res
674
663
675 pats = scmutil.expandpats(pats)
664 pats = scmutil.expandpats(pats)
676 if not pats:
665 if not pats:
677 raise util.Abort(_('no source or destination specified'))
666 raise util.Abort(_('no source or destination specified'))
678 if len(pats) == 1:
667 if len(pats) == 1:
679 raise util.Abort(_('no destination specified'))
668 raise util.Abort(_('no destination specified'))
680 dest = pats.pop()
669 dest = pats.pop()
681 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
670 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
682 if not destdirexists:
671 if not destdirexists:
683 if len(pats) > 1 or matchmod.patkind(pats[0]):
672 if len(pats) > 1 or matchmod.patkind(pats[0]):
684 raise util.Abort(_('with multiple sources, destination must be an '
673 raise util.Abort(_('with multiple sources, destination must be an '
685 'existing directory'))
674 'existing directory'))
686 if util.endswithsep(dest):
675 if util.endswithsep(dest):
687 raise util.Abort(_('destination %s is not a directory') % dest)
676 raise util.Abort(_('destination %s is not a directory') % dest)
688
677
689 tfn = targetpathfn
678 tfn = targetpathfn
690 if after:
679 if after:
691 tfn = targetpathafterfn
680 tfn = targetpathafterfn
692 copylist = []
681 copylist = []
693 for pat in pats:
682 for pat in pats:
694 srcs = walkpat(pat)
683 srcs = walkpat(pat)
695 if not srcs:
684 if not srcs:
696 continue
685 continue
697 copylist.append((tfn(pat, dest, srcs), srcs))
686 copylist.append((tfn(pat, dest, srcs), srcs))
698 if not copylist:
687 if not copylist:
699 raise util.Abort(_('no files to copy'))
688 raise util.Abort(_('no files to copy'))
700
689
701 errors = 0
690 errors = 0
702 for targetpath, srcs in copylist:
691 for targetpath, srcs in copylist:
703 for abssrc, relsrc, exact in srcs:
692 for abssrc, relsrc, exact in srcs:
704 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
693 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
705 errors += 1
694 errors += 1
706
695
707 if errors:
696 if errors:
708 ui.warn(_('(consider using --after)\n'))
697 ui.warn(_('(consider using --after)\n'))
709
698
710 return errors != 0
699 return errors != 0
711
700
712 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
701 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
713 runargs=None, appendpid=False):
702 runargs=None, appendpid=False):
714 '''Run a command as a service.'''
703 '''Run a command as a service.'''
715
704
716 def writepid(pid):
705 def writepid(pid):
717 if opts['pid_file']:
706 if opts['pid_file']:
718 if appendpid:
707 if appendpid:
719 mode = 'a'
708 mode = 'a'
720 else:
709 else:
721 mode = 'w'
710 mode = 'w'
722 fp = open(opts['pid_file'], mode)
711 fp = open(opts['pid_file'], mode)
723 fp.write(str(pid) + '\n')
712 fp.write(str(pid) + '\n')
724 fp.close()
713 fp.close()
725
714
726 if opts['daemon'] and not opts['daemon_pipefds']:
715 if opts['daemon'] and not opts['daemon_pipefds']:
727 # Signal child process startup with file removal
716 # Signal child process startup with file removal
728 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
717 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
729 os.close(lockfd)
718 os.close(lockfd)
730 try:
719 try:
731 if not runargs:
720 if not runargs:
732 runargs = util.hgcmd() + sys.argv[1:]
721 runargs = util.hgcmd() + sys.argv[1:]
733 runargs.append('--daemon-pipefds=%s' % lockpath)
722 runargs.append('--daemon-pipefds=%s' % lockpath)
734 # Don't pass --cwd to the child process, because we've already
723 # Don't pass --cwd to the child process, because we've already
735 # changed directory.
724 # changed directory.
736 for i in xrange(1, len(runargs)):
725 for i in xrange(1, len(runargs)):
737 if runargs[i].startswith('--cwd='):
726 if runargs[i].startswith('--cwd='):
738 del runargs[i]
727 del runargs[i]
739 break
728 break
740 elif runargs[i].startswith('--cwd'):
729 elif runargs[i].startswith('--cwd'):
741 del runargs[i:i + 2]
730 del runargs[i:i + 2]
742 break
731 break
743 def condfn():
732 def condfn():
744 return not os.path.exists(lockpath)
733 return not os.path.exists(lockpath)
745 pid = util.rundetached(runargs, condfn)
734 pid = util.rundetached(runargs, condfn)
746 if pid < 0:
735 if pid < 0:
747 raise util.Abort(_('child process failed to start'))
736 raise util.Abort(_('child process failed to start'))
748 writepid(pid)
737 writepid(pid)
749 finally:
738 finally:
750 try:
739 try:
751 os.unlink(lockpath)
740 os.unlink(lockpath)
752 except OSError, e:
741 except OSError, e:
753 if e.errno != errno.ENOENT:
742 if e.errno != errno.ENOENT:
754 raise
743 raise
755 if parentfn:
744 if parentfn:
756 return parentfn(pid)
745 return parentfn(pid)
757 else:
746 else:
758 return
747 return
759
748
760 if initfn:
749 if initfn:
761 initfn()
750 initfn()
762
751
763 if not opts['daemon']:
752 if not opts['daemon']:
764 writepid(os.getpid())
753 writepid(os.getpid())
765
754
766 if opts['daemon_pipefds']:
755 if opts['daemon_pipefds']:
767 lockpath = opts['daemon_pipefds']
756 lockpath = opts['daemon_pipefds']
768 try:
757 try:
769 os.setsid()
758 os.setsid()
770 except AttributeError:
759 except AttributeError:
771 pass
760 pass
772 os.unlink(lockpath)
761 os.unlink(lockpath)
773 util.hidewindow()
762 util.hidewindow()
774 sys.stdout.flush()
763 sys.stdout.flush()
775 sys.stderr.flush()
764 sys.stderr.flush()
776
765
777 nullfd = os.open(os.devnull, os.O_RDWR)
766 nullfd = os.open(os.devnull, os.O_RDWR)
778 logfilefd = nullfd
767 logfilefd = nullfd
779 if logfile:
768 if logfile:
780 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
769 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
781 os.dup2(nullfd, 0)
770 os.dup2(nullfd, 0)
782 os.dup2(logfilefd, 1)
771 os.dup2(logfilefd, 1)
783 os.dup2(logfilefd, 2)
772 os.dup2(logfilefd, 2)
784 if nullfd not in (0, 1, 2):
773 if nullfd not in (0, 1, 2):
785 os.close(nullfd)
774 os.close(nullfd)
786 if logfile and logfilefd not in (0, 1, 2):
775 if logfile and logfilefd not in (0, 1, 2):
787 os.close(logfilefd)
776 os.close(logfilefd)
788
777
789 if runfn:
778 if runfn:
790 return runfn()
779 return runfn()
791
780
792 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
781 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
793 """Utility function used by commands.import to import a single patch
782 """Utility function used by commands.import to import a single patch
794
783
795 This function is explicitly defined here to help the evolve extension to
784 This function is explicitly defined here to help the evolve extension to
796 wrap this part of the import logic.
785 wrap this part of the import logic.
797
786
798 The API is currently a bit ugly because it a simple code translation from
787 The API is currently a bit ugly because it a simple code translation from
799 the import command. Feel free to make it better.
788 the import command. Feel free to make it better.
800
789
801 :hunk: a patch (as a binary string)
790 :hunk: a patch (as a binary string)
802 :parents: nodes that will be parent of the created commit
791 :parents: nodes that will be parent of the created commit
803 :opts: the full dict of option passed to the import command
792 :opts: the full dict of option passed to the import command
804 :msgs: list to save commit message to.
793 :msgs: list to save commit message to.
805 (used in case we need to save it when failing)
794 (used in case we need to save it when failing)
806 :updatefunc: a function that update a repo to a given node
795 :updatefunc: a function that update a repo to a given node
807 updatefunc(<repo>, <node>)
796 updatefunc(<repo>, <node>)
808 """
797 """
809 tmpname, message, user, date, branch, nodeid, p1, p2 = \
798 tmpname, message, user, date, branch, nodeid, p1, p2 = \
810 patch.extract(ui, hunk)
799 patch.extract(ui, hunk)
811
800
812 update = not opts.get('bypass')
801 update = not opts.get('bypass')
813 strip = opts["strip"]
802 strip = opts["strip"]
814 prefix = opts["prefix"]
803 prefix = opts["prefix"]
815 sim = float(opts.get('similarity') or 0)
804 sim = float(opts.get('similarity') or 0)
816 if not tmpname:
805 if not tmpname:
817 return (None, None, False)
806 return (None, None, False)
818 msg = _('applied to working directory')
807 msg = _('applied to working directory')
819
808
820 rejects = False
809 rejects = False
821
810
822 try:
811 try:
823 cmdline_message = logmessage(ui, opts)
812 cmdline_message = logmessage(ui, opts)
824 if cmdline_message:
813 if cmdline_message:
825 # pickup the cmdline msg
814 # pickup the cmdline msg
826 message = cmdline_message
815 message = cmdline_message
827 elif message:
816 elif message:
828 # pickup the patch msg
817 # pickup the patch msg
829 message = message.strip()
818 message = message.strip()
830 else:
819 else:
831 # launch the editor
820 # launch the editor
832 message = None
821 message = None
833 ui.debug('message:\n%s\n' % message)
822 ui.debug('message:\n%s\n' % message)
834
823
835 if len(parents) == 1:
824 if len(parents) == 1:
836 parents.append(repo[nullid])
825 parents.append(repo[nullid])
837 if opts.get('exact'):
826 if opts.get('exact'):
838 if not nodeid or not p1:
827 if not nodeid or not p1:
839 raise util.Abort(_('not a Mercurial patch'))
828 raise util.Abort(_('not a Mercurial patch'))
840 p1 = repo[p1]
829 p1 = repo[p1]
841 p2 = repo[p2 or nullid]
830 p2 = repo[p2 or nullid]
842 elif p2:
831 elif p2:
843 try:
832 try:
844 p1 = repo[p1]
833 p1 = repo[p1]
845 p2 = repo[p2]
834 p2 = repo[p2]
846 # Without any options, consider p2 only if the
835 # Without any options, consider p2 only if the
847 # patch is being applied on top of the recorded
836 # patch is being applied on top of the recorded
848 # first parent.
837 # first parent.
849 if p1 != parents[0]:
838 if p1 != parents[0]:
850 p1 = parents[0]
839 p1 = parents[0]
851 p2 = repo[nullid]
840 p2 = repo[nullid]
852 except error.RepoError:
841 except error.RepoError:
853 p1, p2 = parents
842 p1, p2 = parents
854 if p2.node() == nullid:
843 if p2.node() == nullid:
855 ui.warn(_("warning: import the patch as a normal revision\n"
844 ui.warn(_("warning: import the patch as a normal revision\n"
856 "(use --exact to import the patch as a merge)\n"))
845 "(use --exact to import the patch as a merge)\n"))
857 else:
846 else:
858 p1, p2 = parents
847 p1, p2 = parents
859
848
860 n = None
849 n = None
861 if update:
850 if update:
862 repo.dirstate.beginparentchange()
851 repo.dirstate.beginparentchange()
863 if p1 != parents[0]:
852 if p1 != parents[0]:
864 updatefunc(repo, p1.node())
853 updatefunc(repo, p1.node())
865 if p2 != parents[1]:
854 if p2 != parents[1]:
866 repo.setparents(p1.node(), p2.node())
855 repo.setparents(p1.node(), p2.node())
867
856
868 if opts.get('exact') or opts.get('import_branch'):
857 if opts.get('exact') or opts.get('import_branch'):
869 repo.dirstate.setbranch(branch or 'default')
858 repo.dirstate.setbranch(branch or 'default')
870
859
871 partial = opts.get('partial', False)
860 partial = opts.get('partial', False)
872 files = set()
861 files = set()
873 try:
862 try:
874 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
863 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
875 files=files, eolmode=None, similarity=sim / 100.0)
864 files=files, eolmode=None, similarity=sim / 100.0)
876 except patch.PatchError, e:
865 except patch.PatchError, e:
877 if not partial:
866 if not partial:
878 raise util.Abort(str(e))
867 raise util.Abort(str(e))
879 if partial:
868 if partial:
880 rejects = True
869 rejects = True
881
870
882 files = list(files)
871 files = list(files)
883 if opts.get('no_commit'):
872 if opts.get('no_commit'):
884 if message:
873 if message:
885 msgs.append(message)
874 msgs.append(message)
886 else:
875 else:
887 if opts.get('exact') or p2:
876 if opts.get('exact') or p2:
888 # If you got here, you either use --force and know what
877 # If you got here, you either use --force and know what
889 # you are doing or used --exact or a merge patch while
878 # you are doing or used --exact or a merge patch while
890 # being updated to its first parent.
879 # being updated to its first parent.
891 m = None
880 m = None
892 else:
881 else:
893 m = scmutil.matchfiles(repo, files or [])
882 m = scmutil.matchfiles(repo, files or [])
894 editform = mergeeditform(repo[None], 'import.normal')
883 editform = mergeeditform(repo[None], 'import.normal')
895 if opts.get('exact'):
884 if opts.get('exact'):
896 editor = None
885 editor = None
897 else:
886 else:
898 editor = getcommiteditor(editform=editform, **opts)
887 editor = getcommiteditor(editform=editform, **opts)
899 n = repo.commit(message, opts.get('user') or user,
888 n = repo.commit(message, opts.get('user') or user,
900 opts.get('date') or date, match=m,
889 opts.get('date') or date, match=m,
901 editor=editor, force=partial)
890 editor=editor, force=partial)
902 repo.dirstate.endparentchange()
891 repo.dirstate.endparentchange()
903 else:
892 else:
904 if opts.get('exact') or opts.get('import_branch'):
893 if opts.get('exact') or opts.get('import_branch'):
905 branch = branch or 'default'
894 branch = branch or 'default'
906 else:
895 else:
907 branch = p1.branch()
896 branch = p1.branch()
908 store = patch.filestore()
897 store = patch.filestore()
909 try:
898 try:
910 files = set()
899 files = set()
911 try:
900 try:
912 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
901 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
913 files, eolmode=None)
902 files, eolmode=None)
914 except patch.PatchError, e:
903 except patch.PatchError, e:
915 raise util.Abort(str(e))
904 raise util.Abort(str(e))
916 if opts.get('exact'):
905 if opts.get('exact'):
917 editor = None
906 editor = None
918 else:
907 else:
919 editor = getcommiteditor(editform='import.bypass')
908 editor = getcommiteditor(editform='import.bypass')
920 memctx = context.makememctx(repo, (p1.node(), p2.node()),
909 memctx = context.makememctx(repo, (p1.node(), p2.node()),
921 message,
910 message,
922 opts.get('user') or user,
911 opts.get('user') or user,
923 opts.get('date') or date,
912 opts.get('date') or date,
924 branch, files, store,
913 branch, files, store,
925 editor=editor)
914 editor=editor)
926 n = memctx.commit()
915 n = memctx.commit()
927 finally:
916 finally:
928 store.close()
917 store.close()
929 if opts.get('exact') and opts.get('no_commit'):
918 if opts.get('exact') and opts.get('no_commit'):
930 # --exact with --no-commit is still useful in that it does merge
919 # --exact with --no-commit is still useful in that it does merge
931 # and branch bits
920 # and branch bits
932 ui.warn(_("warning: can't check exact import with --no-commit\n"))
921 ui.warn(_("warning: can't check exact import with --no-commit\n"))
933 elif opts.get('exact') and hex(n) != nodeid:
922 elif opts.get('exact') and hex(n) != nodeid:
934 raise util.Abort(_('patch is damaged or loses information'))
923 raise util.Abort(_('patch is damaged or loses information'))
935 if n:
924 if n:
936 # i18n: refers to a short changeset id
925 # i18n: refers to a short changeset id
937 msg = _('created %s') % short(n)
926 msg = _('created %s') % short(n)
938 return (msg, n, rejects)
927 return (msg, n, rejects)
939 finally:
928 finally:
940 os.unlink(tmpname)
929 os.unlink(tmpname)
941
930
942 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
931 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
943 opts=None):
932 opts=None):
944 '''export changesets as hg patches.'''
933 '''export changesets as hg patches.'''
945
934
946 total = len(revs)
935 total = len(revs)
947 revwidth = max([len(str(rev)) for rev in revs])
936 revwidth = max([len(str(rev)) for rev in revs])
948 filemode = {}
937 filemode = {}
949
938
950 def single(rev, seqno, fp):
939 def single(rev, seqno, fp):
951 ctx = repo[rev]
940 ctx = repo[rev]
952 node = ctx.node()
941 node = ctx.node()
953 parents = [p.node() for p in ctx.parents() if p]
942 parents = [p.node() for p in ctx.parents() if p]
954 branch = ctx.branch()
943 branch = ctx.branch()
955 if switch_parent:
944 if switch_parent:
956 parents.reverse()
945 parents.reverse()
957
946
958 if parents:
947 if parents:
959 prev = parents[0]
948 prev = parents[0]
960 else:
949 else:
961 prev = nullid
950 prev = nullid
962
951
963 shouldclose = False
952 shouldclose = False
964 if not fp and len(template) > 0:
953 if not fp and len(template) > 0:
965 desc_lines = ctx.description().rstrip().split('\n')
954 desc_lines = ctx.description().rstrip().split('\n')
966 desc = desc_lines[0] #Commit always has a first line.
955 desc = desc_lines[0] #Commit always has a first line.
967 fp = makefileobj(repo, template, node, desc=desc, total=total,
956 fp = makefileobj(repo, template, node, desc=desc, total=total,
968 seqno=seqno, revwidth=revwidth, mode='wb',
957 seqno=seqno, revwidth=revwidth, mode='wb',
969 modemap=filemode)
958 modemap=filemode)
970 if fp != template:
959 if fp != template:
971 shouldclose = True
960 shouldclose = True
972 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
961 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
973 repo.ui.note("%s\n" % fp.name)
962 repo.ui.note("%s\n" % fp.name)
974
963
975 if not fp:
964 if not fp:
976 write = repo.ui.write
965 write = repo.ui.write
977 else:
966 else:
978 def write(s, **kw):
967 def write(s, **kw):
979 fp.write(s)
968 fp.write(s)
980
969
981 write("# HG changeset patch\n")
970 write("# HG changeset patch\n")
982 write("# User %s\n" % ctx.user())
971 write("# User %s\n" % ctx.user())
983 write("# Date %d %d\n" % ctx.date())
972 write("# Date %d %d\n" % ctx.date())
984 write("# %s\n" % util.datestr(ctx.date()))
973 write("# %s\n" % util.datestr(ctx.date()))
985 if branch and branch != 'default':
974 if branch and branch != 'default':
986 write("# Branch %s\n" % branch)
975 write("# Branch %s\n" % branch)
987 write("# Node ID %s\n" % hex(node))
976 write("# Node ID %s\n" % hex(node))
988 write("# Parent %s\n" % hex(prev))
977 write("# Parent %s\n" % hex(prev))
989 if len(parents) > 1:
978 if len(parents) > 1:
990 write("# Parent %s\n" % hex(parents[1]))
979 write("# Parent %s\n" % hex(parents[1]))
991 write(ctx.description().rstrip())
980 write(ctx.description().rstrip())
992 write("\n\n")
981 write("\n\n")
993
982
994 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
983 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
995 write(chunk, label=label)
984 write(chunk, label=label)
996
985
997 if shouldclose:
986 if shouldclose:
998 fp.close()
987 fp.close()
999
988
1000 for seqno, rev in enumerate(revs):
989 for seqno, rev in enumerate(revs):
1001 single(rev, seqno + 1, fp)
990 single(rev, seqno + 1, fp)
1002
991
1003 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
992 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1004 changes=None, stat=False, fp=None, prefix='',
993 changes=None, stat=False, fp=None, prefix='',
1005 root='', listsubrepos=False):
994 root='', listsubrepos=False):
1006 '''show diff or diffstat.'''
995 '''show diff or diffstat.'''
1007 if fp is None:
996 if fp is None:
1008 write = ui.write
997 write = ui.write
1009 else:
998 else:
1010 def write(s, **kw):
999 def write(s, **kw):
1011 fp.write(s)
1000 fp.write(s)
1012
1001
1013 if root:
1002 if root:
1014 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1003 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1015 else:
1004 else:
1016 relroot = ''
1005 relroot = ''
1017 if relroot != '':
1006 if relroot != '':
1018 # XXX relative roots currently don't work if the root is within a
1007 # XXX relative roots currently don't work if the root is within a
1019 # subrepo
1008 # subrepo
1020 uirelroot = match.uipath(relroot)
1009 uirelroot = match.uipath(relroot)
1021 relroot += '/'
1010 relroot += '/'
1022 for matchroot in match.files():
1011 for matchroot in match.files():
1023 if not matchroot.startswith(relroot):
1012 if not matchroot.startswith(relroot):
1024 ui.warn(_('warning: %s not inside relative root %s\n') % (
1013 ui.warn(_('warning: %s not inside relative root %s\n') % (
1025 match.uipath(matchroot), uirelroot))
1014 match.uipath(matchroot), uirelroot))
1026
1015
1027 if stat:
1016 if stat:
1028 diffopts = diffopts.copy(context=0)
1017 diffopts = diffopts.copy(context=0)
1029 width = 80
1018 width = 80
1030 if not ui.plain():
1019 if not ui.plain():
1031 width = ui.termwidth()
1020 width = ui.termwidth()
1032 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1021 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1033 prefix=prefix, relroot=relroot)
1022 prefix=prefix, relroot=relroot)
1034 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1023 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1035 width=width,
1024 width=width,
1036 git=diffopts.git):
1025 git=diffopts.git):
1037 write(chunk, label=label)
1026 write(chunk, label=label)
1038 else:
1027 else:
1039 for chunk, label in patch.diffui(repo, node1, node2, match,
1028 for chunk, label in patch.diffui(repo, node1, node2, match,
1040 changes, diffopts, prefix=prefix,
1029 changes, diffopts, prefix=prefix,
1041 relroot=relroot):
1030 relroot=relroot):
1042 write(chunk, label=label)
1031 write(chunk, label=label)
1043
1032
1044 if listsubrepos:
1033 if listsubrepos:
1045 ctx1 = repo[node1]
1034 ctx1 = repo[node1]
1046 ctx2 = repo[node2]
1035 ctx2 = repo[node2]
1047 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1036 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1048 tempnode2 = node2
1037 tempnode2 = node2
1049 try:
1038 try:
1050 if node2 is not None:
1039 if node2 is not None:
1051 tempnode2 = ctx2.substate[subpath][1]
1040 tempnode2 = ctx2.substate[subpath][1]
1052 except KeyError:
1041 except KeyError:
1053 # A subrepo that existed in node1 was deleted between node1 and
1042 # A subrepo that existed in node1 was deleted between node1 and
1054 # node2 (inclusive). Thus, ctx2's substate won't contain that
1043 # node2 (inclusive). Thus, ctx2's substate won't contain that
1055 # subpath. The best we can do is to ignore it.
1044 # subpath. The best we can do is to ignore it.
1056 tempnode2 = None
1045 tempnode2 = None
1057 submatch = matchmod.narrowmatcher(subpath, match)
1046 submatch = matchmod.narrowmatcher(subpath, match)
1058 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1047 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1059 stat=stat, fp=fp, prefix=prefix)
1048 stat=stat, fp=fp, prefix=prefix)
1060
1049
1061 class changeset_printer(object):
1050 class changeset_printer(object):
1062 '''show changeset information when templating not requested.'''
1051 '''show changeset information when templating not requested.'''
1063
1052
1064 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1053 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1065 self.ui = ui
1054 self.ui = ui
1066 self.repo = repo
1055 self.repo = repo
1067 self.buffered = buffered
1056 self.buffered = buffered
1068 self.matchfn = matchfn
1057 self.matchfn = matchfn
1069 self.diffopts = diffopts
1058 self.diffopts = diffopts
1070 self.header = {}
1059 self.header = {}
1071 self.hunk = {}
1060 self.hunk = {}
1072 self.lastheader = None
1061 self.lastheader = None
1073 self.footer = None
1062 self.footer = None
1074
1063
1075 def flush(self, rev):
1064 def flush(self, rev):
1076 if rev in self.header:
1065 if rev in self.header:
1077 h = self.header[rev]
1066 h = self.header[rev]
1078 if h != self.lastheader:
1067 if h != self.lastheader:
1079 self.lastheader = h
1068 self.lastheader = h
1080 self.ui.write(h)
1069 self.ui.write(h)
1081 del self.header[rev]
1070 del self.header[rev]
1082 if rev in self.hunk:
1071 if rev in self.hunk:
1083 self.ui.write(self.hunk[rev])
1072 self.ui.write(self.hunk[rev])
1084 del self.hunk[rev]
1073 del self.hunk[rev]
1085 return 1
1074 return 1
1086 return 0
1075 return 0
1087
1076
1088 def close(self):
1077 def close(self):
1089 if self.footer:
1078 if self.footer:
1090 self.ui.write(self.footer)
1079 self.ui.write(self.footer)
1091
1080
1092 def show(self, ctx, copies=None, matchfn=None, **props):
1081 def show(self, ctx, copies=None, matchfn=None, **props):
1093 if self.buffered:
1082 if self.buffered:
1094 self.ui.pushbuffer()
1083 self.ui.pushbuffer()
1095 self._show(ctx, copies, matchfn, props)
1084 self._show(ctx, copies, matchfn, props)
1096 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1085 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1097 else:
1086 else:
1098 self._show(ctx, copies, matchfn, props)
1087 self._show(ctx, copies, matchfn, props)
1099
1088
1100 def _show(self, ctx, copies, matchfn, props):
1089 def _show(self, ctx, copies, matchfn, props):
1101 '''show a single changeset or file revision'''
1090 '''show a single changeset or file revision'''
1102 changenode = ctx.node()
1091 changenode = ctx.node()
1103 rev = ctx.rev()
1092 rev = ctx.rev()
1104 if self.ui.debugflag:
1093 if self.ui.debugflag:
1105 hexfunc = hex
1094 hexfunc = hex
1106 else:
1095 else:
1107 hexfunc = short
1096 hexfunc = short
1108 if rev is None:
1097 if rev is None:
1109 pctx = ctx.p1()
1098 pctx = ctx.p1()
1110 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1099 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1111 else:
1100 else:
1112 revnode = (rev, hexfunc(changenode))
1101 revnode = (rev, hexfunc(changenode))
1113
1102
1114 if self.ui.quiet:
1103 if self.ui.quiet:
1115 self.ui.write("%d:%s\n" % revnode, label='log.node')
1104 self.ui.write("%d:%s\n" % revnode, label='log.node')
1116 return
1105 return
1117
1106
1118 date = util.datestr(ctx.date())
1107 date = util.datestr(ctx.date())
1119
1108
1120 # i18n: column positioning for "hg log"
1109 # i18n: column positioning for "hg log"
1121 self.ui.write(_("changeset: %d:%s\n") % revnode,
1110 self.ui.write(_("changeset: %d:%s\n") % revnode,
1122 label='log.changeset changeset.%s' % ctx.phasestr())
1111 label='log.changeset changeset.%s' % ctx.phasestr())
1123
1112
1124 # branches are shown first before any other names due to backwards
1113 # branches are shown first before any other names due to backwards
1125 # compatibility
1114 # compatibility
1126 branch = ctx.branch()
1115 branch = ctx.branch()
1127 # don't show the default branch name
1116 # don't show the default branch name
1128 if branch != 'default':
1117 if branch != 'default':
1129 # i18n: column positioning for "hg log"
1118 # i18n: column positioning for "hg log"
1130 self.ui.write(_("branch: %s\n") % branch,
1119 self.ui.write(_("branch: %s\n") % branch,
1131 label='log.branch')
1120 label='log.branch')
1132
1121
1133 for name, ns in self.repo.names.iteritems():
1122 for name, ns in self.repo.names.iteritems():
1134 # branches has special logic already handled above, so here we just
1123 # branches has special logic already handled above, so here we just
1135 # skip it
1124 # skip it
1136 if name == 'branches':
1125 if name == 'branches':
1137 continue
1126 continue
1138 # we will use the templatename as the color name since those two
1127 # we will use the templatename as the color name since those two
1139 # should be the same
1128 # should be the same
1140 for name in ns.names(self.repo, changenode):
1129 for name in ns.names(self.repo, changenode):
1141 self.ui.write(ns.logfmt % name,
1130 self.ui.write(ns.logfmt % name,
1142 label='log.%s' % ns.colorname)
1131 label='log.%s' % ns.colorname)
1143 if self.ui.debugflag:
1132 if self.ui.debugflag:
1144 # i18n: column positioning for "hg log"
1133 # i18n: column positioning for "hg log"
1145 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
1134 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
1146 label='log.phase')
1135 label='log.phase')
1147 for pctx in self._meaningful_parentrevs(ctx):
1136 for pctx in self._meaningful_parentrevs(ctx):
1148 label = 'log.parent changeset.%s' % pctx.phasestr()
1137 label = 'log.parent changeset.%s' % pctx.phasestr()
1149 # i18n: column positioning for "hg log"
1138 # i18n: column positioning for "hg log"
1150 self.ui.write(_("parent: %d:%s\n")
1139 self.ui.write(_("parent: %d:%s\n")
1151 % (pctx.rev(), hexfunc(pctx.node())),
1140 % (pctx.rev(), hexfunc(pctx.node())),
1152 label=label)
1141 label=label)
1153
1142
1154 if self.ui.debugflag and rev is not None:
1143 if self.ui.debugflag and rev is not None:
1155 mnode = ctx.manifestnode()
1144 mnode = ctx.manifestnode()
1156 # i18n: column positioning for "hg log"
1145 # i18n: column positioning for "hg log"
1157 self.ui.write(_("manifest: %d:%s\n") %
1146 self.ui.write(_("manifest: %d:%s\n") %
1158 (self.repo.manifest.rev(mnode), hex(mnode)),
1147 (self.repo.manifest.rev(mnode), hex(mnode)),
1159 label='ui.debug log.manifest')
1148 label='ui.debug log.manifest')
1160 # i18n: column positioning for "hg log"
1149 # i18n: column positioning for "hg log"
1161 self.ui.write(_("user: %s\n") % ctx.user(),
1150 self.ui.write(_("user: %s\n") % ctx.user(),
1162 label='log.user')
1151 label='log.user')
1163 # i18n: column positioning for "hg log"
1152 # i18n: column positioning for "hg log"
1164 self.ui.write(_("date: %s\n") % date,
1153 self.ui.write(_("date: %s\n") % date,
1165 label='log.date')
1154 label='log.date')
1166
1155
1167 if self.ui.debugflag:
1156 if self.ui.debugflag:
1168 files = ctx.p1().status(ctx)[:3]
1157 files = ctx.p1().status(ctx)[:3]
1169 for key, value in zip([# i18n: column positioning for "hg log"
1158 for key, value in zip([# i18n: column positioning for "hg log"
1170 _("files:"),
1159 _("files:"),
1171 # i18n: column positioning for "hg log"
1160 # i18n: column positioning for "hg log"
1172 _("files+:"),
1161 _("files+:"),
1173 # i18n: column positioning for "hg log"
1162 # i18n: column positioning for "hg log"
1174 _("files-:")], files):
1163 _("files-:")], files):
1175 if value:
1164 if value:
1176 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1165 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1177 label='ui.debug log.files')
1166 label='ui.debug log.files')
1178 elif ctx.files() and self.ui.verbose:
1167 elif ctx.files() and self.ui.verbose:
1179 # i18n: column positioning for "hg log"
1168 # i18n: column positioning for "hg log"
1180 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1169 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1181 label='ui.note log.files')
1170 label='ui.note log.files')
1182 if copies and self.ui.verbose:
1171 if copies and self.ui.verbose:
1183 copies = ['%s (%s)' % c for c in copies]
1172 copies = ['%s (%s)' % c for c in copies]
1184 # i18n: column positioning for "hg log"
1173 # i18n: column positioning for "hg log"
1185 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1174 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1186 label='ui.note log.copies')
1175 label='ui.note log.copies')
1187
1176
1188 extra = ctx.extra()
1177 extra = ctx.extra()
1189 if extra and self.ui.debugflag:
1178 if extra and self.ui.debugflag:
1190 for key, value in sorted(extra.items()):
1179 for key, value in sorted(extra.items()):
1191 # i18n: column positioning for "hg log"
1180 # i18n: column positioning for "hg log"
1192 self.ui.write(_("extra: %s=%s\n")
1181 self.ui.write(_("extra: %s=%s\n")
1193 % (key, value.encode('string_escape')),
1182 % (key, value.encode('string_escape')),
1194 label='ui.debug log.extra')
1183 label='ui.debug log.extra')
1195
1184
1196 description = ctx.description().strip()
1185 description = ctx.description().strip()
1197 if description:
1186 if description:
1198 if self.ui.verbose:
1187 if self.ui.verbose:
1199 self.ui.write(_("description:\n"),
1188 self.ui.write(_("description:\n"),
1200 label='ui.note log.description')
1189 label='ui.note log.description')
1201 self.ui.write(description,
1190 self.ui.write(description,
1202 label='ui.note log.description')
1191 label='ui.note log.description')
1203 self.ui.write("\n\n")
1192 self.ui.write("\n\n")
1204 else:
1193 else:
1205 # i18n: column positioning for "hg log"
1194 # i18n: column positioning for "hg log"
1206 self.ui.write(_("summary: %s\n") %
1195 self.ui.write(_("summary: %s\n") %
1207 description.splitlines()[0],
1196 description.splitlines()[0],
1208 label='log.summary')
1197 label='log.summary')
1209 self.ui.write("\n")
1198 self.ui.write("\n")
1210
1199
1211 self.showpatch(changenode, matchfn)
1200 self.showpatch(changenode, matchfn)
1212
1201
1213 def showpatch(self, node, matchfn):
1202 def showpatch(self, node, matchfn):
1214 if not matchfn:
1203 if not matchfn:
1215 matchfn = self.matchfn
1204 matchfn = self.matchfn
1216 if matchfn:
1205 if matchfn:
1217 stat = self.diffopts.get('stat')
1206 stat = self.diffopts.get('stat')
1218 diff = self.diffopts.get('patch')
1207 diff = self.diffopts.get('patch')
1219 diffopts = patch.diffallopts(self.ui, self.diffopts)
1208 diffopts = patch.diffallopts(self.ui, self.diffopts)
1220 prev = self.repo.changelog.parents(node)[0]
1209 prev = self.repo.changelog.parents(node)[0]
1221 if stat:
1210 if stat:
1222 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1211 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1223 match=matchfn, stat=True)
1212 match=matchfn, stat=True)
1224 if diff:
1213 if diff:
1225 if stat:
1214 if stat:
1226 self.ui.write("\n")
1215 self.ui.write("\n")
1227 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1216 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1228 match=matchfn, stat=False)
1217 match=matchfn, stat=False)
1229 self.ui.write("\n")
1218 self.ui.write("\n")
1230
1219
1231 def _meaningful_parentrevs(self, ctx):
1220 def _meaningful_parentrevs(self, ctx):
1232 """Return list of meaningful (or all if debug) parentrevs for rev.
1221 """Return list of meaningful (or all if debug) parentrevs for rev.
1233
1222
1234 For merges (two non-nullrev revisions) both parents are meaningful.
1223 For merges (two non-nullrev revisions) both parents are meaningful.
1235 Otherwise the first parent revision is considered meaningful if it
1224 Otherwise the first parent revision is considered meaningful if it
1236 is not the preceding revision.
1225 is not the preceding revision.
1237 """
1226 """
1238 parents = ctx.parents()
1227 parents = ctx.parents()
1239 if len(parents) > 1:
1228 if len(parents) > 1:
1240 return parents
1229 return parents
1241 if self.ui.debugflag:
1230 if self.ui.debugflag:
1242 return [parents[0], self.repo['null']]
1231 return [parents[0], self.repo['null']]
1243 if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1:
1232 if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1:
1244 return []
1233 return []
1245 return parents
1234 return parents
1246
1235
1247 class jsonchangeset(changeset_printer):
1236 class jsonchangeset(changeset_printer):
1248 '''format changeset information.'''
1237 '''format changeset information.'''
1249
1238
1250 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1239 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1251 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1240 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1252 self.cache = {}
1241 self.cache = {}
1253 self._first = True
1242 self._first = True
1254
1243
1255 def close(self):
1244 def close(self):
1256 if not self._first:
1245 if not self._first:
1257 self.ui.write("\n]\n")
1246 self.ui.write("\n]\n")
1258 else:
1247 else:
1259 self.ui.write("[]\n")
1248 self.ui.write("[]\n")
1260
1249
1261 def _show(self, ctx, copies, matchfn, props):
1250 def _show(self, ctx, copies, matchfn, props):
1262 '''show a single changeset or file revision'''
1251 '''show a single changeset or file revision'''
1263 rev = ctx.rev()
1252 rev = ctx.rev()
1264 if rev is None:
1253 if rev is None:
1265 jrev = jnode = 'null'
1254 jrev = jnode = 'null'
1266 else:
1255 else:
1267 jrev = str(rev)
1256 jrev = str(rev)
1268 jnode = '"%s"' % hex(ctx.node())
1257 jnode = '"%s"' % hex(ctx.node())
1269 j = encoding.jsonescape
1258 j = encoding.jsonescape
1270
1259
1271 if self._first:
1260 if self._first:
1272 self.ui.write("[\n {")
1261 self.ui.write("[\n {")
1273 self._first = False
1262 self._first = False
1274 else:
1263 else:
1275 self.ui.write(",\n {")
1264 self.ui.write(",\n {")
1276
1265
1277 if self.ui.quiet:
1266 if self.ui.quiet:
1278 self.ui.write('\n "rev": %s' % jrev)
1267 self.ui.write('\n "rev": %s' % jrev)
1279 self.ui.write(',\n "node": %s' % jnode)
1268 self.ui.write(',\n "node": %s' % jnode)
1280 self.ui.write('\n }')
1269 self.ui.write('\n }')
1281 return
1270 return
1282
1271
1283 self.ui.write('\n "rev": %s' % jrev)
1272 self.ui.write('\n "rev": %s' % jrev)
1284 self.ui.write(',\n "node": %s' % jnode)
1273 self.ui.write(',\n "node": %s' % jnode)
1285 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1274 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1286 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1275 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1287 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1276 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1288 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1277 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1289 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1278 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1290
1279
1291 self.ui.write(',\n "bookmarks": [%s]' %
1280 self.ui.write(',\n "bookmarks": [%s]' %
1292 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1281 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1293 self.ui.write(',\n "tags": [%s]' %
1282 self.ui.write(',\n "tags": [%s]' %
1294 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1283 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1295 self.ui.write(',\n "parents": [%s]' %
1284 self.ui.write(',\n "parents": [%s]' %
1296 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1285 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1297
1286
1298 if self.ui.debugflag:
1287 if self.ui.debugflag:
1299 if rev is None:
1288 if rev is None:
1300 jmanifestnode = 'null'
1289 jmanifestnode = 'null'
1301 else:
1290 else:
1302 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1291 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1303 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1292 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1304
1293
1305 self.ui.write(',\n "extra": {%s}' %
1294 self.ui.write(',\n "extra": {%s}' %
1306 ", ".join('"%s": "%s"' % (j(k), j(v))
1295 ", ".join('"%s": "%s"' % (j(k), j(v))
1307 for k, v in ctx.extra().items()))
1296 for k, v in ctx.extra().items()))
1308
1297
1309 files = ctx.p1().status(ctx)
1298 files = ctx.p1().status(ctx)
1310 self.ui.write(',\n "modified": [%s]' %
1299 self.ui.write(',\n "modified": [%s]' %
1311 ", ".join('"%s"' % j(f) for f in files[0]))
1300 ", ".join('"%s"' % j(f) for f in files[0]))
1312 self.ui.write(',\n "added": [%s]' %
1301 self.ui.write(',\n "added": [%s]' %
1313 ", ".join('"%s"' % j(f) for f in files[1]))
1302 ", ".join('"%s"' % j(f) for f in files[1]))
1314 self.ui.write(',\n "removed": [%s]' %
1303 self.ui.write(',\n "removed": [%s]' %
1315 ", ".join('"%s"' % j(f) for f in files[2]))
1304 ", ".join('"%s"' % j(f) for f in files[2]))
1316
1305
1317 elif self.ui.verbose:
1306 elif self.ui.verbose:
1318 self.ui.write(',\n "files": [%s]' %
1307 self.ui.write(',\n "files": [%s]' %
1319 ", ".join('"%s"' % j(f) for f in ctx.files()))
1308 ", ".join('"%s"' % j(f) for f in ctx.files()))
1320
1309
1321 if copies:
1310 if copies:
1322 self.ui.write(',\n "copies": {%s}' %
1311 self.ui.write(',\n "copies": {%s}' %
1323 ", ".join('"%s": "%s"' % (j(k), j(v))
1312 ", ".join('"%s": "%s"' % (j(k), j(v))
1324 for k, v in copies))
1313 for k, v in copies))
1325
1314
1326 matchfn = self.matchfn
1315 matchfn = self.matchfn
1327 if matchfn:
1316 if matchfn:
1328 stat = self.diffopts.get('stat')
1317 stat = self.diffopts.get('stat')
1329 diff = self.diffopts.get('patch')
1318 diff = self.diffopts.get('patch')
1330 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1319 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1331 node, prev = ctx.node(), ctx.p1().node()
1320 node, prev = ctx.node(), ctx.p1().node()
1332 if stat:
1321 if stat:
1333 self.ui.pushbuffer()
1322 self.ui.pushbuffer()
1334 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1323 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1335 match=matchfn, stat=True)
1324 match=matchfn, stat=True)
1336 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1325 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1337 if diff:
1326 if diff:
1338 self.ui.pushbuffer()
1327 self.ui.pushbuffer()
1339 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1328 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1340 match=matchfn, stat=False)
1329 match=matchfn, stat=False)
1341 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1330 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1342
1331
1343 self.ui.write("\n }")
1332 self.ui.write("\n }")
1344
1333
1345 class changeset_templater(changeset_printer):
1334 class changeset_templater(changeset_printer):
1346 '''format changeset information.'''
1335 '''format changeset information.'''
1347
1336
1348 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1337 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1349 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1338 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1350 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1339 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1351 defaulttempl = {
1340 defaulttempl = {
1352 'parent': '{rev}:{node|formatnode} ',
1341 'parent': '{rev}:{node|formatnode} ',
1353 'manifest': '{rev}:{node|formatnode}',
1342 'manifest': '{rev}:{node|formatnode}',
1354 'file_copy': '{name} ({source})',
1343 'file_copy': '{name} ({source})',
1355 'extra': '{key}={value|stringescape}'
1344 'extra': '{key}={value|stringescape}'
1356 }
1345 }
1357 # filecopy is preserved for compatibility reasons
1346 # filecopy is preserved for compatibility reasons
1358 defaulttempl['filecopy'] = defaulttempl['file_copy']
1347 defaulttempl['filecopy'] = defaulttempl['file_copy']
1359 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1348 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1360 cache=defaulttempl)
1349 cache=defaulttempl)
1361 if tmpl:
1350 if tmpl:
1362 self.t.cache['changeset'] = tmpl
1351 self.t.cache['changeset'] = tmpl
1363
1352
1364 self.cache = {}
1353 self.cache = {}
1365
1354
1366 def _show(self, ctx, copies, matchfn, props):
1355 def _show(self, ctx, copies, matchfn, props):
1367 '''show a single changeset or file revision'''
1356 '''show a single changeset or file revision'''
1368
1357
1369 showlist = templatekw.showlist
1358 showlist = templatekw.showlist
1370
1359
1371 # showparents() behaviour depends on ui trace level which
1360 # showparents() behaviour depends on ui trace level which
1372 # causes unexpected behaviours at templating level and makes
1361 # causes unexpected behaviours at templating level and makes
1373 # it harder to extract it in a standalone function. Its
1362 # it harder to extract it in a standalone function. Its
1374 # behaviour cannot be changed so leave it here for now.
1363 # behaviour cannot be changed so leave it here for now.
1375 def showparents(**args):
1364 def showparents(**args):
1376 ctx = args['ctx']
1365 ctx = args['ctx']
1377 parents = [[('rev', p.rev()),
1366 parents = [[('rev', p.rev()),
1378 ('node', p.hex()),
1367 ('node', p.hex()),
1379 ('phase', p.phasestr())]
1368 ('phase', p.phasestr())]
1380 for p in self._meaningful_parentrevs(ctx)]
1369 for p in self._meaningful_parentrevs(ctx)]
1381 return showlist('parent', parents, **args)
1370 return showlist('parent', parents, **args)
1382
1371
1383 props = props.copy()
1372 props = props.copy()
1384 props.update(templatekw.keywords)
1373 props.update(templatekw.keywords)
1385 props['parents'] = showparents
1374 props['parents'] = showparents
1386 props['templ'] = self.t
1375 props['templ'] = self.t
1387 props['ctx'] = ctx
1376 props['ctx'] = ctx
1388 props['repo'] = self.repo
1377 props['repo'] = self.repo
1389 props['revcache'] = {'copies': copies}
1378 props['revcache'] = {'copies': copies}
1390 props['cache'] = self.cache
1379 props['cache'] = self.cache
1391
1380
1392 # find correct templates for current mode
1381 # find correct templates for current mode
1393
1382
1394 tmplmodes = [
1383 tmplmodes = [
1395 (True, None),
1384 (True, None),
1396 (self.ui.verbose, 'verbose'),
1385 (self.ui.verbose, 'verbose'),
1397 (self.ui.quiet, 'quiet'),
1386 (self.ui.quiet, 'quiet'),
1398 (self.ui.debugflag, 'debug'),
1387 (self.ui.debugflag, 'debug'),
1399 ]
1388 ]
1400
1389
1401 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1390 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1402 for mode, postfix in tmplmodes:
1391 for mode, postfix in tmplmodes:
1403 for type in types:
1392 for type in types:
1404 cur = postfix and ('%s_%s' % (type, postfix)) or type
1393 cur = postfix and ('%s_%s' % (type, postfix)) or type
1405 if mode and cur in self.t:
1394 if mode and cur in self.t:
1406 types[type] = cur
1395 types[type] = cur
1407
1396
1408 try:
1397 try:
1409
1398
1410 # write header
1399 # write header
1411 if types['header']:
1400 if types['header']:
1412 h = templater.stringify(self.t(types['header'], **props))
1401 h = templater.stringify(self.t(types['header'], **props))
1413 if self.buffered:
1402 if self.buffered:
1414 self.header[ctx.rev()] = h
1403 self.header[ctx.rev()] = h
1415 else:
1404 else:
1416 if self.lastheader != h:
1405 if self.lastheader != h:
1417 self.lastheader = h
1406 self.lastheader = h
1418 self.ui.write(h)
1407 self.ui.write(h)
1419
1408
1420 # write changeset metadata, then patch if requested
1409 # write changeset metadata, then patch if requested
1421 key = types['changeset']
1410 key = types['changeset']
1422 self.ui.write(templater.stringify(self.t(key, **props)))
1411 self.ui.write(templater.stringify(self.t(key, **props)))
1423 self.showpatch(ctx.node(), matchfn)
1412 self.showpatch(ctx.node(), matchfn)
1424
1413
1425 if types['footer']:
1414 if types['footer']:
1426 if not self.footer:
1415 if not self.footer:
1427 self.footer = templater.stringify(self.t(types['footer'],
1416 self.footer = templater.stringify(self.t(types['footer'],
1428 **props))
1417 **props))
1429
1418
1430 except KeyError, inst:
1419 except KeyError, inst:
1431 msg = _("%s: no key named '%s'")
1420 msg = _("%s: no key named '%s'")
1432 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1421 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1433 except SyntaxError, inst:
1422 except SyntaxError, inst:
1434 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1423 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1435
1424
1436 def gettemplate(ui, tmpl, style):
1425 def gettemplate(ui, tmpl, style):
1437 """
1426 """
1438 Find the template matching the given template spec or style.
1427 Find the template matching the given template spec or style.
1439 """
1428 """
1440
1429
1441 # ui settings
1430 # ui settings
1442 if not tmpl and not style: # template are stronger than style
1431 if not tmpl and not style: # template are stronger than style
1443 tmpl = ui.config('ui', 'logtemplate')
1432 tmpl = ui.config('ui', 'logtemplate')
1444 if tmpl:
1433 if tmpl:
1445 try:
1434 try:
1446 tmpl = templater.parsestring(tmpl)
1435 tmpl = templater.parsestring(tmpl)
1447 except SyntaxError:
1436 except SyntaxError:
1448 tmpl = templater.parsestring(tmpl, quoted=False)
1437 tmpl = templater.parsestring(tmpl, quoted=False)
1449 return tmpl, None
1438 return tmpl, None
1450 else:
1439 else:
1451 style = util.expandpath(ui.config('ui', 'style', ''))
1440 style = util.expandpath(ui.config('ui', 'style', ''))
1452
1441
1453 if not tmpl and style:
1442 if not tmpl and style:
1454 mapfile = style
1443 mapfile = style
1455 if not os.path.split(mapfile)[0]:
1444 if not os.path.split(mapfile)[0]:
1456 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1445 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1457 or templater.templatepath(mapfile))
1446 or templater.templatepath(mapfile))
1458 if mapname:
1447 if mapname:
1459 mapfile = mapname
1448 mapfile = mapname
1460 return None, mapfile
1449 return None, mapfile
1461
1450
1462 if not tmpl:
1451 if not tmpl:
1463 return None, None
1452 return None, None
1464
1453
1465 # looks like a literal template?
1454 # looks like a literal template?
1466 if '{' in tmpl:
1455 if '{' in tmpl:
1467 return tmpl, None
1456 return tmpl, None
1468
1457
1469 # perhaps a stock style?
1458 # perhaps a stock style?
1470 if not os.path.split(tmpl)[0]:
1459 if not os.path.split(tmpl)[0]:
1471 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1460 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1472 or templater.templatepath(tmpl))
1461 or templater.templatepath(tmpl))
1473 if mapname and os.path.isfile(mapname):
1462 if mapname and os.path.isfile(mapname):
1474 return None, mapname
1463 return None, mapname
1475
1464
1476 # perhaps it's a reference to [templates]
1465 # perhaps it's a reference to [templates]
1477 t = ui.config('templates', tmpl)
1466 t = ui.config('templates', tmpl)
1478 if t:
1467 if t:
1479 try:
1468 try:
1480 tmpl = templater.parsestring(t)
1469 tmpl = templater.parsestring(t)
1481 except SyntaxError:
1470 except SyntaxError:
1482 tmpl = templater.parsestring(t, quoted=False)
1471 tmpl = templater.parsestring(t, quoted=False)
1483 return tmpl, None
1472 return tmpl, None
1484
1473
1485 if tmpl == 'list':
1474 if tmpl == 'list':
1486 ui.write(_("available styles: %s\n") % templater.stylelist())
1475 ui.write(_("available styles: %s\n") % templater.stylelist())
1487 raise util.Abort(_("specify a template"))
1476 raise util.Abort(_("specify a template"))
1488
1477
1489 # perhaps it's a path to a map or a template
1478 # perhaps it's a path to a map or a template
1490 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1479 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1491 # is it a mapfile for a style?
1480 # is it a mapfile for a style?
1492 if os.path.basename(tmpl).startswith("map-"):
1481 if os.path.basename(tmpl).startswith("map-"):
1493 return None, os.path.realpath(tmpl)
1482 return None, os.path.realpath(tmpl)
1494 tmpl = open(tmpl).read()
1483 tmpl = open(tmpl).read()
1495 return tmpl, None
1484 return tmpl, None
1496
1485
1497 # constant string?
1486 # constant string?
1498 return tmpl, None
1487 return tmpl, None
1499
1488
1500 def show_changeset(ui, repo, opts, buffered=False):
1489 def show_changeset(ui, repo, opts, buffered=False):
1501 """show one changeset using template or regular display.
1490 """show one changeset using template or regular display.
1502
1491
1503 Display format will be the first non-empty hit of:
1492 Display format will be the first non-empty hit of:
1504 1. option 'template'
1493 1. option 'template'
1505 2. option 'style'
1494 2. option 'style'
1506 3. [ui] setting 'logtemplate'
1495 3. [ui] setting 'logtemplate'
1507 4. [ui] setting 'style'
1496 4. [ui] setting 'style'
1508 If all of these values are either the unset or the empty string,
1497 If all of these values are either the unset or the empty string,
1509 regular display via changeset_printer() is done.
1498 regular display via changeset_printer() is done.
1510 """
1499 """
1511 # options
1500 # options
1512 matchfn = None
1501 matchfn = None
1513 if opts.get('patch') or opts.get('stat'):
1502 if opts.get('patch') or opts.get('stat'):
1514 matchfn = scmutil.matchall(repo)
1503 matchfn = scmutil.matchall(repo)
1515
1504
1516 if opts.get('template') == 'json':
1505 if opts.get('template') == 'json':
1517 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1506 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1518
1507
1519 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1508 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1520
1509
1521 if not tmpl and not mapfile:
1510 if not tmpl and not mapfile:
1522 return changeset_printer(ui, repo, matchfn, opts, buffered)
1511 return changeset_printer(ui, repo, matchfn, opts, buffered)
1523
1512
1524 try:
1513 try:
1525 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1514 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1526 buffered)
1515 buffered)
1527 except SyntaxError, inst:
1516 except SyntaxError, inst:
1528 raise util.Abort(inst.args[0])
1517 raise util.Abort(inst.args[0])
1529 return t
1518 return t
1530
1519
1531 def showmarker(ui, marker):
1520 def showmarker(ui, marker):
1532 """utility function to display obsolescence marker in a readable way
1521 """utility function to display obsolescence marker in a readable way
1533
1522
1534 To be used by debug function."""
1523 To be used by debug function."""
1535 ui.write(hex(marker.precnode()))
1524 ui.write(hex(marker.precnode()))
1536 for repl in marker.succnodes():
1525 for repl in marker.succnodes():
1537 ui.write(' ')
1526 ui.write(' ')
1538 ui.write(hex(repl))
1527 ui.write(hex(repl))
1539 ui.write(' %X ' % marker.flags())
1528 ui.write(' %X ' % marker.flags())
1540 parents = marker.parentnodes()
1529 parents = marker.parentnodes()
1541 if parents is not None:
1530 if parents is not None:
1542 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1531 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1543 ui.write('(%s) ' % util.datestr(marker.date()))
1532 ui.write('(%s) ' % util.datestr(marker.date()))
1544 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1533 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1545 sorted(marker.metadata().items())
1534 sorted(marker.metadata().items())
1546 if t[0] != 'date')))
1535 if t[0] != 'date')))
1547 ui.write('\n')
1536 ui.write('\n')
1548
1537
1549 def finddate(ui, repo, date):
1538 def finddate(ui, repo, date):
1550 """Find the tipmost changeset that matches the given date spec"""
1539 """Find the tipmost changeset that matches the given date spec"""
1551
1540
1552 df = util.matchdate(date)
1541 df = util.matchdate(date)
1553 m = scmutil.matchall(repo)
1542 m = scmutil.matchall(repo)
1554 results = {}
1543 results = {}
1555
1544
1556 def prep(ctx, fns):
1545 def prep(ctx, fns):
1557 d = ctx.date()
1546 d = ctx.date()
1558 if df(d[0]):
1547 if df(d[0]):
1559 results[ctx.rev()] = d
1548 results[ctx.rev()] = d
1560
1549
1561 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1550 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1562 rev = ctx.rev()
1551 rev = ctx.rev()
1563 if rev in results:
1552 if rev in results:
1564 ui.status(_("found revision %s from %s\n") %
1553 ui.status(_("found revision %s from %s\n") %
1565 (rev, util.datestr(results[rev])))
1554 (rev, util.datestr(results[rev])))
1566 return str(rev)
1555 return str(rev)
1567
1556
1568 raise util.Abort(_("revision matching date not found"))
1557 raise util.Abort(_("revision matching date not found"))
1569
1558
1570 def increasingwindows(windowsize=8, sizelimit=512):
1559 def increasingwindows(windowsize=8, sizelimit=512):
1571 while True:
1560 while True:
1572 yield windowsize
1561 yield windowsize
1573 if windowsize < sizelimit:
1562 if windowsize < sizelimit:
1574 windowsize *= 2
1563 windowsize *= 2
1575
1564
1576 class FileWalkError(Exception):
1565 class FileWalkError(Exception):
1577 pass
1566 pass
1578
1567
1579 def walkfilerevs(repo, match, follow, revs, fncache):
1568 def walkfilerevs(repo, match, follow, revs, fncache):
1580 '''Walks the file history for the matched files.
1569 '''Walks the file history for the matched files.
1581
1570
1582 Returns the changeset revs that are involved in the file history.
1571 Returns the changeset revs that are involved in the file history.
1583
1572
1584 Throws FileWalkError if the file history can't be walked using
1573 Throws FileWalkError if the file history can't be walked using
1585 filelogs alone.
1574 filelogs alone.
1586 '''
1575 '''
1587 wanted = set()
1576 wanted = set()
1588 copies = []
1577 copies = []
1589 minrev, maxrev = min(revs), max(revs)
1578 minrev, maxrev = min(revs), max(revs)
1590 def filerevgen(filelog, last):
1579 def filerevgen(filelog, last):
1591 """
1580 """
1592 Only files, no patterns. Check the history of each file.
1581 Only files, no patterns. Check the history of each file.
1593
1582
1594 Examines filelog entries within minrev, maxrev linkrev range
1583 Examines filelog entries within minrev, maxrev linkrev range
1595 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1584 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1596 tuples in backwards order
1585 tuples in backwards order
1597 """
1586 """
1598 cl_count = len(repo)
1587 cl_count = len(repo)
1599 revs = []
1588 revs = []
1600 for j in xrange(0, last + 1):
1589 for j in xrange(0, last + 1):
1601 linkrev = filelog.linkrev(j)
1590 linkrev = filelog.linkrev(j)
1602 if linkrev < minrev:
1591 if linkrev < minrev:
1603 continue
1592 continue
1604 # only yield rev for which we have the changelog, it can
1593 # only yield rev for which we have the changelog, it can
1605 # happen while doing "hg log" during a pull or commit
1594 # happen while doing "hg log" during a pull or commit
1606 if linkrev >= cl_count:
1595 if linkrev >= cl_count:
1607 break
1596 break
1608
1597
1609 parentlinkrevs = []
1598 parentlinkrevs = []
1610 for p in filelog.parentrevs(j):
1599 for p in filelog.parentrevs(j):
1611 if p != nullrev:
1600 if p != nullrev:
1612 parentlinkrevs.append(filelog.linkrev(p))
1601 parentlinkrevs.append(filelog.linkrev(p))
1613 n = filelog.node(j)
1602 n = filelog.node(j)
1614 revs.append((linkrev, parentlinkrevs,
1603 revs.append((linkrev, parentlinkrevs,
1615 follow and filelog.renamed(n)))
1604 follow and filelog.renamed(n)))
1616
1605
1617 return reversed(revs)
1606 return reversed(revs)
1618 def iterfiles():
1607 def iterfiles():
1619 pctx = repo['.']
1608 pctx = repo['.']
1620 for filename in match.files():
1609 for filename in match.files():
1621 if follow:
1610 if follow:
1622 if filename not in pctx:
1611 if filename not in pctx:
1623 raise util.Abort(_('cannot follow file not in parent '
1612 raise util.Abort(_('cannot follow file not in parent '
1624 'revision: "%s"') % filename)
1613 'revision: "%s"') % filename)
1625 yield filename, pctx[filename].filenode()
1614 yield filename, pctx[filename].filenode()
1626 else:
1615 else:
1627 yield filename, None
1616 yield filename, None
1628 for filename_node in copies:
1617 for filename_node in copies:
1629 yield filename_node
1618 yield filename_node
1630
1619
1631 for file_, node in iterfiles():
1620 for file_, node in iterfiles():
1632 filelog = repo.file(file_)
1621 filelog = repo.file(file_)
1633 if not len(filelog):
1622 if not len(filelog):
1634 if node is None:
1623 if node is None:
1635 # A zero count may be a directory or deleted file, so
1624 # A zero count may be a directory or deleted file, so
1636 # try to find matching entries on the slow path.
1625 # try to find matching entries on the slow path.
1637 if follow:
1626 if follow:
1638 raise util.Abort(
1627 raise util.Abort(
1639 _('cannot follow nonexistent file: "%s"') % file_)
1628 _('cannot follow nonexistent file: "%s"') % file_)
1640 raise FileWalkError("Cannot walk via filelog")
1629 raise FileWalkError("Cannot walk via filelog")
1641 else:
1630 else:
1642 continue
1631 continue
1643
1632
1644 if node is None:
1633 if node is None:
1645 last = len(filelog) - 1
1634 last = len(filelog) - 1
1646 else:
1635 else:
1647 last = filelog.rev(node)
1636 last = filelog.rev(node)
1648
1637
1649 # keep track of all ancestors of the file
1638 # keep track of all ancestors of the file
1650 ancestors = set([filelog.linkrev(last)])
1639 ancestors = set([filelog.linkrev(last)])
1651
1640
1652 # iterate from latest to oldest revision
1641 # iterate from latest to oldest revision
1653 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1642 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1654 if not follow:
1643 if not follow:
1655 if rev > maxrev:
1644 if rev > maxrev:
1656 continue
1645 continue
1657 else:
1646 else:
1658 # Note that last might not be the first interesting
1647 # Note that last might not be the first interesting
1659 # rev to us:
1648 # rev to us:
1660 # if the file has been changed after maxrev, we'll
1649 # if the file has been changed after maxrev, we'll
1661 # have linkrev(last) > maxrev, and we still need
1650 # have linkrev(last) > maxrev, and we still need
1662 # to explore the file graph
1651 # to explore the file graph
1663 if rev not in ancestors:
1652 if rev not in ancestors:
1664 continue
1653 continue
1665 # XXX insert 1327 fix here
1654 # XXX insert 1327 fix here
1666 if flparentlinkrevs:
1655 if flparentlinkrevs:
1667 ancestors.update(flparentlinkrevs)
1656 ancestors.update(flparentlinkrevs)
1668
1657
1669 fncache.setdefault(rev, []).append(file_)
1658 fncache.setdefault(rev, []).append(file_)
1670 wanted.add(rev)
1659 wanted.add(rev)
1671 if copied:
1660 if copied:
1672 copies.append(copied)
1661 copies.append(copied)
1673
1662
1674 return wanted
1663 return wanted
1675
1664
1676 class _followfilter(object):
1665 class _followfilter(object):
1677 def __init__(self, repo, onlyfirst=False):
1666 def __init__(self, repo, onlyfirst=False):
1678 self.repo = repo
1667 self.repo = repo
1679 self.startrev = nullrev
1668 self.startrev = nullrev
1680 self.roots = set()
1669 self.roots = set()
1681 self.onlyfirst = onlyfirst
1670 self.onlyfirst = onlyfirst
1682
1671
1683 def match(self, rev):
1672 def match(self, rev):
1684 def realparents(rev):
1673 def realparents(rev):
1685 if self.onlyfirst:
1674 if self.onlyfirst:
1686 return self.repo.changelog.parentrevs(rev)[0:1]
1675 return self.repo.changelog.parentrevs(rev)[0:1]
1687 else:
1676 else:
1688 return filter(lambda x: x != nullrev,
1677 return filter(lambda x: x != nullrev,
1689 self.repo.changelog.parentrevs(rev))
1678 self.repo.changelog.parentrevs(rev))
1690
1679
1691 if self.startrev == nullrev:
1680 if self.startrev == nullrev:
1692 self.startrev = rev
1681 self.startrev = rev
1693 return True
1682 return True
1694
1683
1695 if rev > self.startrev:
1684 if rev > self.startrev:
1696 # forward: all descendants
1685 # forward: all descendants
1697 if not self.roots:
1686 if not self.roots:
1698 self.roots.add(self.startrev)
1687 self.roots.add(self.startrev)
1699 for parent in realparents(rev):
1688 for parent in realparents(rev):
1700 if parent in self.roots:
1689 if parent in self.roots:
1701 self.roots.add(rev)
1690 self.roots.add(rev)
1702 return True
1691 return True
1703 else:
1692 else:
1704 # backwards: all parents
1693 # backwards: all parents
1705 if not self.roots:
1694 if not self.roots:
1706 self.roots.update(realparents(self.startrev))
1695 self.roots.update(realparents(self.startrev))
1707 if rev in self.roots:
1696 if rev in self.roots:
1708 self.roots.remove(rev)
1697 self.roots.remove(rev)
1709 self.roots.update(realparents(rev))
1698 self.roots.update(realparents(rev))
1710 return True
1699 return True
1711
1700
1712 return False
1701 return False
1713
1702
1714 def walkchangerevs(repo, match, opts, prepare):
1703 def walkchangerevs(repo, match, opts, prepare):
1715 '''Iterate over files and the revs in which they changed.
1704 '''Iterate over files and the revs in which they changed.
1716
1705
1717 Callers most commonly need to iterate backwards over the history
1706 Callers most commonly need to iterate backwards over the history
1718 in which they are interested. Doing so has awful (quadratic-looking)
1707 in which they are interested. Doing so has awful (quadratic-looking)
1719 performance, so we use iterators in a "windowed" way.
1708 performance, so we use iterators in a "windowed" way.
1720
1709
1721 We walk a window of revisions in the desired order. Within the
1710 We walk a window of revisions in the desired order. Within the
1722 window, we first walk forwards to gather data, then in the desired
1711 window, we first walk forwards to gather data, then in the desired
1723 order (usually backwards) to display it.
1712 order (usually backwards) to display it.
1724
1713
1725 This function returns an iterator yielding contexts. Before
1714 This function returns an iterator yielding contexts. Before
1726 yielding each context, the iterator will first call the prepare
1715 yielding each context, the iterator will first call the prepare
1727 function on each context in the window in forward order.'''
1716 function on each context in the window in forward order.'''
1728
1717
1729 follow = opts.get('follow') or opts.get('follow_first')
1718 follow = opts.get('follow') or opts.get('follow_first')
1730 revs = _logrevs(repo, opts)
1719 revs = _logrevs(repo, opts)
1731 if not revs:
1720 if not revs:
1732 return []
1721 return []
1733 wanted = set()
1722 wanted = set()
1734 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1723 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1735 fncache = {}
1724 fncache = {}
1736 change = repo.changectx
1725 change = repo.changectx
1737
1726
1738 # First step is to fill wanted, the set of revisions that we want to yield.
1727 # First step is to fill wanted, the set of revisions that we want to yield.
1739 # When it does not induce extra cost, we also fill fncache for revisions in
1728 # When it does not induce extra cost, we also fill fncache for revisions in
1740 # wanted: a cache of filenames that were changed (ctx.files()) and that
1729 # wanted: a cache of filenames that were changed (ctx.files()) and that
1741 # match the file filtering conditions.
1730 # match the file filtering conditions.
1742
1731
1743 if match.always():
1732 if match.always():
1744 # No files, no patterns. Display all revs.
1733 # No files, no patterns. Display all revs.
1745 wanted = revs
1734 wanted = revs
1746
1735
1747 if not slowpath and match.files():
1736 if not slowpath and match.files():
1748 # We only have to read through the filelog to find wanted revisions
1737 # We only have to read through the filelog to find wanted revisions
1749
1738
1750 try:
1739 try:
1751 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1740 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1752 except FileWalkError:
1741 except FileWalkError:
1753 slowpath = True
1742 slowpath = True
1754
1743
1755 # We decided to fall back to the slowpath because at least one
1744 # We decided to fall back to the slowpath because at least one
1756 # of the paths was not a file. Check to see if at least one of them
1745 # of the paths was not a file. Check to see if at least one of them
1757 # existed in history, otherwise simply return
1746 # existed in history, otherwise simply return
1758 for path in match.files():
1747 for path in match.files():
1759 if path == '.' or path in repo.store:
1748 if path == '.' or path in repo.store:
1760 break
1749 break
1761 else:
1750 else:
1762 return []
1751 return []
1763
1752
1764 if slowpath:
1753 if slowpath:
1765 # We have to read the changelog to match filenames against
1754 # We have to read the changelog to match filenames against
1766 # changed files
1755 # changed files
1767
1756
1768 if follow:
1757 if follow:
1769 raise util.Abort(_('can only follow copies/renames for explicit '
1758 raise util.Abort(_('can only follow copies/renames for explicit '
1770 'filenames'))
1759 'filenames'))
1771
1760
1772 # The slow path checks files modified in every changeset.
1761 # The slow path checks files modified in every changeset.
1773 # This is really slow on large repos, so compute the set lazily.
1762 # This is really slow on large repos, so compute the set lazily.
1774 class lazywantedset(object):
1763 class lazywantedset(object):
1775 def __init__(self):
1764 def __init__(self):
1776 self.set = set()
1765 self.set = set()
1777 self.revs = set(revs)
1766 self.revs = set(revs)
1778
1767
1779 # No need to worry about locality here because it will be accessed
1768 # No need to worry about locality here because it will be accessed
1780 # in the same order as the increasing window below.
1769 # in the same order as the increasing window below.
1781 def __contains__(self, value):
1770 def __contains__(self, value):
1782 if value in self.set:
1771 if value in self.set:
1783 return True
1772 return True
1784 elif not value in self.revs:
1773 elif not value in self.revs:
1785 return False
1774 return False
1786 else:
1775 else:
1787 self.revs.discard(value)
1776 self.revs.discard(value)
1788 ctx = change(value)
1777 ctx = change(value)
1789 matches = filter(match, ctx.files())
1778 matches = filter(match, ctx.files())
1790 if matches:
1779 if matches:
1791 fncache[value] = matches
1780 fncache[value] = matches
1792 self.set.add(value)
1781 self.set.add(value)
1793 return True
1782 return True
1794 return False
1783 return False
1795
1784
1796 def discard(self, value):
1785 def discard(self, value):
1797 self.revs.discard(value)
1786 self.revs.discard(value)
1798 self.set.discard(value)
1787 self.set.discard(value)
1799
1788
1800 wanted = lazywantedset()
1789 wanted = lazywantedset()
1801
1790
1802 # it might be worthwhile to do this in the iterator if the rev range
1791 # it might be worthwhile to do this in the iterator if the rev range
1803 # is descending and the prune args are all within that range
1792 # is descending and the prune args are all within that range
1804 for rev in opts.get('prune', ()):
1793 for rev in opts.get('prune', ()):
1805 rev = repo[rev].rev()
1794 rev = repo[rev].rev()
1806 ff = _followfilter(repo)
1795 ff = _followfilter(repo)
1807 stop = min(revs[0], revs[-1])
1796 stop = min(revs[0], revs[-1])
1808 for x in xrange(rev, stop - 1, -1):
1797 for x in xrange(rev, stop - 1, -1):
1809 if ff.match(x):
1798 if ff.match(x):
1810 wanted = wanted - [x]
1799 wanted = wanted - [x]
1811
1800
1812 # Now that wanted is correctly initialized, we can iterate over the
1801 # Now that wanted is correctly initialized, we can iterate over the
1813 # revision range, yielding only revisions in wanted.
1802 # revision range, yielding only revisions in wanted.
1814 def iterate():
1803 def iterate():
1815 if follow and not match.files():
1804 if follow and not match.files():
1816 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1805 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1817 def want(rev):
1806 def want(rev):
1818 return ff.match(rev) and rev in wanted
1807 return ff.match(rev) and rev in wanted
1819 else:
1808 else:
1820 def want(rev):
1809 def want(rev):
1821 return rev in wanted
1810 return rev in wanted
1822
1811
1823 it = iter(revs)
1812 it = iter(revs)
1824 stopiteration = False
1813 stopiteration = False
1825 for windowsize in increasingwindows():
1814 for windowsize in increasingwindows():
1826 nrevs = []
1815 nrevs = []
1827 for i in xrange(windowsize):
1816 for i in xrange(windowsize):
1828 try:
1817 try:
1829 rev = it.next()
1818 rev = it.next()
1830 if want(rev):
1819 if want(rev):
1831 nrevs.append(rev)
1820 nrevs.append(rev)
1832 except (StopIteration):
1821 except (StopIteration):
1833 stopiteration = True
1822 stopiteration = True
1834 break
1823 break
1835 for rev in sorted(nrevs):
1824 for rev in sorted(nrevs):
1836 fns = fncache.get(rev)
1825 fns = fncache.get(rev)
1837 ctx = change(rev)
1826 ctx = change(rev)
1838 if not fns:
1827 if not fns:
1839 def fns_generator():
1828 def fns_generator():
1840 for f in ctx.files():
1829 for f in ctx.files():
1841 if match(f):
1830 if match(f):
1842 yield f
1831 yield f
1843 fns = fns_generator()
1832 fns = fns_generator()
1844 prepare(ctx, fns)
1833 prepare(ctx, fns)
1845 for rev in nrevs:
1834 for rev in nrevs:
1846 yield change(rev)
1835 yield change(rev)
1847
1836
1848 if stopiteration:
1837 if stopiteration:
1849 break
1838 break
1850
1839
1851 return iterate()
1840 return iterate()
1852
1841
1853 def _makefollowlogfilematcher(repo, files, followfirst):
1842 def _makefollowlogfilematcher(repo, files, followfirst):
1854 # When displaying a revision with --patch --follow FILE, we have
1843 # When displaying a revision with --patch --follow FILE, we have
1855 # to know which file of the revision must be diffed. With
1844 # to know which file of the revision must be diffed. With
1856 # --follow, we want the names of the ancestors of FILE in the
1845 # --follow, we want the names of the ancestors of FILE in the
1857 # revision, stored in "fcache". "fcache" is populated by
1846 # revision, stored in "fcache". "fcache" is populated by
1858 # reproducing the graph traversal already done by --follow revset
1847 # reproducing the graph traversal already done by --follow revset
1859 # and relating linkrevs to file names (which is not "correct" but
1848 # and relating linkrevs to file names (which is not "correct" but
1860 # good enough).
1849 # good enough).
1861 fcache = {}
1850 fcache = {}
1862 fcacheready = [False]
1851 fcacheready = [False]
1863 pctx = repo['.']
1852 pctx = repo['.']
1864
1853
1865 def populate():
1854 def populate():
1866 for fn in files:
1855 for fn in files:
1867 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1856 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1868 for c in i:
1857 for c in i:
1869 fcache.setdefault(c.linkrev(), set()).add(c.path())
1858 fcache.setdefault(c.linkrev(), set()).add(c.path())
1870
1859
1871 def filematcher(rev):
1860 def filematcher(rev):
1872 if not fcacheready[0]:
1861 if not fcacheready[0]:
1873 # Lazy initialization
1862 # Lazy initialization
1874 fcacheready[0] = True
1863 fcacheready[0] = True
1875 populate()
1864 populate()
1876 return scmutil.matchfiles(repo, fcache.get(rev, []))
1865 return scmutil.matchfiles(repo, fcache.get(rev, []))
1877
1866
1878 return filematcher
1867 return filematcher
1879
1868
1880 def _makenofollowlogfilematcher(repo, pats, opts):
1869 def _makenofollowlogfilematcher(repo, pats, opts):
1881 '''hook for extensions to override the filematcher for non-follow cases'''
1870 '''hook for extensions to override the filematcher for non-follow cases'''
1882 return None
1871 return None
1883
1872
1884 def _makelogrevset(repo, pats, opts, revs):
1873 def _makelogrevset(repo, pats, opts, revs):
1885 """Return (expr, filematcher) where expr is a revset string built
1874 """Return (expr, filematcher) where expr is a revset string built
1886 from log options and file patterns or None. If --stat or --patch
1875 from log options and file patterns or None. If --stat or --patch
1887 are not passed filematcher is None. Otherwise it is a callable
1876 are not passed filematcher is None. Otherwise it is a callable
1888 taking a revision number and returning a match objects filtering
1877 taking a revision number and returning a match objects filtering
1889 the files to be detailed when displaying the revision.
1878 the files to be detailed when displaying the revision.
1890 """
1879 """
1891 opt2revset = {
1880 opt2revset = {
1892 'no_merges': ('not merge()', None),
1881 'no_merges': ('not merge()', None),
1893 'only_merges': ('merge()', None),
1882 'only_merges': ('merge()', None),
1894 '_ancestors': ('ancestors(%(val)s)', None),
1883 '_ancestors': ('ancestors(%(val)s)', None),
1895 '_fancestors': ('_firstancestors(%(val)s)', None),
1884 '_fancestors': ('_firstancestors(%(val)s)', None),
1896 '_descendants': ('descendants(%(val)s)', None),
1885 '_descendants': ('descendants(%(val)s)', None),
1897 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1886 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1898 '_matchfiles': ('_matchfiles(%(val)s)', None),
1887 '_matchfiles': ('_matchfiles(%(val)s)', None),
1899 'date': ('date(%(val)r)', None),
1888 'date': ('date(%(val)r)', None),
1900 'branch': ('branch(%(val)r)', ' or '),
1889 'branch': ('branch(%(val)r)', ' or '),
1901 '_patslog': ('filelog(%(val)r)', ' or '),
1890 '_patslog': ('filelog(%(val)r)', ' or '),
1902 '_patsfollow': ('follow(%(val)r)', ' or '),
1891 '_patsfollow': ('follow(%(val)r)', ' or '),
1903 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1892 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1904 'keyword': ('keyword(%(val)r)', ' or '),
1893 'keyword': ('keyword(%(val)r)', ' or '),
1905 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1894 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1906 'user': ('user(%(val)r)', ' or '),
1895 'user': ('user(%(val)r)', ' or '),
1907 }
1896 }
1908
1897
1909 opts = dict(opts)
1898 opts = dict(opts)
1910 # follow or not follow?
1899 # follow or not follow?
1911 follow = opts.get('follow') or opts.get('follow_first')
1900 follow = opts.get('follow') or opts.get('follow_first')
1912 if opts.get('follow_first'):
1901 if opts.get('follow_first'):
1913 followfirst = 1
1902 followfirst = 1
1914 else:
1903 else:
1915 followfirst = 0
1904 followfirst = 0
1916 # --follow with FILE behaviour depends on revs...
1905 # --follow with FILE behaviour depends on revs...
1917 it = iter(revs)
1906 it = iter(revs)
1918 startrev = it.next()
1907 startrev = it.next()
1919 try:
1908 try:
1920 followdescendants = startrev < it.next()
1909 followdescendants = startrev < it.next()
1921 except (StopIteration):
1910 except (StopIteration):
1922 followdescendants = False
1911 followdescendants = False
1923
1912
1924 # branch and only_branch are really aliases and must be handled at
1913 # branch and only_branch are really aliases and must be handled at
1925 # the same time
1914 # the same time
1926 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1915 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1927 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1916 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1928 # pats/include/exclude are passed to match.match() directly in
1917 # pats/include/exclude are passed to match.match() directly in
1929 # _matchfiles() revset but walkchangerevs() builds its matcher with
1918 # _matchfiles() revset but walkchangerevs() builds its matcher with
1930 # scmutil.match(). The difference is input pats are globbed on
1919 # scmutil.match(). The difference is input pats are globbed on
1931 # platforms without shell expansion (windows).
1920 # platforms without shell expansion (windows).
1932 wctx = repo[None]
1921 wctx = repo[None]
1933 match, pats = scmutil.matchandpats(wctx, pats, opts)
1922 match, pats = scmutil.matchandpats(wctx, pats, opts)
1934 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1923 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1935 if not slowpath:
1924 if not slowpath:
1936 for f in match.files():
1925 for f in match.files():
1937 if follow and f not in wctx:
1926 if follow and f not in wctx:
1938 # If the file exists, it may be a directory, so let it
1927 # If the file exists, it may be a directory, so let it
1939 # take the slow path.
1928 # take the slow path.
1940 if os.path.exists(repo.wjoin(f)):
1929 if os.path.exists(repo.wjoin(f)):
1941 slowpath = True
1930 slowpath = True
1942 continue
1931 continue
1943 else:
1932 else:
1944 raise util.Abort(_('cannot follow file not in parent '
1933 raise util.Abort(_('cannot follow file not in parent '
1945 'revision: "%s"') % f)
1934 'revision: "%s"') % f)
1946 filelog = repo.file(f)
1935 filelog = repo.file(f)
1947 if not filelog:
1936 if not filelog:
1948 # A zero count may be a directory or deleted file, so
1937 # A zero count may be a directory or deleted file, so
1949 # try to find matching entries on the slow path.
1938 # try to find matching entries on the slow path.
1950 if follow:
1939 if follow:
1951 raise util.Abort(
1940 raise util.Abort(
1952 _('cannot follow nonexistent file: "%s"') % f)
1941 _('cannot follow nonexistent file: "%s"') % f)
1953 slowpath = True
1942 slowpath = True
1954
1943
1955 # We decided to fall back to the slowpath because at least one
1944 # We decided to fall back to the slowpath because at least one
1956 # of the paths was not a file. Check to see if at least one of them
1945 # of the paths was not a file. Check to see if at least one of them
1957 # existed in history - in that case, we'll continue down the
1946 # existed in history - in that case, we'll continue down the
1958 # slowpath; otherwise, we can turn off the slowpath
1947 # slowpath; otherwise, we can turn off the slowpath
1959 if slowpath:
1948 if slowpath:
1960 for path in match.files():
1949 for path in match.files():
1961 if path == '.' or path in repo.store:
1950 if path == '.' or path in repo.store:
1962 break
1951 break
1963 else:
1952 else:
1964 slowpath = False
1953 slowpath = False
1965
1954
1966 fpats = ('_patsfollow', '_patsfollowfirst')
1955 fpats = ('_patsfollow', '_patsfollowfirst')
1967 fnopats = (('_ancestors', '_fancestors'),
1956 fnopats = (('_ancestors', '_fancestors'),
1968 ('_descendants', '_fdescendants'))
1957 ('_descendants', '_fdescendants'))
1969 if slowpath:
1958 if slowpath:
1970 # See walkchangerevs() slow path.
1959 # See walkchangerevs() slow path.
1971 #
1960 #
1972 # pats/include/exclude cannot be represented as separate
1961 # pats/include/exclude cannot be represented as separate
1973 # revset expressions as their filtering logic applies at file
1962 # revset expressions as their filtering logic applies at file
1974 # level. For instance "-I a -X a" matches a revision touching
1963 # level. For instance "-I a -X a" matches a revision touching
1975 # "a" and "b" while "file(a) and not file(b)" does
1964 # "a" and "b" while "file(a) and not file(b)" does
1976 # not. Besides, filesets are evaluated against the working
1965 # not. Besides, filesets are evaluated against the working
1977 # directory.
1966 # directory.
1978 matchargs = ['r:', 'd:relpath']
1967 matchargs = ['r:', 'd:relpath']
1979 for p in pats:
1968 for p in pats:
1980 matchargs.append('p:' + p)
1969 matchargs.append('p:' + p)
1981 for p in opts.get('include', []):
1970 for p in opts.get('include', []):
1982 matchargs.append('i:' + p)
1971 matchargs.append('i:' + p)
1983 for p in opts.get('exclude', []):
1972 for p in opts.get('exclude', []):
1984 matchargs.append('x:' + p)
1973 matchargs.append('x:' + p)
1985 matchargs = ','.join(('%r' % p) for p in matchargs)
1974 matchargs = ','.join(('%r' % p) for p in matchargs)
1986 opts['_matchfiles'] = matchargs
1975 opts['_matchfiles'] = matchargs
1987 if follow:
1976 if follow:
1988 opts[fnopats[0][followfirst]] = '.'
1977 opts[fnopats[0][followfirst]] = '.'
1989 else:
1978 else:
1990 if follow:
1979 if follow:
1991 if pats:
1980 if pats:
1992 # follow() revset interprets its file argument as a
1981 # follow() revset interprets its file argument as a
1993 # manifest entry, so use match.files(), not pats.
1982 # manifest entry, so use match.files(), not pats.
1994 opts[fpats[followfirst]] = list(match.files())
1983 opts[fpats[followfirst]] = list(match.files())
1995 else:
1984 else:
1996 op = fnopats[followdescendants][followfirst]
1985 op = fnopats[followdescendants][followfirst]
1997 opts[op] = 'rev(%d)' % startrev
1986 opts[op] = 'rev(%d)' % startrev
1998 else:
1987 else:
1999 opts['_patslog'] = list(pats)
1988 opts['_patslog'] = list(pats)
2000
1989
2001 filematcher = None
1990 filematcher = None
2002 if opts.get('patch') or opts.get('stat'):
1991 if opts.get('patch') or opts.get('stat'):
2003 # When following files, track renames via a special matcher.
1992 # When following files, track renames via a special matcher.
2004 # If we're forced to take the slowpath it means we're following
1993 # If we're forced to take the slowpath it means we're following
2005 # at least one pattern/directory, so don't bother with rename tracking.
1994 # at least one pattern/directory, so don't bother with rename tracking.
2006 if follow and not match.always() and not slowpath:
1995 if follow and not match.always() and not slowpath:
2007 # _makefollowlogfilematcher expects its files argument to be
1996 # _makefollowlogfilematcher expects its files argument to be
2008 # relative to the repo root, so use match.files(), not pats.
1997 # relative to the repo root, so use match.files(), not pats.
2009 filematcher = _makefollowlogfilematcher(repo, match.files(),
1998 filematcher = _makefollowlogfilematcher(repo, match.files(),
2010 followfirst)
1999 followfirst)
2011 else:
2000 else:
2012 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2001 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2013 if filematcher is None:
2002 if filematcher is None:
2014 filematcher = lambda rev: match
2003 filematcher = lambda rev: match
2015
2004
2016 expr = []
2005 expr = []
2017 for op, val in sorted(opts.iteritems()):
2006 for op, val in sorted(opts.iteritems()):
2018 if not val:
2007 if not val:
2019 continue
2008 continue
2020 if op not in opt2revset:
2009 if op not in opt2revset:
2021 continue
2010 continue
2022 revop, andor = opt2revset[op]
2011 revop, andor = opt2revset[op]
2023 if '%(val)' not in revop:
2012 if '%(val)' not in revop:
2024 expr.append(revop)
2013 expr.append(revop)
2025 else:
2014 else:
2026 if not isinstance(val, list):
2015 if not isinstance(val, list):
2027 e = revop % {'val': val}
2016 e = revop % {'val': val}
2028 else:
2017 else:
2029 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2018 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2030 expr.append(e)
2019 expr.append(e)
2031
2020
2032 if expr:
2021 if expr:
2033 expr = '(' + ' and '.join(expr) + ')'
2022 expr = '(' + ' and '.join(expr) + ')'
2034 else:
2023 else:
2035 expr = None
2024 expr = None
2036 return expr, filematcher
2025 return expr, filematcher
2037
2026
2038 def _logrevs(repo, opts):
2027 def _logrevs(repo, opts):
2039 # Default --rev value depends on --follow but --follow behaviour
2028 # Default --rev value depends on --follow but --follow behaviour
2040 # depends on revisions resolved from --rev...
2029 # depends on revisions resolved from --rev...
2041 follow = opts.get('follow') or opts.get('follow_first')
2030 follow = opts.get('follow') or opts.get('follow_first')
2042 if opts.get('rev'):
2031 if opts.get('rev'):
2043 revs = scmutil.revrange(repo, opts['rev'])
2032 revs = scmutil.revrange(repo, opts['rev'])
2044 elif follow and repo.dirstate.p1() == nullid:
2033 elif follow and repo.dirstate.p1() == nullid:
2045 revs = revset.baseset()
2034 revs = revset.baseset()
2046 elif follow:
2035 elif follow:
2047 revs = repo.revs('reverse(:.)')
2036 revs = repo.revs('reverse(:.)')
2048 else:
2037 else:
2049 revs = revset.spanset(repo)
2038 revs = revset.spanset(repo)
2050 revs.reverse()
2039 revs.reverse()
2051 return revs
2040 return revs
2052
2041
2053 def getgraphlogrevs(repo, pats, opts):
2042 def getgraphlogrevs(repo, pats, opts):
2054 """Return (revs, expr, filematcher) where revs is an iterable of
2043 """Return (revs, expr, filematcher) where revs is an iterable of
2055 revision numbers, expr is a revset string built from log options
2044 revision numbers, expr is a revset string built from log options
2056 and file patterns or None, and used to filter 'revs'. If --stat or
2045 and file patterns or None, and used to filter 'revs'. If --stat or
2057 --patch are not passed filematcher is None. Otherwise it is a
2046 --patch are not passed filematcher is None. Otherwise it is a
2058 callable taking a revision number and returning a match objects
2047 callable taking a revision number and returning a match objects
2059 filtering the files to be detailed when displaying the revision.
2048 filtering the files to be detailed when displaying the revision.
2060 """
2049 """
2061 limit = loglimit(opts)
2050 limit = loglimit(opts)
2062 revs = _logrevs(repo, opts)
2051 revs = _logrevs(repo, opts)
2063 if not revs:
2052 if not revs:
2064 return revset.baseset(), None, None
2053 return revset.baseset(), None, None
2065 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2054 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2066 if opts.get('rev'):
2055 if opts.get('rev'):
2067 # User-specified revs might be unsorted, but don't sort before
2056 # User-specified revs might be unsorted, but don't sort before
2068 # _makelogrevset because it might depend on the order of revs
2057 # _makelogrevset because it might depend on the order of revs
2069 revs.sort(reverse=True)
2058 revs.sort(reverse=True)
2070 if expr:
2059 if expr:
2071 # Revset matchers often operate faster on revisions in changelog
2060 # Revset matchers often operate faster on revisions in changelog
2072 # order, because most filters deal with the changelog.
2061 # order, because most filters deal with the changelog.
2073 revs.reverse()
2062 revs.reverse()
2074 matcher = revset.match(repo.ui, expr)
2063 matcher = revset.match(repo.ui, expr)
2075 # Revset matches can reorder revisions. "A or B" typically returns
2064 # Revset matches can reorder revisions. "A or B" typically returns
2076 # returns the revision matching A then the revision matching B. Sort
2065 # returns the revision matching A then the revision matching B. Sort
2077 # again to fix that.
2066 # again to fix that.
2078 revs = matcher(repo, revs)
2067 revs = matcher(repo, revs)
2079 revs.sort(reverse=True)
2068 revs.sort(reverse=True)
2080 if limit is not None:
2069 if limit is not None:
2081 limitedrevs = []
2070 limitedrevs = []
2082 for idx, rev in enumerate(revs):
2071 for idx, rev in enumerate(revs):
2083 if idx >= limit:
2072 if idx >= limit:
2084 break
2073 break
2085 limitedrevs.append(rev)
2074 limitedrevs.append(rev)
2086 revs = revset.baseset(limitedrevs)
2075 revs = revset.baseset(limitedrevs)
2087
2076
2088 return revs, expr, filematcher
2077 return revs, expr, filematcher
2089
2078
2090 def getlogrevs(repo, pats, opts):
2079 def getlogrevs(repo, pats, opts):
2091 """Return (revs, expr, filematcher) where revs is an iterable of
2080 """Return (revs, expr, filematcher) where revs is an iterable of
2092 revision numbers, expr is a revset string built from log options
2081 revision numbers, expr is a revset string built from log options
2093 and file patterns or None, and used to filter 'revs'. If --stat or
2082 and file patterns or None, and used to filter 'revs'. If --stat or
2094 --patch are not passed filematcher is None. Otherwise it is a
2083 --patch are not passed filematcher is None. Otherwise it is a
2095 callable taking a revision number and returning a match objects
2084 callable taking a revision number and returning a match objects
2096 filtering the files to be detailed when displaying the revision.
2085 filtering the files to be detailed when displaying the revision.
2097 """
2086 """
2098 limit = loglimit(opts)
2087 limit = loglimit(opts)
2099 revs = _logrevs(repo, opts)
2088 revs = _logrevs(repo, opts)
2100 if not revs:
2089 if not revs:
2101 return revset.baseset([]), None, None
2090 return revset.baseset([]), None, None
2102 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2091 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2103 if expr:
2092 if expr:
2104 # Revset matchers often operate faster on revisions in changelog
2093 # Revset matchers often operate faster on revisions in changelog
2105 # order, because most filters deal with the changelog.
2094 # order, because most filters deal with the changelog.
2106 if not opts.get('rev'):
2095 if not opts.get('rev'):
2107 revs.reverse()
2096 revs.reverse()
2108 matcher = revset.match(repo.ui, expr)
2097 matcher = revset.match(repo.ui, expr)
2109 # Revset matches can reorder revisions. "A or B" typically returns
2098 # Revset matches can reorder revisions. "A or B" typically returns
2110 # returns the revision matching A then the revision matching B. Sort
2099 # returns the revision matching A then the revision matching B. Sort
2111 # again to fix that.
2100 # again to fix that.
2112 revs = matcher(repo, revs)
2101 revs = matcher(repo, revs)
2113 if not opts.get('rev'):
2102 if not opts.get('rev'):
2114 revs.sort(reverse=True)
2103 revs.sort(reverse=True)
2115 if limit is not None:
2104 if limit is not None:
2116 count = 0
2105 count = 0
2117 limitedrevs = []
2106 limitedrevs = []
2118 it = iter(revs)
2107 it = iter(revs)
2119 while count < limit:
2108 while count < limit:
2120 try:
2109 try:
2121 limitedrevs.append(it.next())
2110 limitedrevs.append(it.next())
2122 except (StopIteration):
2111 except (StopIteration):
2123 break
2112 break
2124 count += 1
2113 count += 1
2125 revs = revset.baseset(limitedrevs)
2114 revs = revset.baseset(limitedrevs)
2126
2115
2127 return revs, expr, filematcher
2116 return revs, expr, filematcher
2128
2117
2129 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2118 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2130 filematcher=None):
2119 filematcher=None):
2131 seen, state = [], graphmod.asciistate()
2120 seen, state = [], graphmod.asciistate()
2132 for rev, type, ctx, parents in dag:
2121 for rev, type, ctx, parents in dag:
2133 char = 'o'
2122 char = 'o'
2134 if ctx.node() in showparents:
2123 if ctx.node() in showparents:
2135 char = '@'
2124 char = '@'
2136 elif ctx.obsolete():
2125 elif ctx.obsolete():
2137 char = 'x'
2126 char = 'x'
2138 elif ctx.closesbranch():
2127 elif ctx.closesbranch():
2139 char = '_'
2128 char = '_'
2140 copies = None
2129 copies = None
2141 if getrenamed and ctx.rev():
2130 if getrenamed and ctx.rev():
2142 copies = []
2131 copies = []
2143 for fn in ctx.files():
2132 for fn in ctx.files():
2144 rename = getrenamed(fn, ctx.rev())
2133 rename = getrenamed(fn, ctx.rev())
2145 if rename:
2134 if rename:
2146 copies.append((fn, rename[0]))
2135 copies.append((fn, rename[0]))
2147 revmatchfn = None
2136 revmatchfn = None
2148 if filematcher is not None:
2137 if filematcher is not None:
2149 revmatchfn = filematcher(ctx.rev())
2138 revmatchfn = filematcher(ctx.rev())
2150 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2139 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2151 lines = displayer.hunk.pop(rev).split('\n')
2140 lines = displayer.hunk.pop(rev).split('\n')
2152 if not lines[-1]:
2141 if not lines[-1]:
2153 del lines[-1]
2142 del lines[-1]
2154 displayer.flush(rev)
2143 displayer.flush(rev)
2155 edges = edgefn(type, char, lines, seen, rev, parents)
2144 edges = edgefn(type, char, lines, seen, rev, parents)
2156 for type, char, lines, coldata in edges:
2145 for type, char, lines, coldata in edges:
2157 graphmod.ascii(ui, state, type, char, lines, coldata)
2146 graphmod.ascii(ui, state, type, char, lines, coldata)
2158 displayer.close()
2147 displayer.close()
2159
2148
2160 def graphlog(ui, repo, *pats, **opts):
2149 def graphlog(ui, repo, *pats, **opts):
2161 # Parameters are identical to log command ones
2150 # Parameters are identical to log command ones
2162 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2151 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2163 revdag = graphmod.dagwalker(repo, revs)
2152 revdag = graphmod.dagwalker(repo, revs)
2164
2153
2165 getrenamed = None
2154 getrenamed = None
2166 if opts.get('copies'):
2155 if opts.get('copies'):
2167 endrev = None
2156 endrev = None
2168 if opts.get('rev'):
2157 if opts.get('rev'):
2169 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2158 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2170 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2159 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2171 displayer = show_changeset(ui, repo, opts, buffered=True)
2160 displayer = show_changeset(ui, repo, opts, buffered=True)
2172 showparents = [ctx.node() for ctx in repo[None].parents()]
2161 showparents = [ctx.node() for ctx in repo[None].parents()]
2173 displaygraph(ui, revdag, displayer, showparents,
2162 displaygraph(ui, revdag, displayer, showparents,
2174 graphmod.asciiedges, getrenamed, filematcher)
2163 graphmod.asciiedges, getrenamed, filematcher)
2175
2164
2176 def checkunsupportedgraphflags(pats, opts):
2165 def checkunsupportedgraphflags(pats, opts):
2177 for op in ["newest_first"]:
2166 for op in ["newest_first"]:
2178 if op in opts and opts[op]:
2167 if op in opts and opts[op]:
2179 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2168 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2180 % op.replace("_", "-"))
2169 % op.replace("_", "-"))
2181
2170
2182 def graphrevs(repo, nodes, opts):
2171 def graphrevs(repo, nodes, opts):
2183 limit = loglimit(opts)
2172 limit = loglimit(opts)
2184 nodes.reverse()
2173 nodes.reverse()
2185 if limit is not None:
2174 if limit is not None:
2186 nodes = nodes[:limit]
2175 nodes = nodes[:limit]
2187 return graphmod.nodes(repo, nodes)
2176 return graphmod.nodes(repo, nodes)
2188
2177
2189 def add(ui, repo, match, prefix, explicitonly, **opts):
2178 def add(ui, repo, match, prefix, explicitonly, **opts):
2190 join = lambda f: os.path.join(prefix, f)
2179 join = lambda f: os.path.join(prefix, f)
2191 bad = []
2180 bad = []
2192 oldbad = match.bad
2181 oldbad = match.bad
2193 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2182 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2194 names = []
2183 names = []
2195 wctx = repo[None]
2184 wctx = repo[None]
2196 cca = None
2185 cca = None
2197 abort, warn = scmutil.checkportabilityalert(ui)
2186 abort, warn = scmutil.checkportabilityalert(ui)
2198 if abort or warn:
2187 if abort or warn:
2199 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2188 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2200 for f in wctx.walk(match):
2189 for f in wctx.walk(match):
2201 exact = match.exact(f)
2190 exact = match.exact(f)
2202 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2191 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2203 if cca:
2192 if cca:
2204 cca(f)
2193 cca(f)
2205 names.append(f)
2194 names.append(f)
2206 if ui.verbose or not exact:
2195 if ui.verbose or not exact:
2207 ui.status(_('adding %s\n') % match.rel(f))
2196 ui.status(_('adding %s\n') % match.rel(f))
2208
2197
2209 for subpath in sorted(wctx.substate):
2198 for subpath in sorted(wctx.substate):
2210 sub = wctx.sub(subpath)
2199 sub = wctx.sub(subpath)
2211 try:
2200 try:
2212 submatch = matchmod.narrowmatcher(subpath, match)
2201 submatch = matchmod.narrowmatcher(subpath, match)
2213 if opts.get('subrepos'):
2202 if opts.get('subrepos'):
2214 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2203 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2215 else:
2204 else:
2216 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2205 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2217 except error.LookupError:
2206 except error.LookupError:
2218 ui.status(_("skipping missing subrepository: %s\n")
2207 ui.status(_("skipping missing subrepository: %s\n")
2219 % join(subpath))
2208 % join(subpath))
2220
2209
2221 if not opts.get('dry_run'):
2210 if not opts.get('dry_run'):
2222 rejected = wctx.add(names, prefix)
2211 rejected = wctx.add(names, prefix)
2223 bad.extend(f for f in rejected if f in match.files())
2212 bad.extend(f for f in rejected if f in match.files())
2224 return bad
2213 return bad
2225
2214
2226 def forget(ui, repo, match, prefix, explicitonly):
2215 def forget(ui, repo, match, prefix, explicitonly):
2227 join = lambda f: os.path.join(prefix, f)
2216 join = lambda f: os.path.join(prefix, f)
2228 bad = []
2217 bad = []
2229 oldbad = match.bad
2218 oldbad = match.bad
2230 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2219 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2231 wctx = repo[None]
2220 wctx = repo[None]
2232 forgot = []
2221 forgot = []
2233 s = repo.status(match=match, clean=True)
2222 s = repo.status(match=match, clean=True)
2234 forget = sorted(s[0] + s[1] + s[3] + s[6])
2223 forget = sorted(s[0] + s[1] + s[3] + s[6])
2235 if explicitonly:
2224 if explicitonly:
2236 forget = [f for f in forget if match.exact(f)]
2225 forget = [f for f in forget if match.exact(f)]
2237
2226
2238 for subpath in sorted(wctx.substate):
2227 for subpath in sorted(wctx.substate):
2239 sub = wctx.sub(subpath)
2228 sub = wctx.sub(subpath)
2240 try:
2229 try:
2241 submatch = matchmod.narrowmatcher(subpath, match)
2230 submatch = matchmod.narrowmatcher(subpath, match)
2242 subbad, subforgot = sub.forget(submatch, prefix)
2231 subbad, subforgot = sub.forget(submatch, prefix)
2243 bad.extend([subpath + '/' + f for f in subbad])
2232 bad.extend([subpath + '/' + f for f in subbad])
2244 forgot.extend([subpath + '/' + f for f in subforgot])
2233 forgot.extend([subpath + '/' + f for f in subforgot])
2245 except error.LookupError:
2234 except error.LookupError:
2246 ui.status(_("skipping missing subrepository: %s\n")
2235 ui.status(_("skipping missing subrepository: %s\n")
2247 % join(subpath))
2236 % join(subpath))
2248
2237
2249 if not explicitonly:
2238 if not explicitonly:
2250 for f in match.files():
2239 for f in match.files():
2251 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2240 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2252 if f not in forgot:
2241 if f not in forgot:
2253 if repo.wvfs.exists(f):
2242 if repo.wvfs.exists(f):
2254 # Don't complain if the exact case match wasn't given.
2243 # Don't complain if the exact case match wasn't given.
2255 # But don't do this until after checking 'forgot', so
2244 # But don't do this until after checking 'forgot', so
2256 # that subrepo files aren't normalized, and this op is
2245 # that subrepo files aren't normalized, and this op is
2257 # purely from data cached by the status walk above.
2246 # purely from data cached by the status walk above.
2258 if repo.dirstate.normalize(f) in repo.dirstate:
2247 if repo.dirstate.normalize(f) in repo.dirstate:
2259 continue
2248 continue
2260 ui.warn(_('not removing %s: '
2249 ui.warn(_('not removing %s: '
2261 'file is already untracked\n')
2250 'file is already untracked\n')
2262 % match.rel(f))
2251 % match.rel(f))
2263 bad.append(f)
2252 bad.append(f)
2264
2253
2265 for f in forget:
2254 for f in forget:
2266 if ui.verbose or not match.exact(f):
2255 if ui.verbose or not match.exact(f):
2267 ui.status(_('removing %s\n') % match.rel(f))
2256 ui.status(_('removing %s\n') % match.rel(f))
2268
2257
2269 rejected = wctx.forget(forget, prefix)
2258 rejected = wctx.forget(forget, prefix)
2270 bad.extend(f for f in rejected if f in match.files())
2259 bad.extend(f for f in rejected if f in match.files())
2271 forgot.extend(f for f in forget if f not in rejected)
2260 forgot.extend(f for f in forget if f not in rejected)
2272 return bad, forgot
2261 return bad, forgot
2273
2262
2274 def files(ui, ctx, m, fm, fmt, subrepos):
2263 def files(ui, ctx, m, fm, fmt, subrepos):
2275 rev = ctx.rev()
2264 rev = ctx.rev()
2276 ret = 1
2265 ret = 1
2277 ds = ctx.repo().dirstate
2266 ds = ctx.repo().dirstate
2278
2267
2279 for f in ctx.matches(m):
2268 for f in ctx.matches(m):
2280 if rev is None and ds[f] == 'r':
2269 if rev is None and ds[f] == 'r':
2281 continue
2270 continue
2282 fm.startitem()
2271 fm.startitem()
2283 if ui.verbose:
2272 if ui.verbose:
2284 fc = ctx[f]
2273 fc = ctx[f]
2285 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2274 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2286 fm.data(abspath=f)
2275 fm.data(abspath=f)
2287 fm.write('path', fmt, m.rel(f))
2276 fm.write('path', fmt, m.rel(f))
2288 ret = 0
2277 ret = 0
2289
2278
2290 if subrepos:
2279 if subrepos:
2291 for subpath in sorted(ctx.substate):
2280 for subpath in sorted(ctx.substate):
2292 sub = ctx.sub(subpath)
2281 sub = ctx.sub(subpath)
2293 try:
2282 try:
2294 submatch = matchmod.narrowmatcher(subpath, m)
2283 submatch = matchmod.narrowmatcher(subpath, m)
2295 if sub.printfiles(ui, submatch, fm, fmt) == 0:
2284 if sub.printfiles(ui, submatch, fm, fmt) == 0:
2296 ret = 0
2285 ret = 0
2297 except error.LookupError:
2286 except error.LookupError:
2298 ui.status(_("skipping missing subrepository: %s\n")
2287 ui.status(_("skipping missing subrepository: %s\n")
2299 % m.abs(subpath))
2288 % m.abs(subpath))
2300
2289
2301 return ret
2290 return ret
2302
2291
2303 def remove(ui, repo, m, prefix, after, force, subrepos):
2292 def remove(ui, repo, m, prefix, after, force, subrepos):
2304 join = lambda f: os.path.join(prefix, f)
2293 join = lambda f: os.path.join(prefix, f)
2305 ret = 0
2294 ret = 0
2306 s = repo.status(match=m, clean=True)
2295 s = repo.status(match=m, clean=True)
2307 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2296 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2308
2297
2309 wctx = repo[None]
2298 wctx = repo[None]
2310
2299
2311 for subpath in sorted(wctx.substate):
2300 for subpath in sorted(wctx.substate):
2312 def matchessubrepo(matcher, subpath):
2301 def matchessubrepo(matcher, subpath):
2313 if matcher.exact(subpath):
2302 if matcher.exact(subpath):
2314 return True
2303 return True
2315 for f in matcher.files():
2304 for f in matcher.files():
2316 if f.startswith(subpath):
2305 if f.startswith(subpath):
2317 return True
2306 return True
2318 return False
2307 return False
2319
2308
2320 if subrepos or matchessubrepo(m, subpath):
2309 if subrepos or matchessubrepo(m, subpath):
2321 sub = wctx.sub(subpath)
2310 sub = wctx.sub(subpath)
2322 try:
2311 try:
2323 submatch = matchmod.narrowmatcher(subpath, m)
2312 submatch = matchmod.narrowmatcher(subpath, m)
2324 if sub.removefiles(submatch, prefix, after, force, subrepos):
2313 if sub.removefiles(submatch, prefix, after, force, subrepos):
2325 ret = 1
2314 ret = 1
2326 except error.LookupError:
2315 except error.LookupError:
2327 ui.status(_("skipping missing subrepository: %s\n")
2316 ui.status(_("skipping missing subrepository: %s\n")
2328 % join(subpath))
2317 % join(subpath))
2329
2318
2330 # warn about failure to delete explicit files/dirs
2319 # warn about failure to delete explicit files/dirs
2331 deleteddirs = util.dirs(deleted)
2320 deleteddirs = util.dirs(deleted)
2332 for f in m.files():
2321 for f in m.files():
2333 def insubrepo():
2322 def insubrepo():
2334 for subpath in wctx.substate:
2323 for subpath in wctx.substate:
2335 if f.startswith(subpath):
2324 if f.startswith(subpath):
2336 return True
2325 return True
2337 return False
2326 return False
2338
2327
2339 isdir = f in deleteddirs or f in wctx.dirs()
2328 isdir = f in deleteddirs or f in wctx.dirs()
2340 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2329 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2341 continue
2330 continue
2342
2331
2343 if repo.wvfs.exists(f):
2332 if repo.wvfs.exists(f):
2344 if repo.wvfs.isdir(f):
2333 if repo.wvfs.isdir(f):
2345 ui.warn(_('not removing %s: no tracked files\n')
2334 ui.warn(_('not removing %s: no tracked files\n')
2346 % m.rel(f))
2335 % m.rel(f))
2347 else:
2336 else:
2348 ui.warn(_('not removing %s: file is untracked\n')
2337 ui.warn(_('not removing %s: file is untracked\n')
2349 % m.rel(f))
2338 % m.rel(f))
2350 # missing files will generate a warning elsewhere
2339 # missing files will generate a warning elsewhere
2351 ret = 1
2340 ret = 1
2352
2341
2353 if force:
2342 if force:
2354 list = modified + deleted + clean + added
2343 list = modified + deleted + clean + added
2355 elif after:
2344 elif after:
2356 list = deleted
2345 list = deleted
2357 for f in modified + added + clean:
2346 for f in modified + added + clean:
2358 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2347 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2359 ret = 1
2348 ret = 1
2360 else:
2349 else:
2361 list = deleted + clean
2350 list = deleted + clean
2362 for f in modified:
2351 for f in modified:
2363 ui.warn(_('not removing %s: file is modified (use -f'
2352 ui.warn(_('not removing %s: file is modified (use -f'
2364 ' to force removal)\n') % m.rel(f))
2353 ' to force removal)\n') % m.rel(f))
2365 ret = 1
2354 ret = 1
2366 for f in added:
2355 for f in added:
2367 ui.warn(_('not removing %s: file has been marked for add'
2356 ui.warn(_('not removing %s: file has been marked for add'
2368 ' (use forget to undo)\n') % m.rel(f))
2357 ' (use forget to undo)\n') % m.rel(f))
2369 ret = 1
2358 ret = 1
2370
2359
2371 for f in sorted(list):
2360 for f in sorted(list):
2372 if ui.verbose or not m.exact(f):
2361 if ui.verbose or not m.exact(f):
2373 ui.status(_('removing %s\n') % m.rel(f))
2362 ui.status(_('removing %s\n') % m.rel(f))
2374
2363
2375 wlock = repo.wlock()
2364 wlock = repo.wlock()
2376 try:
2365 try:
2377 if not after:
2366 if not after:
2378 for f in list:
2367 for f in list:
2379 if f in added:
2368 if f in added:
2380 continue # we never unlink added files on remove
2369 continue # we never unlink added files on remove
2381 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2370 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2382 repo[None].forget(list)
2371 repo[None].forget(list)
2383 finally:
2372 finally:
2384 wlock.release()
2373 wlock.release()
2385
2374
2386 return ret
2375 return ret
2387
2376
2388 def cat(ui, repo, ctx, matcher, prefix, **opts):
2377 def cat(ui, repo, ctx, matcher, prefix, **opts):
2389 err = 1
2378 err = 1
2390
2379
2391 def write(path):
2380 def write(path):
2392 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2381 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2393 pathname=os.path.join(prefix, path))
2382 pathname=os.path.join(prefix, path))
2394 data = ctx[path].data()
2383 data = ctx[path].data()
2395 if opts.get('decode'):
2384 if opts.get('decode'):
2396 data = repo.wwritedata(path, data)
2385 data = repo.wwritedata(path, data)
2397 fp.write(data)
2386 fp.write(data)
2398 fp.close()
2387 fp.close()
2399
2388
2400 # Automation often uses hg cat on single files, so special case it
2389 # Automation often uses hg cat on single files, so special case it
2401 # for performance to avoid the cost of parsing the manifest.
2390 # for performance to avoid the cost of parsing the manifest.
2402 if len(matcher.files()) == 1 and not matcher.anypats():
2391 if len(matcher.files()) == 1 and not matcher.anypats():
2403 file = matcher.files()[0]
2392 file = matcher.files()[0]
2404 mf = repo.manifest
2393 mf = repo.manifest
2405 mfnode = ctx.manifestnode()
2394 mfnode = ctx.manifestnode()
2406 if mfnode and mf.find(mfnode, file)[0]:
2395 if mfnode and mf.find(mfnode, file)[0]:
2407 write(file)
2396 write(file)
2408 return 0
2397 return 0
2409
2398
2410 # Don't warn about "missing" files that are really in subrepos
2399 # Don't warn about "missing" files that are really in subrepos
2411 bad = matcher.bad
2400 bad = matcher.bad
2412
2401
2413 def badfn(path, msg):
2402 def badfn(path, msg):
2414 for subpath in ctx.substate:
2403 for subpath in ctx.substate:
2415 if path.startswith(subpath):
2404 if path.startswith(subpath):
2416 return
2405 return
2417 bad(path, msg)
2406 bad(path, msg)
2418
2407
2419 matcher.bad = badfn
2408 matcher.bad = badfn
2420
2409
2421 for abs in ctx.walk(matcher):
2410 for abs in ctx.walk(matcher):
2422 write(abs)
2411 write(abs)
2423 err = 0
2412 err = 0
2424
2413
2425 matcher.bad = bad
2414 matcher.bad = bad
2426
2415
2427 for subpath in sorted(ctx.substate):
2416 for subpath in sorted(ctx.substate):
2428 sub = ctx.sub(subpath)
2417 sub = ctx.sub(subpath)
2429 try:
2418 try:
2430 submatch = matchmod.narrowmatcher(subpath, matcher)
2419 submatch = matchmod.narrowmatcher(subpath, matcher)
2431
2420
2432 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2421 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2433 **opts):
2422 **opts):
2434 err = 0
2423 err = 0
2435 except error.RepoLookupError:
2424 except error.RepoLookupError:
2436 ui.status(_("skipping missing subrepository: %s\n")
2425 ui.status(_("skipping missing subrepository: %s\n")
2437 % os.path.join(prefix, subpath))
2426 % os.path.join(prefix, subpath))
2438
2427
2439 return err
2428 return err
2440
2429
2441 def commit(ui, repo, commitfunc, pats, opts):
2430 def commit(ui, repo, commitfunc, pats, opts):
2442 '''commit the specified files or all outstanding changes'''
2431 '''commit the specified files or all outstanding changes'''
2443 date = opts.get('date')
2432 date = opts.get('date')
2444 if date:
2433 if date:
2445 opts['date'] = util.parsedate(date)
2434 opts['date'] = util.parsedate(date)
2446 message = logmessage(ui, opts)
2435 message = logmessage(ui, opts)
2447 matcher = scmutil.match(repo[None], pats, opts)
2436 matcher = scmutil.match(repo[None], pats, opts)
2448
2437
2449 # extract addremove carefully -- this function can be called from a command
2438 # extract addremove carefully -- this function can be called from a command
2450 # that doesn't support addremove
2439 # that doesn't support addremove
2451 if opts.get('addremove'):
2440 if opts.get('addremove'):
2452 if scmutil.addremove(repo, matcher, "", opts) != 0:
2441 if scmutil.addremove(repo, matcher, "", opts) != 0:
2453 raise util.Abort(
2442 raise util.Abort(
2454 _("failed to mark all new/missing files as added/removed"))
2443 _("failed to mark all new/missing files as added/removed"))
2455
2444
2456 return commitfunc(ui, repo, message, matcher, opts)
2445 return commitfunc(ui, repo, message, matcher, opts)
2457
2446
2458 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2447 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2459 # amend will reuse the existing user if not specified, but the obsolete
2448 # amend will reuse the existing user if not specified, but the obsolete
2460 # marker creation requires that the current user's name is specified.
2449 # marker creation requires that the current user's name is specified.
2461 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2450 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2462 ui.username() # raise exception if username not set
2451 ui.username() # raise exception if username not set
2463
2452
2464 ui.note(_('amending changeset %s\n') % old)
2453 ui.note(_('amending changeset %s\n') % old)
2465 base = old.p1()
2454 base = old.p1()
2466
2455
2467 wlock = lock = newid = None
2456 wlock = lock = newid = None
2468 try:
2457 try:
2469 wlock = repo.wlock()
2458 wlock = repo.wlock()
2470 lock = repo.lock()
2459 lock = repo.lock()
2471 tr = repo.transaction('amend')
2460 tr = repo.transaction('amend')
2472 try:
2461 try:
2473 # See if we got a message from -m or -l, if not, open the editor
2462 # See if we got a message from -m or -l, if not, open the editor
2474 # with the message of the changeset to amend
2463 # with the message of the changeset to amend
2475 message = logmessage(ui, opts)
2464 message = logmessage(ui, opts)
2476 # ensure logfile does not conflict with later enforcement of the
2465 # ensure logfile does not conflict with later enforcement of the
2477 # message. potential logfile content has been processed by
2466 # message. potential logfile content has been processed by
2478 # `logmessage` anyway.
2467 # `logmessage` anyway.
2479 opts.pop('logfile')
2468 opts.pop('logfile')
2480 # First, do a regular commit to record all changes in the working
2469 # First, do a regular commit to record all changes in the working
2481 # directory (if there are any)
2470 # directory (if there are any)
2482 ui.callhooks = False
2471 ui.callhooks = False
2483 currentbookmark = repo._bookmarkcurrent
2472 currentbookmark = repo._bookmarkcurrent
2484 try:
2473 try:
2485 repo._bookmarkcurrent = None
2474 repo._bookmarkcurrent = None
2486 opts['message'] = 'temporary amend commit for %s' % old
2475 opts['message'] = 'temporary amend commit for %s' % old
2487 node = commit(ui, repo, commitfunc, pats, opts)
2476 node = commit(ui, repo, commitfunc, pats, opts)
2488 finally:
2477 finally:
2489 repo._bookmarkcurrent = currentbookmark
2478 repo._bookmarkcurrent = currentbookmark
2490 ui.callhooks = True
2479 ui.callhooks = True
2491 ctx = repo[node]
2480 ctx = repo[node]
2492
2481
2493 # Participating changesets:
2482 # Participating changesets:
2494 #
2483 #
2495 # node/ctx o - new (intermediate) commit that contains changes
2484 # node/ctx o - new (intermediate) commit that contains changes
2496 # | from working dir to go into amending commit
2485 # | from working dir to go into amending commit
2497 # | (or a workingctx if there were no changes)
2486 # | (or a workingctx if there were no changes)
2498 # |
2487 # |
2499 # old o - changeset to amend
2488 # old o - changeset to amend
2500 # |
2489 # |
2501 # base o - parent of amending changeset
2490 # base o - parent of amending changeset
2502
2491
2503 # Update extra dict from amended commit (e.g. to preserve graft
2492 # Update extra dict from amended commit (e.g. to preserve graft
2504 # source)
2493 # source)
2505 extra.update(old.extra())
2494 extra.update(old.extra())
2506
2495
2507 # Also update it from the intermediate commit or from the wctx
2496 # Also update it from the intermediate commit or from the wctx
2508 extra.update(ctx.extra())
2497 extra.update(ctx.extra())
2509
2498
2510 if len(old.parents()) > 1:
2499 if len(old.parents()) > 1:
2511 # ctx.files() isn't reliable for merges, so fall back to the
2500 # ctx.files() isn't reliable for merges, so fall back to the
2512 # slower repo.status() method
2501 # slower repo.status() method
2513 files = set([fn for st in repo.status(base, old)[:3]
2502 files = set([fn for st in repo.status(base, old)[:3]
2514 for fn in st])
2503 for fn in st])
2515 else:
2504 else:
2516 files = set(old.files())
2505 files = set(old.files())
2517
2506
2518 # Second, we use either the commit we just did, or if there were no
2507 # Second, we use either the commit we just did, or if there were no
2519 # changes the parent of the working directory as the version of the
2508 # changes the parent of the working directory as the version of the
2520 # files in the final amend commit
2509 # files in the final amend commit
2521 if node:
2510 if node:
2522 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2511 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2523
2512
2524 user = ctx.user()
2513 user = ctx.user()
2525 date = ctx.date()
2514 date = ctx.date()
2526 # Recompute copies (avoid recording a -> b -> a)
2515 # Recompute copies (avoid recording a -> b -> a)
2527 copied = copies.pathcopies(base, ctx)
2516 copied = copies.pathcopies(base, ctx)
2528 if old.p2:
2517 if old.p2:
2529 copied.update(copies.pathcopies(old.p2(), ctx))
2518 copied.update(copies.pathcopies(old.p2(), ctx))
2530
2519
2531 # Prune files which were reverted by the updates: if old
2520 # Prune files which were reverted by the updates: if old
2532 # introduced file X and our intermediate commit, node,
2521 # introduced file X and our intermediate commit, node,
2533 # renamed that file, then those two files are the same and
2522 # renamed that file, then those two files are the same and
2534 # we can discard X from our list of files. Likewise if X
2523 # we can discard X from our list of files. Likewise if X
2535 # was deleted, it's no longer relevant
2524 # was deleted, it's no longer relevant
2536 files.update(ctx.files())
2525 files.update(ctx.files())
2537
2526
2538 def samefile(f):
2527 def samefile(f):
2539 if f in ctx.manifest():
2528 if f in ctx.manifest():
2540 a = ctx.filectx(f)
2529 a = ctx.filectx(f)
2541 if f in base.manifest():
2530 if f in base.manifest():
2542 b = base.filectx(f)
2531 b = base.filectx(f)
2543 return (not a.cmp(b)
2532 return (not a.cmp(b)
2544 and a.flags() == b.flags())
2533 and a.flags() == b.flags())
2545 else:
2534 else:
2546 return False
2535 return False
2547 else:
2536 else:
2548 return f not in base.manifest()
2537 return f not in base.manifest()
2549 files = [f for f in files if not samefile(f)]
2538 files = [f for f in files if not samefile(f)]
2550
2539
2551 def filectxfn(repo, ctx_, path):
2540 def filectxfn(repo, ctx_, path):
2552 try:
2541 try:
2553 fctx = ctx[path]
2542 fctx = ctx[path]
2554 flags = fctx.flags()
2543 flags = fctx.flags()
2555 mctx = context.memfilectx(repo,
2544 mctx = context.memfilectx(repo,
2556 fctx.path(), fctx.data(),
2545 fctx.path(), fctx.data(),
2557 islink='l' in flags,
2546 islink='l' in flags,
2558 isexec='x' in flags,
2547 isexec='x' in flags,
2559 copied=copied.get(path))
2548 copied=copied.get(path))
2560 return mctx
2549 return mctx
2561 except KeyError:
2550 except KeyError:
2562 return None
2551 return None
2563 else:
2552 else:
2564 ui.note(_('copying changeset %s to %s\n') % (old, base))
2553 ui.note(_('copying changeset %s to %s\n') % (old, base))
2565
2554
2566 # Use version of files as in the old cset
2555 # Use version of files as in the old cset
2567 def filectxfn(repo, ctx_, path):
2556 def filectxfn(repo, ctx_, path):
2568 try:
2557 try:
2569 return old.filectx(path)
2558 return old.filectx(path)
2570 except KeyError:
2559 except KeyError:
2571 return None
2560 return None
2572
2561
2573 user = opts.get('user') or old.user()
2562 user = opts.get('user') or old.user()
2574 date = opts.get('date') or old.date()
2563 date = opts.get('date') or old.date()
2575 editform = mergeeditform(old, 'commit.amend')
2564 editform = mergeeditform(old, 'commit.amend')
2576 editor = getcommiteditor(editform=editform, **opts)
2565 editor = getcommiteditor(editform=editform, **opts)
2577 if not message:
2566 if not message:
2578 editor = getcommiteditor(edit=True, editform=editform)
2567 editor = getcommiteditor(edit=True, editform=editform)
2579 message = old.description()
2568 message = old.description()
2580
2569
2581 pureextra = extra.copy()
2570 pureextra = extra.copy()
2582 extra['amend_source'] = old.hex()
2571 extra['amend_source'] = old.hex()
2583
2572
2584 new = context.memctx(repo,
2573 new = context.memctx(repo,
2585 parents=[base.node(), old.p2().node()],
2574 parents=[base.node(), old.p2().node()],
2586 text=message,
2575 text=message,
2587 files=files,
2576 files=files,
2588 filectxfn=filectxfn,
2577 filectxfn=filectxfn,
2589 user=user,
2578 user=user,
2590 date=date,
2579 date=date,
2591 extra=extra,
2580 extra=extra,
2592 editor=editor)
2581 editor=editor)
2593
2582
2594 newdesc = changelog.stripdesc(new.description())
2583 newdesc = changelog.stripdesc(new.description())
2595 if ((not node)
2584 if ((not node)
2596 and newdesc == old.description()
2585 and newdesc == old.description()
2597 and user == old.user()
2586 and user == old.user()
2598 and date == old.date()
2587 and date == old.date()
2599 and pureextra == old.extra()):
2588 and pureextra == old.extra()):
2600 # nothing changed. continuing here would create a new node
2589 # nothing changed. continuing here would create a new node
2601 # anyway because of the amend_source noise.
2590 # anyway because of the amend_source noise.
2602 #
2591 #
2603 # This not what we expect from amend.
2592 # This not what we expect from amend.
2604 return old.node()
2593 return old.node()
2605
2594
2606 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2595 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2607 try:
2596 try:
2608 if opts.get('secret'):
2597 if opts.get('secret'):
2609 commitphase = 'secret'
2598 commitphase = 'secret'
2610 else:
2599 else:
2611 commitphase = old.phase()
2600 commitphase = old.phase()
2612 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2601 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2613 newid = repo.commitctx(new)
2602 newid = repo.commitctx(new)
2614 finally:
2603 finally:
2615 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2604 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2616 if newid != old.node():
2605 if newid != old.node():
2617 # Reroute the working copy parent to the new changeset
2606 # Reroute the working copy parent to the new changeset
2618 repo.setparents(newid, nullid)
2607 repo.setparents(newid, nullid)
2619
2608
2620 # Move bookmarks from old parent to amend commit
2609 # Move bookmarks from old parent to amend commit
2621 bms = repo.nodebookmarks(old.node())
2610 bms = repo.nodebookmarks(old.node())
2622 if bms:
2611 if bms:
2623 marks = repo._bookmarks
2612 marks = repo._bookmarks
2624 for bm in bms:
2613 for bm in bms:
2625 marks[bm] = newid
2614 marks[bm] = newid
2626 marks.write()
2615 marks.write()
2627 #commit the whole amend process
2616 #commit the whole amend process
2628 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2617 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2629 if createmarkers and newid != old.node():
2618 if createmarkers and newid != old.node():
2630 # mark the new changeset as successor of the rewritten one
2619 # mark the new changeset as successor of the rewritten one
2631 new = repo[newid]
2620 new = repo[newid]
2632 obs = [(old, (new,))]
2621 obs = [(old, (new,))]
2633 if node:
2622 if node:
2634 obs.append((ctx, ()))
2623 obs.append((ctx, ()))
2635
2624
2636 obsolete.createmarkers(repo, obs)
2625 obsolete.createmarkers(repo, obs)
2637 tr.close()
2626 tr.close()
2638 finally:
2627 finally:
2639 tr.release()
2628 tr.release()
2640 if not createmarkers and newid != old.node():
2629 if not createmarkers and newid != old.node():
2641 # Strip the intermediate commit (if there was one) and the amended
2630 # Strip the intermediate commit (if there was one) and the amended
2642 # commit
2631 # commit
2643 if node:
2632 if node:
2644 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2633 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2645 ui.note(_('stripping amended changeset %s\n') % old)
2634 ui.note(_('stripping amended changeset %s\n') % old)
2646 repair.strip(ui, repo, old.node(), topic='amend-backup')
2635 repair.strip(ui, repo, old.node(), topic='amend-backup')
2647 finally:
2636 finally:
2648 if newid is None:
2637 if newid is None:
2649 repo.dirstate.invalidate()
2638 repo.dirstate.invalidate()
2650 lockmod.release(lock, wlock)
2639 lockmod.release(lock, wlock)
2651 return newid
2640 return newid
2652
2641
2653 def commiteditor(repo, ctx, subs, editform=''):
2642 def commiteditor(repo, ctx, subs, editform=''):
2654 if ctx.description():
2643 if ctx.description():
2655 return ctx.description()
2644 return ctx.description()
2656 return commitforceeditor(repo, ctx, subs, editform=editform)
2645 return commitforceeditor(repo, ctx, subs, editform=editform)
2657
2646
2658 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2647 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2659 editform=''):
2648 editform=''):
2660 if not extramsg:
2649 if not extramsg:
2661 extramsg = _("Leave message empty to abort commit.")
2650 extramsg = _("Leave message empty to abort commit.")
2662
2651
2663 forms = [e for e in editform.split('.') if e]
2652 forms = [e for e in editform.split('.') if e]
2664 forms.insert(0, 'changeset')
2653 forms.insert(0, 'changeset')
2665 while forms:
2654 while forms:
2666 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2655 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2667 if tmpl:
2656 if tmpl:
2668 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2657 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2669 break
2658 break
2670 forms.pop()
2659 forms.pop()
2671 else:
2660 else:
2672 committext = buildcommittext(repo, ctx, subs, extramsg)
2661 committext = buildcommittext(repo, ctx, subs, extramsg)
2673
2662
2674 # run editor in the repository root
2663 # run editor in the repository root
2675 olddir = os.getcwd()
2664 olddir = os.getcwd()
2676 os.chdir(repo.root)
2665 os.chdir(repo.root)
2677 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2666 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2678 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2667 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2679 os.chdir(olddir)
2668 os.chdir(olddir)
2680
2669
2681 if finishdesc:
2670 if finishdesc:
2682 text = finishdesc(text)
2671 text = finishdesc(text)
2683 if not text.strip():
2672 if not text.strip():
2684 raise util.Abort(_("empty commit message"))
2673 raise util.Abort(_("empty commit message"))
2685
2674
2686 return text
2675 return text
2687
2676
2688 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2677 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2689 ui = repo.ui
2678 ui = repo.ui
2690 tmpl, mapfile = gettemplate(ui, tmpl, None)
2679 tmpl, mapfile = gettemplate(ui, tmpl, None)
2691
2680
2692 try:
2681 try:
2693 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2682 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2694 except SyntaxError, inst:
2683 except SyntaxError, inst:
2695 raise util.Abort(inst.args[0])
2684 raise util.Abort(inst.args[0])
2696
2685
2697 for k, v in repo.ui.configitems('committemplate'):
2686 for k, v in repo.ui.configitems('committemplate'):
2698 if k != 'changeset':
2687 if k != 'changeset':
2699 t.t.cache[k] = v
2688 t.t.cache[k] = v
2700
2689
2701 if not extramsg:
2690 if not extramsg:
2702 extramsg = '' # ensure that extramsg is string
2691 extramsg = '' # ensure that extramsg is string
2703
2692
2704 ui.pushbuffer()
2693 ui.pushbuffer()
2705 t.show(ctx, extramsg=extramsg)
2694 t.show(ctx, extramsg=extramsg)
2706 return ui.popbuffer()
2695 return ui.popbuffer()
2707
2696
2708 def buildcommittext(repo, ctx, subs, extramsg):
2697 def buildcommittext(repo, ctx, subs, extramsg):
2709 edittext = []
2698 edittext = []
2710 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2699 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2711 if ctx.description():
2700 if ctx.description():
2712 edittext.append(ctx.description())
2701 edittext.append(ctx.description())
2713 edittext.append("")
2702 edittext.append("")
2714 edittext.append("") # Empty line between message and comments.
2703 edittext.append("") # Empty line between message and comments.
2715 edittext.append(_("HG: Enter commit message."
2704 edittext.append(_("HG: Enter commit message."
2716 " Lines beginning with 'HG:' are removed."))
2705 " Lines beginning with 'HG:' are removed."))
2717 edittext.append("HG: %s" % extramsg)
2706 edittext.append("HG: %s" % extramsg)
2718 edittext.append("HG: --")
2707 edittext.append("HG: --")
2719 edittext.append(_("HG: user: %s") % ctx.user())
2708 edittext.append(_("HG: user: %s") % ctx.user())
2720 if ctx.p2():
2709 if ctx.p2():
2721 edittext.append(_("HG: branch merge"))
2710 edittext.append(_("HG: branch merge"))
2722 if ctx.branch():
2711 if ctx.branch():
2723 edittext.append(_("HG: branch '%s'") % ctx.branch())
2712 edittext.append(_("HG: branch '%s'") % ctx.branch())
2724 if bookmarks.iscurrent(repo):
2713 if bookmarks.iscurrent(repo):
2725 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2714 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2726 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2715 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2727 edittext.extend([_("HG: added %s") % f for f in added])
2716 edittext.extend([_("HG: added %s") % f for f in added])
2728 edittext.extend([_("HG: changed %s") % f for f in modified])
2717 edittext.extend([_("HG: changed %s") % f for f in modified])
2729 edittext.extend([_("HG: removed %s") % f for f in removed])
2718 edittext.extend([_("HG: removed %s") % f for f in removed])
2730 if not added and not modified and not removed:
2719 if not added and not modified and not removed:
2731 edittext.append(_("HG: no files changed"))
2720 edittext.append(_("HG: no files changed"))
2732 edittext.append("")
2721 edittext.append("")
2733
2722
2734 return "\n".join(edittext)
2723 return "\n".join(edittext)
2735
2724
2736 def commitstatus(repo, node, branch, bheads=None, opts={}):
2725 def commitstatus(repo, node, branch, bheads=None, opts={}):
2737 ctx = repo[node]
2726 ctx = repo[node]
2738 parents = ctx.parents()
2727 parents = ctx.parents()
2739
2728
2740 if (not opts.get('amend') and bheads and node not in bheads and not
2729 if (not opts.get('amend') and bheads and node not in bheads and not
2741 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2730 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2742 repo.ui.status(_('created new head\n'))
2731 repo.ui.status(_('created new head\n'))
2743 # The message is not printed for initial roots. For the other
2732 # The message is not printed for initial roots. For the other
2744 # changesets, it is printed in the following situations:
2733 # changesets, it is printed in the following situations:
2745 #
2734 #
2746 # Par column: for the 2 parents with ...
2735 # Par column: for the 2 parents with ...
2747 # N: null or no parent
2736 # N: null or no parent
2748 # B: parent is on another named branch
2737 # B: parent is on another named branch
2749 # C: parent is a regular non head changeset
2738 # C: parent is a regular non head changeset
2750 # H: parent was a branch head of the current branch
2739 # H: parent was a branch head of the current branch
2751 # Msg column: whether we print "created new head" message
2740 # Msg column: whether we print "created new head" message
2752 # In the following, it is assumed that there already exists some
2741 # In the following, it is assumed that there already exists some
2753 # initial branch heads of the current branch, otherwise nothing is
2742 # initial branch heads of the current branch, otherwise nothing is
2754 # printed anyway.
2743 # printed anyway.
2755 #
2744 #
2756 # Par Msg Comment
2745 # Par Msg Comment
2757 # N N y additional topo root
2746 # N N y additional topo root
2758 #
2747 #
2759 # B N y additional branch root
2748 # B N y additional branch root
2760 # C N y additional topo head
2749 # C N y additional topo head
2761 # H N n usual case
2750 # H N n usual case
2762 #
2751 #
2763 # B B y weird additional branch root
2752 # B B y weird additional branch root
2764 # C B y branch merge
2753 # C B y branch merge
2765 # H B n merge with named branch
2754 # H B n merge with named branch
2766 #
2755 #
2767 # C C y additional head from merge
2756 # C C y additional head from merge
2768 # C H n merge with a head
2757 # C H n merge with a head
2769 #
2758 #
2770 # H H n head merge: head count decreases
2759 # H H n head merge: head count decreases
2771
2760
2772 if not opts.get('close_branch'):
2761 if not opts.get('close_branch'):
2773 for r in parents:
2762 for r in parents:
2774 if r.closesbranch() and r.branch() == branch:
2763 if r.closesbranch() and r.branch() == branch:
2775 repo.ui.status(_('reopening closed branch head %d\n') % r)
2764 repo.ui.status(_('reopening closed branch head %d\n') % r)
2776
2765
2777 if repo.ui.debugflag:
2766 if repo.ui.debugflag:
2778 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2767 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2779 elif repo.ui.verbose:
2768 elif repo.ui.verbose:
2780 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2769 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2781
2770
2782 def revert(ui, repo, ctx, parents, *pats, **opts):
2771 def revert(ui, repo, ctx, parents, *pats, **opts):
2783 parent, p2 = parents
2772 parent, p2 = parents
2784 node = ctx.node()
2773 node = ctx.node()
2785
2774
2786 mf = ctx.manifest()
2775 mf = ctx.manifest()
2787 if node == p2:
2776 if node == p2:
2788 parent = p2
2777 parent = p2
2789 if node == parent:
2778 if node == parent:
2790 pmf = mf
2779 pmf = mf
2791 else:
2780 else:
2792 pmf = None
2781 pmf = None
2793
2782
2794 # need all matching names in dirstate and manifest of target rev,
2783 # need all matching names in dirstate and manifest of target rev,
2795 # so have to walk both. do not print errors if files exist in one
2784 # so have to walk both. do not print errors if files exist in one
2796 # but not other. in both cases, filesets should be evaluated against
2785 # but not other. in both cases, filesets should be evaluated against
2797 # workingctx to get consistent result (issue4497). this means 'set:**'
2786 # workingctx to get consistent result (issue4497). this means 'set:**'
2798 # cannot be used to select missing files from target rev.
2787 # cannot be used to select missing files from target rev.
2799
2788
2800 # `names` is a mapping for all elements in working copy and target revision
2789 # `names` is a mapping for all elements in working copy and target revision
2801 # The mapping is in the form:
2790 # The mapping is in the form:
2802 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2791 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2803 names = {}
2792 names = {}
2804
2793
2805 wlock = repo.wlock()
2794 wlock = repo.wlock()
2806 try:
2795 try:
2807 ## filling of the `names` mapping
2796 ## filling of the `names` mapping
2808 # walk dirstate to fill `names`
2797 # walk dirstate to fill `names`
2809
2798
2810 interactive = opts.get('interactive', False)
2799 interactive = opts.get('interactive', False)
2811 wctx = repo[None]
2800 wctx = repo[None]
2812 m = scmutil.match(wctx, pats, opts)
2801 m = scmutil.match(wctx, pats, opts)
2813
2802
2814 # we'll need this later
2803 # we'll need this later
2815 targetsubs = sorted(s for s in wctx.substate if m(s))
2804 targetsubs = sorted(s for s in wctx.substate if m(s))
2816
2805
2817 if not m.always():
2806 if not m.always():
2818 m.bad = lambda x, y: False
2807 m.bad = lambda x, y: False
2819 for abs in repo.walk(m):
2808 for abs in repo.walk(m):
2820 names[abs] = m.rel(abs), m.exact(abs)
2809 names[abs] = m.rel(abs), m.exact(abs)
2821
2810
2822 # walk target manifest to fill `names`
2811 # walk target manifest to fill `names`
2823
2812
2824 def badfn(path, msg):
2813 def badfn(path, msg):
2825 if path in names:
2814 if path in names:
2826 return
2815 return
2827 if path in ctx.substate:
2816 if path in ctx.substate:
2828 return
2817 return
2829 path_ = path + '/'
2818 path_ = path + '/'
2830 for f in names:
2819 for f in names:
2831 if f.startswith(path_):
2820 if f.startswith(path_):
2832 return
2821 return
2833 ui.warn("%s: %s\n" % (m.rel(path), msg))
2822 ui.warn("%s: %s\n" % (m.rel(path), msg))
2834
2823
2835 m.bad = badfn
2824 m.bad = badfn
2836 for abs in ctx.walk(m):
2825 for abs in ctx.walk(m):
2837 if abs not in names:
2826 if abs not in names:
2838 names[abs] = m.rel(abs), m.exact(abs)
2827 names[abs] = m.rel(abs), m.exact(abs)
2839
2828
2840 # Find status of all file in `names`.
2829 # Find status of all file in `names`.
2841 m = scmutil.matchfiles(repo, names)
2830 m = scmutil.matchfiles(repo, names)
2842
2831
2843 changes = repo.status(node1=node, match=m,
2832 changes = repo.status(node1=node, match=m,
2844 unknown=True, ignored=True, clean=True)
2833 unknown=True, ignored=True, clean=True)
2845 else:
2834 else:
2846 changes = repo.status(node1=node, match=m)
2835 changes = repo.status(node1=node, match=m)
2847 for kind in changes:
2836 for kind in changes:
2848 for abs in kind:
2837 for abs in kind:
2849 names[abs] = m.rel(abs), m.exact(abs)
2838 names[abs] = m.rel(abs), m.exact(abs)
2850
2839
2851 m = scmutil.matchfiles(repo, names)
2840 m = scmutil.matchfiles(repo, names)
2852
2841
2853 modified = set(changes.modified)
2842 modified = set(changes.modified)
2854 added = set(changes.added)
2843 added = set(changes.added)
2855 removed = set(changes.removed)
2844 removed = set(changes.removed)
2856 _deleted = set(changes.deleted)
2845 _deleted = set(changes.deleted)
2857 unknown = set(changes.unknown)
2846 unknown = set(changes.unknown)
2858 unknown.update(changes.ignored)
2847 unknown.update(changes.ignored)
2859 clean = set(changes.clean)
2848 clean = set(changes.clean)
2860 modadded = set()
2849 modadded = set()
2861
2850
2862 # split between files known in target manifest and the others
2851 # split between files known in target manifest and the others
2863 smf = set(mf)
2852 smf = set(mf)
2864
2853
2865 # determine the exact nature of the deleted changesets
2854 # determine the exact nature of the deleted changesets
2866 deladded = _deleted - smf
2855 deladded = _deleted - smf
2867 deleted = _deleted - deladded
2856 deleted = _deleted - deladded
2868
2857
2869 # We need to account for the state of the file in the dirstate,
2858 # We need to account for the state of the file in the dirstate,
2870 # even when we revert against something else than parent. This will
2859 # even when we revert against something else than parent. This will
2871 # slightly alter the behavior of revert (doing back up or not, delete
2860 # slightly alter the behavior of revert (doing back up or not, delete
2872 # or just forget etc).
2861 # or just forget etc).
2873 if parent == node:
2862 if parent == node:
2874 dsmodified = modified
2863 dsmodified = modified
2875 dsadded = added
2864 dsadded = added
2876 dsremoved = removed
2865 dsremoved = removed
2877 # store all local modifications, useful later for rename detection
2866 # store all local modifications, useful later for rename detection
2878 localchanges = dsmodified | dsadded
2867 localchanges = dsmodified | dsadded
2879 modified, added, removed = set(), set(), set()
2868 modified, added, removed = set(), set(), set()
2880 else:
2869 else:
2881 changes = repo.status(node1=parent, match=m)
2870 changes = repo.status(node1=parent, match=m)
2882 dsmodified = set(changes.modified)
2871 dsmodified = set(changes.modified)
2883 dsadded = set(changes.added)
2872 dsadded = set(changes.added)
2884 dsremoved = set(changes.removed)
2873 dsremoved = set(changes.removed)
2885 # store all local modifications, useful later for rename detection
2874 # store all local modifications, useful later for rename detection
2886 localchanges = dsmodified | dsadded
2875 localchanges = dsmodified | dsadded
2887
2876
2888 # only take into account for removes between wc and target
2877 # only take into account for removes between wc and target
2889 clean |= dsremoved - removed
2878 clean |= dsremoved - removed
2890 dsremoved &= removed
2879 dsremoved &= removed
2891 # distinct between dirstate remove and other
2880 # distinct between dirstate remove and other
2892 removed -= dsremoved
2881 removed -= dsremoved
2893
2882
2894 modadded = added & dsmodified
2883 modadded = added & dsmodified
2895 added -= modadded
2884 added -= modadded
2896
2885
2897 # tell newly modified apart.
2886 # tell newly modified apart.
2898 dsmodified &= modified
2887 dsmodified &= modified
2899 dsmodified |= modified & dsadded # dirstate added may needs backup
2888 dsmodified |= modified & dsadded # dirstate added may needs backup
2900 modified -= dsmodified
2889 modified -= dsmodified
2901
2890
2902 # We need to wait for some post-processing to update this set
2891 # We need to wait for some post-processing to update this set
2903 # before making the distinction. The dirstate will be used for
2892 # before making the distinction. The dirstate will be used for
2904 # that purpose.
2893 # that purpose.
2905 dsadded = added
2894 dsadded = added
2906
2895
2907 # in case of merge, files that are actually added can be reported as
2896 # in case of merge, files that are actually added can be reported as
2908 # modified, we need to post process the result
2897 # modified, we need to post process the result
2909 if p2 != nullid:
2898 if p2 != nullid:
2910 if pmf is None:
2899 if pmf is None:
2911 # only need parent manifest in the merge case,
2900 # only need parent manifest in the merge case,
2912 # so do not read by default
2901 # so do not read by default
2913 pmf = repo[parent].manifest()
2902 pmf = repo[parent].manifest()
2914 mergeadd = dsmodified - set(pmf)
2903 mergeadd = dsmodified - set(pmf)
2915 dsadded |= mergeadd
2904 dsadded |= mergeadd
2916 dsmodified -= mergeadd
2905 dsmodified -= mergeadd
2917
2906
2918 # if f is a rename, update `names` to also revert the source
2907 # if f is a rename, update `names` to also revert the source
2919 cwd = repo.getcwd()
2908 cwd = repo.getcwd()
2920 for f in localchanges:
2909 for f in localchanges:
2921 src = repo.dirstate.copied(f)
2910 src = repo.dirstate.copied(f)
2922 # XXX should we check for rename down to target node?
2911 # XXX should we check for rename down to target node?
2923 if src and src not in names and repo.dirstate[src] == 'r':
2912 if src and src not in names and repo.dirstate[src] == 'r':
2924 dsremoved.add(src)
2913 dsremoved.add(src)
2925 names[src] = (repo.pathto(src, cwd), True)
2914 names[src] = (repo.pathto(src, cwd), True)
2926
2915
2927 # distinguish between file to forget and the other
2916 # distinguish between file to forget and the other
2928 added = set()
2917 added = set()
2929 for abs in dsadded:
2918 for abs in dsadded:
2930 if repo.dirstate[abs] != 'a':
2919 if repo.dirstate[abs] != 'a':
2931 added.add(abs)
2920 added.add(abs)
2932 dsadded -= added
2921 dsadded -= added
2933
2922
2934 for abs in deladded:
2923 for abs in deladded:
2935 if repo.dirstate[abs] == 'a':
2924 if repo.dirstate[abs] == 'a':
2936 dsadded.add(abs)
2925 dsadded.add(abs)
2937 deladded -= dsadded
2926 deladded -= dsadded
2938
2927
2939 # For files marked as removed, we check if an unknown file is present at
2928 # For files marked as removed, we check if an unknown file is present at
2940 # the same path. If a such file exists it may need to be backed up.
2929 # the same path. If a such file exists it may need to be backed up.
2941 # Making the distinction at this stage helps have simpler backup
2930 # Making the distinction at this stage helps have simpler backup
2942 # logic.
2931 # logic.
2943 removunk = set()
2932 removunk = set()
2944 for abs in removed:
2933 for abs in removed:
2945 target = repo.wjoin(abs)
2934 target = repo.wjoin(abs)
2946 if os.path.lexists(target):
2935 if os.path.lexists(target):
2947 removunk.add(abs)
2936 removunk.add(abs)
2948 removed -= removunk
2937 removed -= removunk
2949
2938
2950 dsremovunk = set()
2939 dsremovunk = set()
2951 for abs in dsremoved:
2940 for abs in dsremoved:
2952 target = repo.wjoin(abs)
2941 target = repo.wjoin(abs)
2953 if os.path.lexists(target):
2942 if os.path.lexists(target):
2954 dsremovunk.add(abs)
2943 dsremovunk.add(abs)
2955 dsremoved -= dsremovunk
2944 dsremoved -= dsremovunk
2956
2945
2957 # action to be actually performed by revert
2946 # action to be actually performed by revert
2958 # (<list of file>, message>) tuple
2947 # (<list of file>, message>) tuple
2959 actions = {'revert': ([], _('reverting %s\n')),
2948 actions = {'revert': ([], _('reverting %s\n')),
2960 'add': ([], _('adding %s\n')),
2949 'add': ([], _('adding %s\n')),
2961 'remove': ([], _('removing %s\n')),
2950 'remove': ([], _('removing %s\n')),
2962 'drop': ([], _('removing %s\n')),
2951 'drop': ([], _('removing %s\n')),
2963 'forget': ([], _('forgetting %s\n')),
2952 'forget': ([], _('forgetting %s\n')),
2964 'undelete': ([], _('undeleting %s\n')),
2953 'undelete': ([], _('undeleting %s\n')),
2965 'noop': (None, _('no changes needed to %s\n')),
2954 'noop': (None, _('no changes needed to %s\n')),
2966 'unknown': (None, _('file not managed: %s\n')),
2955 'unknown': (None, _('file not managed: %s\n')),
2967 }
2956 }
2968
2957
2969 # "constant" that convey the backup strategy.
2958 # "constant" that convey the backup strategy.
2970 # All set to `discard` if `no-backup` is set do avoid checking
2959 # All set to `discard` if `no-backup` is set do avoid checking
2971 # no_backup lower in the code.
2960 # no_backup lower in the code.
2972 # These values are ordered for comparison purposes
2961 # These values are ordered for comparison purposes
2973 backup = 2 # unconditionally do backup
2962 backup = 2 # unconditionally do backup
2974 check = 1 # check if the existing file differs from target
2963 check = 1 # check if the existing file differs from target
2975 discard = 0 # never do backup
2964 discard = 0 # never do backup
2976 if opts.get('no_backup'):
2965 if opts.get('no_backup'):
2977 backup = check = discard
2966 backup = check = discard
2978
2967
2979 backupanddel = actions['remove']
2968 backupanddel = actions['remove']
2980 if not opts.get('no_backup'):
2969 if not opts.get('no_backup'):
2981 backupanddel = actions['drop']
2970 backupanddel = actions['drop']
2982
2971
2983 disptable = (
2972 disptable = (
2984 # dispatch table:
2973 # dispatch table:
2985 # file state
2974 # file state
2986 # action
2975 # action
2987 # make backup
2976 # make backup
2988
2977
2989 ## Sets that results that will change file on disk
2978 ## Sets that results that will change file on disk
2990 # Modified compared to target, no local change
2979 # Modified compared to target, no local change
2991 (modified, actions['revert'], discard),
2980 (modified, actions['revert'], discard),
2992 # Modified compared to target, but local file is deleted
2981 # Modified compared to target, but local file is deleted
2993 (deleted, actions['revert'], discard),
2982 (deleted, actions['revert'], discard),
2994 # Modified compared to target, local change
2983 # Modified compared to target, local change
2995 (dsmodified, actions['revert'], backup),
2984 (dsmodified, actions['revert'], backup),
2996 # Added since target
2985 # Added since target
2997 (added, actions['remove'], discard),
2986 (added, actions['remove'], discard),
2998 # Added in working directory
2987 # Added in working directory
2999 (dsadded, actions['forget'], discard),
2988 (dsadded, actions['forget'], discard),
3000 # Added since target, have local modification
2989 # Added since target, have local modification
3001 (modadded, backupanddel, backup),
2990 (modadded, backupanddel, backup),
3002 # Added since target but file is missing in working directory
2991 # Added since target but file is missing in working directory
3003 (deladded, actions['drop'], discard),
2992 (deladded, actions['drop'], discard),
3004 # Removed since target, before working copy parent
2993 # Removed since target, before working copy parent
3005 (removed, actions['add'], discard),
2994 (removed, actions['add'], discard),
3006 # Same as `removed` but an unknown file exists at the same path
2995 # Same as `removed` but an unknown file exists at the same path
3007 (removunk, actions['add'], check),
2996 (removunk, actions['add'], check),
3008 # Removed since targe, marked as such in working copy parent
2997 # Removed since targe, marked as such in working copy parent
3009 (dsremoved, actions['undelete'], discard),
2998 (dsremoved, actions['undelete'], discard),
3010 # Same as `dsremoved` but an unknown file exists at the same path
2999 # Same as `dsremoved` but an unknown file exists at the same path
3011 (dsremovunk, actions['undelete'], check),
3000 (dsremovunk, actions['undelete'], check),
3012 ## the following sets does not result in any file changes
3001 ## the following sets does not result in any file changes
3013 # File with no modification
3002 # File with no modification
3014 (clean, actions['noop'], discard),
3003 (clean, actions['noop'], discard),
3015 # Existing file, not tracked anywhere
3004 # Existing file, not tracked anywhere
3016 (unknown, actions['unknown'], discard),
3005 (unknown, actions['unknown'], discard),
3017 )
3006 )
3018
3007
3019 for abs, (rel, exact) in sorted(names.items()):
3008 for abs, (rel, exact) in sorted(names.items()):
3020 # target file to be touch on disk (relative to cwd)
3009 # target file to be touch on disk (relative to cwd)
3021 target = repo.wjoin(abs)
3010 target = repo.wjoin(abs)
3022 # search the entry in the dispatch table.
3011 # search the entry in the dispatch table.
3023 # if the file is in any of these sets, it was touched in the working
3012 # if the file is in any of these sets, it was touched in the working
3024 # directory parent and we are sure it needs to be reverted.
3013 # directory parent and we are sure it needs to be reverted.
3025 for table, (xlist, msg), dobackup in disptable:
3014 for table, (xlist, msg), dobackup in disptable:
3026 if abs not in table:
3015 if abs not in table:
3027 continue
3016 continue
3028 if xlist is not None:
3017 if xlist is not None:
3029 xlist.append(abs)
3018 xlist.append(abs)
3030 if dobackup and (backup <= dobackup
3019 if dobackup and (backup <= dobackup
3031 or wctx[abs].cmp(ctx[abs])):
3020 or wctx[abs].cmp(ctx[abs])):
3032 bakname = "%s.orig" % rel
3021 bakname = "%s.orig" % rel
3033 ui.note(_('saving current version of %s as %s\n') %
3022 ui.note(_('saving current version of %s as %s\n') %
3034 (rel, bakname))
3023 (rel, bakname))
3035 if not opts.get('dry_run'):
3024 if not opts.get('dry_run'):
3036 if interactive:
3025 if interactive:
3037 util.copyfile(target, bakname)
3026 util.copyfile(target, bakname)
3038 else:
3027 else:
3039 util.rename(target, bakname)
3028 util.rename(target, bakname)
3040 if ui.verbose or not exact:
3029 if ui.verbose or not exact:
3041 if not isinstance(msg, basestring):
3030 if not isinstance(msg, basestring):
3042 msg = msg(abs)
3031 msg = msg(abs)
3043 ui.status(msg % rel)
3032 ui.status(msg % rel)
3044 elif exact:
3033 elif exact:
3045 ui.warn(msg % rel)
3034 ui.warn(msg % rel)
3046 break
3035 break
3047
3036
3048 if not opts.get('dry_run'):
3037 if not opts.get('dry_run'):
3049 needdata = ('revert', 'add', 'undelete')
3038 needdata = ('revert', 'add', 'undelete')
3050 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3039 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3051 _performrevert(repo, parents, ctx, actions, interactive)
3040 _performrevert(repo, parents, ctx, actions, interactive)
3052
3041
3053 if targetsubs:
3042 if targetsubs:
3054 # Revert the subrepos on the revert list
3043 # Revert the subrepos on the revert list
3055 for sub in targetsubs:
3044 for sub in targetsubs:
3056 try:
3045 try:
3057 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3046 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3058 except KeyError:
3047 except KeyError:
3059 raise util.Abort("subrepository '%s' does not exist in %s!"
3048 raise util.Abort("subrepository '%s' does not exist in %s!"
3060 % (sub, short(ctx.node())))
3049 % (sub, short(ctx.node())))
3061 finally:
3050 finally:
3062 wlock.release()
3051 wlock.release()
3063
3052
3064 def _revertprefetch(repo, ctx, *files):
3053 def _revertprefetch(repo, ctx, *files):
3065 """Let extension changing the storage layer prefetch content"""
3054 """Let extension changing the storage layer prefetch content"""
3066 pass
3055 pass
3067
3056
3068 def _performrevert(repo, parents, ctx, actions, interactive=False):
3057 def _performrevert(repo, parents, ctx, actions, interactive=False):
3069 """function that actually perform all the actions computed for revert
3058 """function that actually perform all the actions computed for revert
3070
3059
3071 This is an independent function to let extension to plug in and react to
3060 This is an independent function to let extension to plug in and react to
3072 the imminent revert.
3061 the imminent revert.
3073
3062
3074 Make sure you have the working directory locked when calling this function.
3063 Make sure you have the working directory locked when calling this function.
3075 """
3064 """
3076 parent, p2 = parents
3065 parent, p2 = parents
3077 node = ctx.node()
3066 node = ctx.node()
3078 def checkout(f):
3067 def checkout(f):
3079 fc = ctx[f]
3068 fc = ctx[f]
3080 repo.wwrite(f, fc.data(), fc.flags())
3069 repo.wwrite(f, fc.data(), fc.flags())
3081
3070
3082 audit_path = pathutil.pathauditor(repo.root)
3071 audit_path = pathutil.pathauditor(repo.root)
3083 for f in actions['forget'][0]:
3072 for f in actions['forget'][0]:
3084 repo.dirstate.drop(f)
3073 repo.dirstate.drop(f)
3085 for f in actions['remove'][0]:
3074 for f in actions['remove'][0]:
3086 audit_path(f)
3075 audit_path(f)
3087 util.unlinkpath(repo.wjoin(f))
3076 util.unlinkpath(repo.wjoin(f))
3088 repo.dirstate.remove(f)
3077 repo.dirstate.remove(f)
3089 for f in actions['drop'][0]:
3078 for f in actions['drop'][0]:
3090 audit_path(f)
3079 audit_path(f)
3091 repo.dirstate.remove(f)
3080 repo.dirstate.remove(f)
3092
3081
3093 normal = None
3082 normal = None
3094 if node == parent:
3083 if node == parent:
3095 # We're reverting to our parent. If possible, we'd like status
3084 # We're reverting to our parent. If possible, we'd like status
3096 # to report the file as clean. We have to use normallookup for
3085 # to report the file as clean. We have to use normallookup for
3097 # merges to avoid losing information about merged/dirty files.
3086 # merges to avoid losing information about merged/dirty files.
3098 if p2 != nullid:
3087 if p2 != nullid:
3099 normal = repo.dirstate.normallookup
3088 normal = repo.dirstate.normallookup
3100 else:
3089 else:
3101 normal = repo.dirstate.normal
3090 normal = repo.dirstate.normal
3102
3091
3103 if interactive:
3092 if interactive:
3104 # Prompt the user for changes to revert
3093 # Prompt the user for changes to revert
3105 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3094 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3106 m = scmutil.match(ctx, torevert, {})
3095 m = scmutil.match(ctx, torevert, {})
3107 diff = patch.diff(repo, None, ctx.node(), m)
3096 diff = patch.diff(repo, None, ctx.node(), m)
3108 originalchunks = patch.parsepatch(diff)
3097 originalchunks = patch.parsepatch(diff)
3109 try:
3098 try:
3110 chunks = recordfilter(repo.ui, originalchunks)
3099 chunks = recordfilter(repo.ui, originalchunks)
3111 except patch.PatchError, err:
3100 except patch.PatchError, err:
3112 raise util.Abort(_('error parsing patch: %s') % err)
3101 raise util.Abort(_('error parsing patch: %s') % err)
3113
3102
3114 # Apply changes
3103 # Apply changes
3115 fp = cStringIO.StringIO()
3104 fp = cStringIO.StringIO()
3116 for c in chunks:
3105 for c in chunks:
3117 c.write(fp)
3106 c.write(fp)
3118 dopatch = fp.tell()
3107 dopatch = fp.tell()
3119 fp.seek(0)
3108 fp.seek(0)
3120 if dopatch:
3109 if dopatch:
3121 try:
3110 try:
3122 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3111 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3123 except patch.PatchError, err:
3112 except patch.PatchError, err:
3124 raise util.Abort(str(err))
3113 raise util.Abort(str(err))
3125 del fp
3114 del fp
3126 else:
3115 else:
3127 for f in actions['revert'][0]:
3116 for f in actions['revert'][0]:
3128 checkout(f)
3117 checkout(f)
3129 if normal:
3118 if normal:
3130 normal(f)
3119 normal(f)
3131
3120
3132 for f in actions['add'][0]:
3121 for f in actions['add'][0]:
3133 checkout(f)
3122 checkout(f)
3134 repo.dirstate.add(f)
3123 repo.dirstate.add(f)
3135
3124
3136 normal = repo.dirstate.normallookup
3125 normal = repo.dirstate.normallookup
3137 if node == parent and p2 == nullid:
3126 if node == parent and p2 == nullid:
3138 normal = repo.dirstate.normal
3127 normal = repo.dirstate.normal
3139 for f in actions['undelete'][0]:
3128 for f in actions['undelete'][0]:
3140 checkout(f)
3129 checkout(f)
3141 normal(f)
3130 normal(f)
3142
3131
3143 copied = copies.pathcopies(repo[parent], ctx)
3132 copied = copies.pathcopies(repo[parent], ctx)
3144
3133
3145 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3134 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3146 if f in copied:
3135 if f in copied:
3147 repo.dirstate.copy(copied[f], f)
3136 repo.dirstate.copy(copied[f], f)
3148
3137
3149 def command(table):
3138 def command(table):
3150 """Returns a function object to be used as a decorator for making commands.
3139 """Returns a function object to be used as a decorator for making commands.
3151
3140
3152 This function receives a command table as its argument. The table should
3141 This function receives a command table as its argument. The table should
3153 be a dict.
3142 be a dict.
3154
3143
3155 The returned function can be used as a decorator for adding commands
3144 The returned function can be used as a decorator for adding commands
3156 to that command table. This function accepts multiple arguments to define
3145 to that command table. This function accepts multiple arguments to define
3157 a command.
3146 a command.
3158
3147
3159 The first argument is the command name.
3148 The first argument is the command name.
3160
3149
3161 The options argument is an iterable of tuples defining command arguments.
3150 The options argument is an iterable of tuples defining command arguments.
3162 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3151 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3163
3152
3164 The synopsis argument defines a short, one line summary of how to use the
3153 The synopsis argument defines a short, one line summary of how to use the
3165 command. This shows up in the help output.
3154 command. This shows up in the help output.
3166
3155
3167 The norepo argument defines whether the command does not require a
3156 The norepo argument defines whether the command does not require a
3168 local repository. Most commands operate against a repository, thus the
3157 local repository. Most commands operate against a repository, thus the
3169 default is False.
3158 default is False.
3170
3159
3171 The optionalrepo argument defines whether the command optionally requires
3160 The optionalrepo argument defines whether the command optionally requires
3172 a local repository.
3161 a local repository.
3173
3162
3174 The inferrepo argument defines whether to try to find a repository from the
3163 The inferrepo argument defines whether to try to find a repository from the
3175 command line arguments. If True, arguments will be examined for potential
3164 command line arguments. If True, arguments will be examined for potential
3176 repository locations. See ``findrepo()``. If a repository is found, it
3165 repository locations. See ``findrepo()``. If a repository is found, it
3177 will be used.
3166 will be used.
3178 """
3167 """
3179 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3168 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3180 inferrepo=False):
3169 inferrepo=False):
3181 def decorator(func):
3170 def decorator(func):
3182 if synopsis:
3171 if synopsis:
3183 table[name] = func, list(options), synopsis
3172 table[name] = func, list(options), synopsis
3184 else:
3173 else:
3185 table[name] = func, list(options)
3174 table[name] = func, list(options)
3186
3175
3187 if norepo:
3176 if norepo:
3188 # Avoid import cycle.
3177 # Avoid import cycle.
3189 import commands
3178 import commands
3190 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3179 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3191
3180
3192 if optionalrepo:
3181 if optionalrepo:
3193 import commands
3182 import commands
3194 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3183 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3195
3184
3196 if inferrepo:
3185 if inferrepo:
3197 import commands
3186 import commands
3198 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3187 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3199
3188
3200 return func
3189 return func
3201 return decorator
3190 return decorator
3202
3191
3203 return cmd
3192 return cmd
3204
3193
3205 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3194 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3206 # commands.outgoing. "missing" is "missing" of the result of
3195 # commands.outgoing. "missing" is "missing" of the result of
3207 # "findcommonoutgoing()"
3196 # "findcommonoutgoing()"
3208 outgoinghooks = util.hooks()
3197 outgoinghooks = util.hooks()
3209
3198
3210 # a list of (ui, repo) functions called by commands.summary
3199 # a list of (ui, repo) functions called by commands.summary
3211 summaryhooks = util.hooks()
3200 summaryhooks = util.hooks()
3212
3201
3213 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3202 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3214 #
3203 #
3215 # functions should return tuple of booleans below, if 'changes' is None:
3204 # functions should return tuple of booleans below, if 'changes' is None:
3216 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3205 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3217 #
3206 #
3218 # otherwise, 'changes' is a tuple of tuples below:
3207 # otherwise, 'changes' is a tuple of tuples below:
3219 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3208 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3220 # - (desturl, destbranch, destpeer, outgoing)
3209 # - (desturl, destbranch, destpeer, outgoing)
3221 summaryremotehooks = util.hooks()
3210 summaryremotehooks = util.hooks()
3222
3211
3223 # A list of state files kept by multistep operations like graft.
3212 # A list of state files kept by multistep operations like graft.
3224 # Since graft cannot be aborted, it is considered 'clearable' by update.
3213 # Since graft cannot be aborted, it is considered 'clearable' by update.
3225 # note: bisect is intentionally excluded
3214 # note: bisect is intentionally excluded
3226 # (state file, clearable, allowcommit, error, hint)
3215 # (state file, clearable, allowcommit, error, hint)
3227 unfinishedstates = [
3216 unfinishedstates = [
3228 ('graftstate', True, False, _('graft in progress'),
3217 ('graftstate', True, False, _('graft in progress'),
3229 _("use 'hg graft --continue' or 'hg update' to abort")),
3218 _("use 'hg graft --continue' or 'hg update' to abort")),
3230 ('updatestate', True, False, _('last update was interrupted'),
3219 ('updatestate', True, False, _('last update was interrupted'),
3231 _("use 'hg update' to get a consistent checkout"))
3220 _("use 'hg update' to get a consistent checkout"))
3232 ]
3221 ]
3233
3222
3234 def checkunfinished(repo, commit=False):
3223 def checkunfinished(repo, commit=False):
3235 '''Look for an unfinished multistep operation, like graft, and abort
3224 '''Look for an unfinished multistep operation, like graft, and abort
3236 if found. It's probably good to check this right before
3225 if found. It's probably good to check this right before
3237 bailifchanged().
3226 bailifchanged().
3238 '''
3227 '''
3239 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3228 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3240 if commit and allowcommit:
3229 if commit and allowcommit:
3241 continue
3230 continue
3242 if repo.vfs.exists(f):
3231 if repo.vfs.exists(f):
3243 raise util.Abort(msg, hint=hint)
3232 raise util.Abort(msg, hint=hint)
3244
3233
3245 def clearunfinished(repo):
3234 def clearunfinished(repo):
3246 '''Check for unfinished operations (as above), and clear the ones
3235 '''Check for unfinished operations (as above), and clear the ones
3247 that are clearable.
3236 that are clearable.
3248 '''
3237 '''
3249 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3238 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3250 if not clearable and repo.vfs.exists(f):
3239 if not clearable and repo.vfs.exists(f):
3251 raise util.Abort(msg, hint=hint)
3240 raise util.Abort(msg, hint=hint)
3252 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3241 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3253 if clearable and repo.vfs.exists(f):
3242 if clearable and repo.vfs.exists(f):
3254 util.unlink(repo.join(f))
3243 util.unlink(repo.join(f))
@@ -1,2464 +1,2464 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email, os, errno, re, posixpath, copy
9 import cStringIO, email, os, errno, re, posixpath, copy
10 import tempfile, zlib, shutil
10 import tempfile, zlib, shutil
11 # On python2.4 you have to import these by name or they fail to
11 # On python2.4 you have to import these by name or they fail to
12 # load. This was not a problem on Python 2.7.
12 # load. This was not a problem on Python 2.7.
13 import email.Generator
13 import email.Generator
14 import email.Parser
14 import email.Parser
15
15
16 from i18n import _
16 from i18n import _
17 from node import hex, short
17 from node import hex, short
18 import cStringIO
18 import cStringIO
19 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
19 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
20 import pathutil
20 import pathutil
21
21
22 gitre = re.compile('diff --git a/(.*) b/(.*)')
22 gitre = re.compile('diff --git a/(.*) b/(.*)')
23 tabsplitter = re.compile(r'(\t+|[^\t]+)')
23 tabsplitter = re.compile(r'(\t+|[^\t]+)')
24
24
25 class PatchError(Exception):
25 class PatchError(Exception):
26 pass
26 pass
27
27
28
28
29 # public functions
29 # public functions
30
30
31 def split(stream):
31 def split(stream):
32 '''return an iterator of individual patches from a stream'''
32 '''return an iterator of individual patches from a stream'''
33 def isheader(line, inheader):
33 def isheader(line, inheader):
34 if inheader and line[0] in (' ', '\t'):
34 if inheader and line[0] in (' ', '\t'):
35 # continuation
35 # continuation
36 return True
36 return True
37 if line[0] in (' ', '-', '+'):
37 if line[0] in (' ', '-', '+'):
38 # diff line - don't check for header pattern in there
38 # diff line - don't check for header pattern in there
39 return False
39 return False
40 l = line.split(': ', 1)
40 l = line.split(': ', 1)
41 return len(l) == 2 and ' ' not in l[0]
41 return len(l) == 2 and ' ' not in l[0]
42
42
43 def chunk(lines):
43 def chunk(lines):
44 return cStringIO.StringIO(''.join(lines))
44 return cStringIO.StringIO(''.join(lines))
45
45
46 def hgsplit(stream, cur):
46 def hgsplit(stream, cur):
47 inheader = True
47 inheader = True
48
48
49 for line in stream:
49 for line in stream:
50 if not line.strip():
50 if not line.strip():
51 inheader = False
51 inheader = False
52 if not inheader and line.startswith('# HG changeset patch'):
52 if not inheader and line.startswith('# HG changeset patch'):
53 yield chunk(cur)
53 yield chunk(cur)
54 cur = []
54 cur = []
55 inheader = True
55 inheader = True
56
56
57 cur.append(line)
57 cur.append(line)
58
58
59 if cur:
59 if cur:
60 yield chunk(cur)
60 yield chunk(cur)
61
61
62 def mboxsplit(stream, cur):
62 def mboxsplit(stream, cur):
63 for line in stream:
63 for line in stream:
64 if line.startswith('From '):
64 if line.startswith('From '):
65 for c in split(chunk(cur[1:])):
65 for c in split(chunk(cur[1:])):
66 yield c
66 yield c
67 cur = []
67 cur = []
68
68
69 cur.append(line)
69 cur.append(line)
70
70
71 if cur:
71 if cur:
72 for c in split(chunk(cur[1:])):
72 for c in split(chunk(cur[1:])):
73 yield c
73 yield c
74
74
75 def mimesplit(stream, cur):
75 def mimesplit(stream, cur):
76 def msgfp(m):
76 def msgfp(m):
77 fp = cStringIO.StringIO()
77 fp = cStringIO.StringIO()
78 g = email.Generator.Generator(fp, mangle_from_=False)
78 g = email.Generator.Generator(fp, mangle_from_=False)
79 g.flatten(m)
79 g.flatten(m)
80 fp.seek(0)
80 fp.seek(0)
81 return fp
81 return fp
82
82
83 for line in stream:
83 for line in stream:
84 cur.append(line)
84 cur.append(line)
85 c = chunk(cur)
85 c = chunk(cur)
86
86
87 m = email.Parser.Parser().parse(c)
87 m = email.Parser.Parser().parse(c)
88 if not m.is_multipart():
88 if not m.is_multipart():
89 yield msgfp(m)
89 yield msgfp(m)
90 else:
90 else:
91 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
91 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
92 for part in m.walk():
92 for part in m.walk():
93 ct = part.get_content_type()
93 ct = part.get_content_type()
94 if ct not in ok_types:
94 if ct not in ok_types:
95 continue
95 continue
96 yield msgfp(part)
96 yield msgfp(part)
97
97
98 def headersplit(stream, cur):
98 def headersplit(stream, cur):
99 inheader = False
99 inheader = False
100
100
101 for line in stream:
101 for line in stream:
102 if not inheader and isheader(line, inheader):
102 if not inheader and isheader(line, inheader):
103 yield chunk(cur)
103 yield chunk(cur)
104 cur = []
104 cur = []
105 inheader = True
105 inheader = True
106 if inheader and not isheader(line, inheader):
106 if inheader and not isheader(line, inheader):
107 inheader = False
107 inheader = False
108
108
109 cur.append(line)
109 cur.append(line)
110
110
111 if cur:
111 if cur:
112 yield chunk(cur)
112 yield chunk(cur)
113
113
114 def remainder(cur):
114 def remainder(cur):
115 yield chunk(cur)
115 yield chunk(cur)
116
116
117 class fiter(object):
117 class fiter(object):
118 def __init__(self, fp):
118 def __init__(self, fp):
119 self.fp = fp
119 self.fp = fp
120
120
121 def __iter__(self):
121 def __iter__(self):
122 return self
122 return self
123
123
124 def next(self):
124 def next(self):
125 l = self.fp.readline()
125 l = self.fp.readline()
126 if not l:
126 if not l:
127 raise StopIteration
127 raise StopIteration
128 return l
128 return l
129
129
130 inheader = False
130 inheader = False
131 cur = []
131 cur = []
132
132
133 mimeheaders = ['content-type']
133 mimeheaders = ['content-type']
134
134
135 if not util.safehasattr(stream, 'next'):
135 if not util.safehasattr(stream, 'next'):
136 # http responses, for example, have readline but not next
136 # http responses, for example, have readline but not next
137 stream = fiter(stream)
137 stream = fiter(stream)
138
138
139 for line in stream:
139 for line in stream:
140 cur.append(line)
140 cur.append(line)
141 if line.startswith('# HG changeset patch'):
141 if line.startswith('# HG changeset patch'):
142 return hgsplit(stream, cur)
142 return hgsplit(stream, cur)
143 elif line.startswith('From '):
143 elif line.startswith('From '):
144 return mboxsplit(stream, cur)
144 return mboxsplit(stream, cur)
145 elif isheader(line, inheader):
145 elif isheader(line, inheader):
146 inheader = True
146 inheader = True
147 if line.split(':', 1)[0].lower() in mimeheaders:
147 if line.split(':', 1)[0].lower() in mimeheaders:
148 # let email parser handle this
148 # let email parser handle this
149 return mimesplit(stream, cur)
149 return mimesplit(stream, cur)
150 elif line.startswith('--- ') and inheader:
150 elif line.startswith('--- ') and inheader:
151 # No evil headers seen by diff start, split by hand
151 # No evil headers seen by diff start, split by hand
152 return headersplit(stream, cur)
152 return headersplit(stream, cur)
153 # Not enough info, keep reading
153 # Not enough info, keep reading
154
154
155 # if we are here, we have a very plain patch
155 # if we are here, we have a very plain patch
156 return remainder(cur)
156 return remainder(cur)
157
157
158 def extract(ui, fileobj):
158 def extract(ui, fileobj):
159 '''extract patch from data read from fileobj.
159 '''extract patch from data read from fileobj.
160
160
161 patch can be a normal patch or contained in an email message.
161 patch can be a normal patch or contained in an email message.
162
162
163 return tuple (filename, message, user, date, branch, node, p1, p2).
163 return tuple (filename, message, user, date, branch, node, p1, p2).
164 Any item in the returned tuple can be None. If filename is None,
164 Any item in the returned tuple can be None. If filename is None,
165 fileobj did not contain a patch. Caller must unlink filename when done.'''
165 fileobj did not contain a patch. Caller must unlink filename when done.'''
166
166
167 # attempt to detect the start of a patch
167 # attempt to detect the start of a patch
168 # (this heuristic is borrowed from quilt)
168 # (this heuristic is borrowed from quilt)
169 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
169 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
170 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
170 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
171 r'---[ \t].*?^\+\+\+[ \t]|'
171 r'---[ \t].*?^\+\+\+[ \t]|'
172 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
172 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
173
173
174 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
174 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
175 tmpfp = os.fdopen(fd, 'w')
175 tmpfp = os.fdopen(fd, 'w')
176 try:
176 try:
177 msg = email.Parser.Parser().parse(fileobj)
177 msg = email.Parser.Parser().parse(fileobj)
178
178
179 subject = msg['Subject']
179 subject = msg['Subject']
180 user = msg['From']
180 user = msg['From']
181 if not subject and not user:
181 if not subject and not user:
182 # Not an email, restore parsed headers if any
182 # Not an email, restore parsed headers if any
183 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
183 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
184
184
185 # should try to parse msg['Date']
185 # should try to parse msg['Date']
186 date = None
186 date = None
187 nodeid = None
187 nodeid = None
188 branch = None
188 branch = None
189 parents = []
189 parents = []
190
190
191 if subject:
191 if subject:
192 if subject.startswith('[PATCH'):
192 if subject.startswith('[PATCH'):
193 pend = subject.find(']')
193 pend = subject.find(']')
194 if pend >= 0:
194 if pend >= 0:
195 subject = subject[pend + 1:].lstrip()
195 subject = subject[pend + 1:].lstrip()
196 subject = re.sub(r'\n[ \t]+', ' ', subject)
196 subject = re.sub(r'\n[ \t]+', ' ', subject)
197 ui.debug('Subject: %s\n' % subject)
197 ui.debug('Subject: %s\n' % subject)
198 if user:
198 if user:
199 ui.debug('From: %s\n' % user)
199 ui.debug('From: %s\n' % user)
200 diffs_seen = 0
200 diffs_seen = 0
201 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
201 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
202 message = ''
202 message = ''
203 for part in msg.walk():
203 for part in msg.walk():
204 content_type = part.get_content_type()
204 content_type = part.get_content_type()
205 ui.debug('Content-Type: %s\n' % content_type)
205 ui.debug('Content-Type: %s\n' % content_type)
206 if content_type not in ok_types:
206 if content_type not in ok_types:
207 continue
207 continue
208 payload = part.get_payload(decode=True)
208 payload = part.get_payload(decode=True)
209 m = diffre.search(payload)
209 m = diffre.search(payload)
210 if m:
210 if m:
211 hgpatch = False
211 hgpatch = False
212 hgpatchheader = False
212 hgpatchheader = False
213 ignoretext = False
213 ignoretext = False
214
214
215 ui.debug('found patch at byte %d\n' % m.start(0))
215 ui.debug('found patch at byte %d\n' % m.start(0))
216 diffs_seen += 1
216 diffs_seen += 1
217 cfp = cStringIO.StringIO()
217 cfp = cStringIO.StringIO()
218 for line in payload[:m.start(0)].splitlines():
218 for line in payload[:m.start(0)].splitlines():
219 if line.startswith('# HG changeset patch') and not hgpatch:
219 if line.startswith('# HG changeset patch') and not hgpatch:
220 ui.debug('patch generated by hg export\n')
220 ui.debug('patch generated by hg export\n')
221 hgpatch = True
221 hgpatch = True
222 hgpatchheader = True
222 hgpatchheader = True
223 # drop earlier commit message content
223 # drop earlier commit message content
224 cfp.seek(0)
224 cfp.seek(0)
225 cfp.truncate()
225 cfp.truncate()
226 subject = None
226 subject = None
227 elif hgpatchheader:
227 elif hgpatchheader:
228 if line.startswith('# User '):
228 if line.startswith('# User '):
229 user = line[7:]
229 user = line[7:]
230 ui.debug('From: %s\n' % user)
230 ui.debug('From: %s\n' % user)
231 elif line.startswith("# Date "):
231 elif line.startswith("# Date "):
232 date = line[7:]
232 date = line[7:]
233 elif line.startswith("# Branch "):
233 elif line.startswith("# Branch "):
234 branch = line[9:]
234 branch = line[9:]
235 elif line.startswith("# Node ID "):
235 elif line.startswith("# Node ID "):
236 nodeid = line[10:]
236 nodeid = line[10:]
237 elif line.startswith("# Parent "):
237 elif line.startswith("# Parent "):
238 parents.append(line[9:].lstrip())
238 parents.append(line[9:].lstrip())
239 elif not line.startswith("# "):
239 elif not line.startswith("# "):
240 hgpatchheader = False
240 hgpatchheader = False
241 elif line == '---':
241 elif line == '---':
242 ignoretext = True
242 ignoretext = True
243 if not hgpatchheader and not ignoretext:
243 if not hgpatchheader and not ignoretext:
244 cfp.write(line)
244 cfp.write(line)
245 cfp.write('\n')
245 cfp.write('\n')
246 message = cfp.getvalue()
246 message = cfp.getvalue()
247 if tmpfp:
247 if tmpfp:
248 tmpfp.write(payload)
248 tmpfp.write(payload)
249 if not payload.endswith('\n'):
249 if not payload.endswith('\n'):
250 tmpfp.write('\n')
250 tmpfp.write('\n')
251 elif not diffs_seen and message and content_type == 'text/plain':
251 elif not diffs_seen and message and content_type == 'text/plain':
252 message += '\n' + payload
252 message += '\n' + payload
253 except: # re-raises
253 except: # re-raises
254 tmpfp.close()
254 tmpfp.close()
255 os.unlink(tmpname)
255 os.unlink(tmpname)
256 raise
256 raise
257
257
258 if subject and not message.startswith(subject):
258 if subject and not message.startswith(subject):
259 message = '%s\n%s' % (subject, message)
259 message = '%s\n%s' % (subject, message)
260 tmpfp.close()
260 tmpfp.close()
261 if not diffs_seen:
261 if not diffs_seen:
262 os.unlink(tmpname)
262 os.unlink(tmpname)
263 return None, message, user, date, branch, None, None, None
263 return None, message, user, date, branch, None, None, None
264
264
265 if parents:
265 if parents:
266 p1 = parents.pop(0)
266 p1 = parents.pop(0)
267 else:
267 else:
268 p1 = None
268 p1 = None
269
269
270 if parents:
270 if parents:
271 p2 = parents.pop(0)
271 p2 = parents.pop(0)
272 else:
272 else:
273 p2 = None
273 p2 = None
274
274
275 return tmpname, message, user, date, branch, nodeid, p1, p2
275 return tmpname, message, user, date, branch, nodeid, p1, p2
276
276
277 class patchmeta(object):
277 class patchmeta(object):
278 """Patched file metadata
278 """Patched file metadata
279
279
280 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
280 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
281 or COPY. 'path' is patched file path. 'oldpath' is set to the
281 or COPY. 'path' is patched file path. 'oldpath' is set to the
282 origin file when 'op' is either COPY or RENAME, None otherwise. If
282 origin file when 'op' is either COPY or RENAME, None otherwise. If
283 file mode is changed, 'mode' is a tuple (islink, isexec) where
283 file mode is changed, 'mode' is a tuple (islink, isexec) where
284 'islink' is True if the file is a symlink and 'isexec' is True if
284 'islink' is True if the file is a symlink and 'isexec' is True if
285 the file is executable. Otherwise, 'mode' is None.
285 the file is executable. Otherwise, 'mode' is None.
286 """
286 """
287 def __init__(self, path):
287 def __init__(self, path):
288 self.path = path
288 self.path = path
289 self.oldpath = None
289 self.oldpath = None
290 self.mode = None
290 self.mode = None
291 self.op = 'MODIFY'
291 self.op = 'MODIFY'
292 self.binary = False
292 self.binary = False
293
293
294 def setmode(self, mode):
294 def setmode(self, mode):
295 islink = mode & 020000
295 islink = mode & 020000
296 isexec = mode & 0100
296 isexec = mode & 0100
297 self.mode = (islink, isexec)
297 self.mode = (islink, isexec)
298
298
299 def copy(self):
299 def copy(self):
300 other = patchmeta(self.path)
300 other = patchmeta(self.path)
301 other.oldpath = self.oldpath
301 other.oldpath = self.oldpath
302 other.mode = self.mode
302 other.mode = self.mode
303 other.op = self.op
303 other.op = self.op
304 other.binary = self.binary
304 other.binary = self.binary
305 return other
305 return other
306
306
307 def _ispatchinga(self, afile):
307 def _ispatchinga(self, afile):
308 if afile == '/dev/null':
308 if afile == '/dev/null':
309 return self.op == 'ADD'
309 return self.op == 'ADD'
310 return afile == 'a/' + (self.oldpath or self.path)
310 return afile == 'a/' + (self.oldpath or self.path)
311
311
312 def _ispatchingb(self, bfile):
312 def _ispatchingb(self, bfile):
313 if bfile == '/dev/null':
313 if bfile == '/dev/null':
314 return self.op == 'DELETE'
314 return self.op == 'DELETE'
315 return bfile == 'b/' + self.path
315 return bfile == 'b/' + self.path
316
316
317 def ispatching(self, afile, bfile):
317 def ispatching(self, afile, bfile):
318 return self._ispatchinga(afile) and self._ispatchingb(bfile)
318 return self._ispatchinga(afile) and self._ispatchingb(bfile)
319
319
320 def __repr__(self):
320 def __repr__(self):
321 return "<patchmeta %s %r>" % (self.op, self.path)
321 return "<patchmeta %s %r>" % (self.op, self.path)
322
322
323 def readgitpatch(lr):
323 def readgitpatch(lr):
324 """extract git-style metadata about patches from <patchname>"""
324 """extract git-style metadata about patches from <patchname>"""
325
325
326 # Filter patch for git information
326 # Filter patch for git information
327 gp = None
327 gp = None
328 gitpatches = []
328 gitpatches = []
329 for line in lr:
329 for line in lr:
330 line = line.rstrip(' \r\n')
330 line = line.rstrip(' \r\n')
331 if line.startswith('diff --git a/'):
331 if line.startswith('diff --git a/'):
332 m = gitre.match(line)
332 m = gitre.match(line)
333 if m:
333 if m:
334 if gp:
334 if gp:
335 gitpatches.append(gp)
335 gitpatches.append(gp)
336 dst = m.group(2)
336 dst = m.group(2)
337 gp = patchmeta(dst)
337 gp = patchmeta(dst)
338 elif gp:
338 elif gp:
339 if line.startswith('--- '):
339 if line.startswith('--- '):
340 gitpatches.append(gp)
340 gitpatches.append(gp)
341 gp = None
341 gp = None
342 continue
342 continue
343 if line.startswith('rename from '):
343 if line.startswith('rename from '):
344 gp.op = 'RENAME'
344 gp.op = 'RENAME'
345 gp.oldpath = line[12:]
345 gp.oldpath = line[12:]
346 elif line.startswith('rename to '):
346 elif line.startswith('rename to '):
347 gp.path = line[10:]
347 gp.path = line[10:]
348 elif line.startswith('copy from '):
348 elif line.startswith('copy from '):
349 gp.op = 'COPY'
349 gp.op = 'COPY'
350 gp.oldpath = line[10:]
350 gp.oldpath = line[10:]
351 elif line.startswith('copy to '):
351 elif line.startswith('copy to '):
352 gp.path = line[8:]
352 gp.path = line[8:]
353 elif line.startswith('deleted file'):
353 elif line.startswith('deleted file'):
354 gp.op = 'DELETE'
354 gp.op = 'DELETE'
355 elif line.startswith('new file mode '):
355 elif line.startswith('new file mode '):
356 gp.op = 'ADD'
356 gp.op = 'ADD'
357 gp.setmode(int(line[-6:], 8))
357 gp.setmode(int(line[-6:], 8))
358 elif line.startswith('new mode '):
358 elif line.startswith('new mode '):
359 gp.setmode(int(line[-6:], 8))
359 gp.setmode(int(line[-6:], 8))
360 elif line.startswith('GIT binary patch'):
360 elif line.startswith('GIT binary patch'):
361 gp.binary = True
361 gp.binary = True
362 if gp:
362 if gp:
363 gitpatches.append(gp)
363 gitpatches.append(gp)
364
364
365 return gitpatches
365 return gitpatches
366
366
367 class linereader(object):
367 class linereader(object):
368 # simple class to allow pushing lines back into the input stream
368 # simple class to allow pushing lines back into the input stream
369 def __init__(self, fp):
369 def __init__(self, fp):
370 self.fp = fp
370 self.fp = fp
371 self.buf = []
371 self.buf = []
372
372
373 def push(self, line):
373 def push(self, line):
374 if line is not None:
374 if line is not None:
375 self.buf.append(line)
375 self.buf.append(line)
376
376
377 def readline(self):
377 def readline(self):
378 if self.buf:
378 if self.buf:
379 l = self.buf[0]
379 l = self.buf[0]
380 del self.buf[0]
380 del self.buf[0]
381 return l
381 return l
382 return self.fp.readline()
382 return self.fp.readline()
383
383
384 def __iter__(self):
384 def __iter__(self):
385 while True:
385 while True:
386 l = self.readline()
386 l = self.readline()
387 if not l:
387 if not l:
388 break
388 break
389 yield l
389 yield l
390
390
391 class abstractbackend(object):
391 class abstractbackend(object):
392 def __init__(self, ui):
392 def __init__(self, ui):
393 self.ui = ui
393 self.ui = ui
394
394
395 def getfile(self, fname):
395 def getfile(self, fname):
396 """Return target file data and flags as a (data, (islink,
396 """Return target file data and flags as a (data, (islink,
397 isexec)) tuple. Data is None if file is missing/deleted.
397 isexec)) tuple. Data is None if file is missing/deleted.
398 """
398 """
399 raise NotImplementedError
399 raise NotImplementedError
400
400
401 def setfile(self, fname, data, mode, copysource):
401 def setfile(self, fname, data, mode, copysource):
402 """Write data to target file fname and set its mode. mode is a
402 """Write data to target file fname and set its mode. mode is a
403 (islink, isexec) tuple. If data is None, the file content should
403 (islink, isexec) tuple. If data is None, the file content should
404 be left unchanged. If the file is modified after being copied,
404 be left unchanged. If the file is modified after being copied,
405 copysource is set to the original file name.
405 copysource is set to the original file name.
406 """
406 """
407 raise NotImplementedError
407 raise NotImplementedError
408
408
409 def unlink(self, fname):
409 def unlink(self, fname):
410 """Unlink target file."""
410 """Unlink target file."""
411 raise NotImplementedError
411 raise NotImplementedError
412
412
413 def writerej(self, fname, failed, total, lines):
413 def writerej(self, fname, failed, total, lines):
414 """Write rejected lines for fname. total is the number of hunks
414 """Write rejected lines for fname. total is the number of hunks
415 which failed to apply and total the total number of hunks for this
415 which failed to apply and total the total number of hunks for this
416 files.
416 files.
417 """
417 """
418 pass
418 pass
419
419
420 def exists(self, fname):
420 def exists(self, fname):
421 raise NotImplementedError
421 raise NotImplementedError
422
422
423 class fsbackend(abstractbackend):
423 class fsbackend(abstractbackend):
424 def __init__(self, ui, basedir):
424 def __init__(self, ui, basedir):
425 super(fsbackend, self).__init__(ui)
425 super(fsbackend, self).__init__(ui)
426 self.opener = scmutil.opener(basedir)
426 self.opener = scmutil.opener(basedir)
427
427
428 def _join(self, f):
428 def _join(self, f):
429 return os.path.join(self.opener.base, f)
429 return os.path.join(self.opener.base, f)
430
430
431 def getfile(self, fname):
431 def getfile(self, fname):
432 if self.opener.islink(fname):
432 if self.opener.islink(fname):
433 return (self.opener.readlink(fname), (True, False))
433 return (self.opener.readlink(fname), (True, False))
434
434
435 isexec = False
435 isexec = False
436 try:
436 try:
437 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
437 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
438 except OSError, e:
438 except OSError, e:
439 if e.errno != errno.ENOENT:
439 if e.errno != errno.ENOENT:
440 raise
440 raise
441 try:
441 try:
442 return (self.opener.read(fname), (False, isexec))
442 return (self.opener.read(fname), (False, isexec))
443 except IOError, e:
443 except IOError, e:
444 if e.errno != errno.ENOENT:
444 if e.errno != errno.ENOENT:
445 raise
445 raise
446 return None, None
446 return None, None
447
447
448 def setfile(self, fname, data, mode, copysource):
448 def setfile(self, fname, data, mode, copysource):
449 islink, isexec = mode
449 islink, isexec = mode
450 if data is None:
450 if data is None:
451 self.opener.setflags(fname, islink, isexec)
451 self.opener.setflags(fname, islink, isexec)
452 return
452 return
453 if islink:
453 if islink:
454 self.opener.symlink(data, fname)
454 self.opener.symlink(data, fname)
455 else:
455 else:
456 self.opener.write(fname, data)
456 self.opener.write(fname, data)
457 if isexec:
457 if isexec:
458 self.opener.setflags(fname, False, True)
458 self.opener.setflags(fname, False, True)
459
459
460 def unlink(self, fname):
460 def unlink(self, fname):
461 self.opener.unlinkpath(fname, ignoremissing=True)
461 self.opener.unlinkpath(fname, ignoremissing=True)
462
462
463 def writerej(self, fname, failed, total, lines):
463 def writerej(self, fname, failed, total, lines):
464 fname = fname + ".rej"
464 fname = fname + ".rej"
465 self.ui.warn(
465 self.ui.warn(
466 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
466 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
467 (failed, total, fname))
467 (failed, total, fname))
468 fp = self.opener(fname, 'w')
468 fp = self.opener(fname, 'w')
469 fp.writelines(lines)
469 fp.writelines(lines)
470 fp.close()
470 fp.close()
471
471
472 def exists(self, fname):
472 def exists(self, fname):
473 return self.opener.lexists(fname)
473 return self.opener.lexists(fname)
474
474
475 class workingbackend(fsbackend):
475 class workingbackend(fsbackend):
476 def __init__(self, ui, repo, similarity):
476 def __init__(self, ui, repo, similarity):
477 super(workingbackend, self).__init__(ui, repo.root)
477 super(workingbackend, self).__init__(ui, repo.root)
478 self.repo = repo
478 self.repo = repo
479 self.similarity = similarity
479 self.similarity = similarity
480 self.removed = set()
480 self.removed = set()
481 self.changed = set()
481 self.changed = set()
482 self.copied = []
482 self.copied = []
483
483
484 def _checkknown(self, fname):
484 def _checkknown(self, fname):
485 if self.repo.dirstate[fname] == '?' and self.exists(fname):
485 if self.repo.dirstate[fname] == '?' and self.exists(fname):
486 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
486 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
487
487
488 def setfile(self, fname, data, mode, copysource):
488 def setfile(self, fname, data, mode, copysource):
489 self._checkknown(fname)
489 self._checkknown(fname)
490 super(workingbackend, self).setfile(fname, data, mode, copysource)
490 super(workingbackend, self).setfile(fname, data, mode, copysource)
491 if copysource is not None:
491 if copysource is not None:
492 self.copied.append((copysource, fname))
492 self.copied.append((copysource, fname))
493 self.changed.add(fname)
493 self.changed.add(fname)
494
494
495 def unlink(self, fname):
495 def unlink(self, fname):
496 self._checkknown(fname)
496 self._checkknown(fname)
497 super(workingbackend, self).unlink(fname)
497 super(workingbackend, self).unlink(fname)
498 self.removed.add(fname)
498 self.removed.add(fname)
499 self.changed.add(fname)
499 self.changed.add(fname)
500
500
501 def close(self):
501 def close(self):
502 wctx = self.repo[None]
502 wctx = self.repo[None]
503 changed = set(self.changed)
503 changed = set(self.changed)
504 for src, dst in self.copied:
504 for src, dst in self.copied:
505 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
505 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
506 if self.removed:
506 if self.removed:
507 wctx.forget(sorted(self.removed))
507 wctx.forget(sorted(self.removed))
508 for f in self.removed:
508 for f in self.removed:
509 if f not in self.repo.dirstate:
509 if f not in self.repo.dirstate:
510 # File was deleted and no longer belongs to the
510 # File was deleted and no longer belongs to the
511 # dirstate, it was probably marked added then
511 # dirstate, it was probably marked added then
512 # deleted, and should not be considered by
512 # deleted, and should not be considered by
513 # marktouched().
513 # marktouched().
514 changed.discard(f)
514 changed.discard(f)
515 if changed:
515 if changed:
516 scmutil.marktouched(self.repo, changed, self.similarity)
516 scmutil.marktouched(self.repo, changed, self.similarity)
517 return sorted(self.changed)
517 return sorted(self.changed)
518
518
519 class filestore(object):
519 class filestore(object):
520 def __init__(self, maxsize=None):
520 def __init__(self, maxsize=None):
521 self.opener = None
521 self.opener = None
522 self.files = {}
522 self.files = {}
523 self.created = 0
523 self.created = 0
524 self.maxsize = maxsize
524 self.maxsize = maxsize
525 if self.maxsize is None:
525 if self.maxsize is None:
526 self.maxsize = 4*(2**20)
526 self.maxsize = 4*(2**20)
527 self.size = 0
527 self.size = 0
528 self.data = {}
528 self.data = {}
529
529
530 def setfile(self, fname, data, mode, copied=None):
530 def setfile(self, fname, data, mode, copied=None):
531 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
531 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
532 self.data[fname] = (data, mode, copied)
532 self.data[fname] = (data, mode, copied)
533 self.size += len(data)
533 self.size += len(data)
534 else:
534 else:
535 if self.opener is None:
535 if self.opener is None:
536 root = tempfile.mkdtemp(prefix='hg-patch-')
536 root = tempfile.mkdtemp(prefix='hg-patch-')
537 self.opener = scmutil.opener(root)
537 self.opener = scmutil.opener(root)
538 # Avoid filename issues with these simple names
538 # Avoid filename issues with these simple names
539 fn = str(self.created)
539 fn = str(self.created)
540 self.opener.write(fn, data)
540 self.opener.write(fn, data)
541 self.created += 1
541 self.created += 1
542 self.files[fname] = (fn, mode, copied)
542 self.files[fname] = (fn, mode, copied)
543
543
544 def getfile(self, fname):
544 def getfile(self, fname):
545 if fname in self.data:
545 if fname in self.data:
546 return self.data[fname]
546 return self.data[fname]
547 if not self.opener or fname not in self.files:
547 if not self.opener or fname not in self.files:
548 return None, None, None
548 return None, None, None
549 fn, mode, copied = self.files[fname]
549 fn, mode, copied = self.files[fname]
550 return self.opener.read(fn), mode, copied
550 return self.opener.read(fn), mode, copied
551
551
552 def close(self):
552 def close(self):
553 if self.opener:
553 if self.opener:
554 shutil.rmtree(self.opener.base)
554 shutil.rmtree(self.opener.base)
555
555
556 class repobackend(abstractbackend):
556 class repobackend(abstractbackend):
557 def __init__(self, ui, repo, ctx, store):
557 def __init__(self, ui, repo, ctx, store):
558 super(repobackend, self).__init__(ui)
558 super(repobackend, self).__init__(ui)
559 self.repo = repo
559 self.repo = repo
560 self.ctx = ctx
560 self.ctx = ctx
561 self.store = store
561 self.store = store
562 self.changed = set()
562 self.changed = set()
563 self.removed = set()
563 self.removed = set()
564 self.copied = {}
564 self.copied = {}
565
565
566 def _checkknown(self, fname):
566 def _checkknown(self, fname):
567 if fname not in self.ctx:
567 if fname not in self.ctx:
568 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
568 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
569
569
570 def getfile(self, fname):
570 def getfile(self, fname):
571 try:
571 try:
572 fctx = self.ctx[fname]
572 fctx = self.ctx[fname]
573 except error.LookupError:
573 except error.LookupError:
574 return None, None
574 return None, None
575 flags = fctx.flags()
575 flags = fctx.flags()
576 return fctx.data(), ('l' in flags, 'x' in flags)
576 return fctx.data(), ('l' in flags, 'x' in flags)
577
577
578 def setfile(self, fname, data, mode, copysource):
578 def setfile(self, fname, data, mode, copysource):
579 if copysource:
579 if copysource:
580 self._checkknown(copysource)
580 self._checkknown(copysource)
581 if data is None:
581 if data is None:
582 data = self.ctx[fname].data()
582 data = self.ctx[fname].data()
583 self.store.setfile(fname, data, mode, copysource)
583 self.store.setfile(fname, data, mode, copysource)
584 self.changed.add(fname)
584 self.changed.add(fname)
585 if copysource:
585 if copysource:
586 self.copied[fname] = copysource
586 self.copied[fname] = copysource
587
587
588 def unlink(self, fname):
588 def unlink(self, fname):
589 self._checkknown(fname)
589 self._checkknown(fname)
590 self.removed.add(fname)
590 self.removed.add(fname)
591
591
592 def exists(self, fname):
592 def exists(self, fname):
593 return fname in self.ctx
593 return fname in self.ctx
594
594
595 def close(self):
595 def close(self):
596 return self.changed | self.removed
596 return self.changed | self.removed
597
597
598 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
598 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
599 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
599 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
600 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
600 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
601 eolmodes = ['strict', 'crlf', 'lf', 'auto']
601 eolmodes = ['strict', 'crlf', 'lf', 'auto']
602
602
603 class patchfile(object):
603 class patchfile(object):
604 def __init__(self, ui, gp, backend, store, eolmode='strict'):
604 def __init__(self, ui, gp, backend, store, eolmode='strict'):
605 self.fname = gp.path
605 self.fname = gp.path
606 self.eolmode = eolmode
606 self.eolmode = eolmode
607 self.eol = None
607 self.eol = None
608 self.backend = backend
608 self.backend = backend
609 self.ui = ui
609 self.ui = ui
610 self.lines = []
610 self.lines = []
611 self.exists = False
611 self.exists = False
612 self.missing = True
612 self.missing = True
613 self.mode = gp.mode
613 self.mode = gp.mode
614 self.copysource = gp.oldpath
614 self.copysource = gp.oldpath
615 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
615 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
616 self.remove = gp.op == 'DELETE'
616 self.remove = gp.op == 'DELETE'
617 if self.copysource is None:
617 if self.copysource is None:
618 data, mode = backend.getfile(self.fname)
618 data, mode = backend.getfile(self.fname)
619 else:
619 else:
620 data, mode = store.getfile(self.copysource)[:2]
620 data, mode = store.getfile(self.copysource)[:2]
621 if data is not None:
621 if data is not None:
622 self.exists = self.copysource is None or backend.exists(self.fname)
622 self.exists = self.copysource is None or backend.exists(self.fname)
623 self.missing = False
623 self.missing = False
624 if data:
624 if data:
625 self.lines = mdiff.splitnewlines(data)
625 self.lines = mdiff.splitnewlines(data)
626 if self.mode is None:
626 if self.mode is None:
627 self.mode = mode
627 self.mode = mode
628 if self.lines:
628 if self.lines:
629 # Normalize line endings
629 # Normalize line endings
630 if self.lines[0].endswith('\r\n'):
630 if self.lines[0].endswith('\r\n'):
631 self.eol = '\r\n'
631 self.eol = '\r\n'
632 elif self.lines[0].endswith('\n'):
632 elif self.lines[0].endswith('\n'):
633 self.eol = '\n'
633 self.eol = '\n'
634 if eolmode != 'strict':
634 if eolmode != 'strict':
635 nlines = []
635 nlines = []
636 for l in self.lines:
636 for l in self.lines:
637 if l.endswith('\r\n'):
637 if l.endswith('\r\n'):
638 l = l[:-2] + '\n'
638 l = l[:-2] + '\n'
639 nlines.append(l)
639 nlines.append(l)
640 self.lines = nlines
640 self.lines = nlines
641 else:
641 else:
642 if self.create:
642 if self.create:
643 self.missing = False
643 self.missing = False
644 if self.mode is None:
644 if self.mode is None:
645 self.mode = (False, False)
645 self.mode = (False, False)
646 if self.missing:
646 if self.missing:
647 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
647 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
648
648
649 self.hash = {}
649 self.hash = {}
650 self.dirty = 0
650 self.dirty = 0
651 self.offset = 0
651 self.offset = 0
652 self.skew = 0
652 self.skew = 0
653 self.rej = []
653 self.rej = []
654 self.fileprinted = False
654 self.fileprinted = False
655 self.printfile(False)
655 self.printfile(False)
656 self.hunks = 0
656 self.hunks = 0
657
657
658 def writelines(self, fname, lines, mode):
658 def writelines(self, fname, lines, mode):
659 if self.eolmode == 'auto':
659 if self.eolmode == 'auto':
660 eol = self.eol
660 eol = self.eol
661 elif self.eolmode == 'crlf':
661 elif self.eolmode == 'crlf':
662 eol = '\r\n'
662 eol = '\r\n'
663 else:
663 else:
664 eol = '\n'
664 eol = '\n'
665
665
666 if self.eolmode != 'strict' and eol and eol != '\n':
666 if self.eolmode != 'strict' and eol and eol != '\n':
667 rawlines = []
667 rawlines = []
668 for l in lines:
668 for l in lines:
669 if l and l[-1] == '\n':
669 if l and l[-1] == '\n':
670 l = l[:-1] + eol
670 l = l[:-1] + eol
671 rawlines.append(l)
671 rawlines.append(l)
672 lines = rawlines
672 lines = rawlines
673
673
674 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
674 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
675
675
676 def printfile(self, warn):
676 def printfile(self, warn):
677 if self.fileprinted:
677 if self.fileprinted:
678 return
678 return
679 if warn or self.ui.verbose:
679 if warn or self.ui.verbose:
680 self.fileprinted = True
680 self.fileprinted = True
681 s = _("patching file %s\n") % self.fname
681 s = _("patching file %s\n") % self.fname
682 if warn:
682 if warn:
683 self.ui.warn(s)
683 self.ui.warn(s)
684 else:
684 else:
685 self.ui.note(s)
685 self.ui.note(s)
686
686
687
687
688 def findlines(self, l, linenum):
688 def findlines(self, l, linenum):
689 # looks through the hash and finds candidate lines. The
689 # looks through the hash and finds candidate lines. The
690 # result is a list of line numbers sorted based on distance
690 # result is a list of line numbers sorted based on distance
691 # from linenum
691 # from linenum
692
692
693 cand = self.hash.get(l, [])
693 cand = self.hash.get(l, [])
694 if len(cand) > 1:
694 if len(cand) > 1:
695 # resort our list of potentials forward then back.
695 # resort our list of potentials forward then back.
696 cand.sort(key=lambda x: abs(x - linenum))
696 cand.sort(key=lambda x: abs(x - linenum))
697 return cand
697 return cand
698
698
699 def write_rej(self):
699 def write_rej(self):
700 # our rejects are a little different from patch(1). This always
700 # our rejects are a little different from patch(1). This always
701 # creates rejects in the same form as the original patch. A file
701 # creates rejects in the same form as the original patch. A file
702 # header is inserted so that you can run the reject through patch again
702 # header is inserted so that you can run the reject through patch again
703 # without having to type the filename.
703 # without having to type the filename.
704 if not self.rej:
704 if not self.rej:
705 return
705 return
706 base = os.path.basename(self.fname)
706 base = os.path.basename(self.fname)
707 lines = ["--- %s\n+++ %s\n" % (base, base)]
707 lines = ["--- %s\n+++ %s\n" % (base, base)]
708 for x in self.rej:
708 for x in self.rej:
709 for l in x.hunk:
709 for l in x.hunk:
710 lines.append(l)
710 lines.append(l)
711 if l[-1] != '\n':
711 if l[-1] != '\n':
712 lines.append("\n\ No newline at end of file\n")
712 lines.append("\n\ No newline at end of file\n")
713 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
713 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
714
714
715 def apply(self, h):
715 def apply(self, h):
716 if not h.complete():
716 if not h.complete():
717 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
717 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
718 (h.number, h.desc, len(h.a), h.lena, len(h.b),
718 (h.number, h.desc, len(h.a), h.lena, len(h.b),
719 h.lenb))
719 h.lenb))
720
720
721 self.hunks += 1
721 self.hunks += 1
722
722
723 if self.missing:
723 if self.missing:
724 self.rej.append(h)
724 self.rej.append(h)
725 return -1
725 return -1
726
726
727 if self.exists and self.create:
727 if self.exists and self.create:
728 if self.copysource:
728 if self.copysource:
729 self.ui.warn(_("cannot create %s: destination already "
729 self.ui.warn(_("cannot create %s: destination already "
730 "exists\n") % self.fname)
730 "exists\n") % self.fname)
731 else:
731 else:
732 self.ui.warn(_("file %s already exists\n") % self.fname)
732 self.ui.warn(_("file %s already exists\n") % self.fname)
733 self.rej.append(h)
733 self.rej.append(h)
734 return -1
734 return -1
735
735
736 if isinstance(h, binhunk):
736 if isinstance(h, binhunk):
737 if self.remove:
737 if self.remove:
738 self.backend.unlink(self.fname)
738 self.backend.unlink(self.fname)
739 else:
739 else:
740 l = h.new(self.lines)
740 l = h.new(self.lines)
741 self.lines[:] = l
741 self.lines[:] = l
742 self.offset += len(l)
742 self.offset += len(l)
743 self.dirty = True
743 self.dirty = True
744 return 0
744 return 0
745
745
746 horig = h
746 horig = h
747 if (self.eolmode in ('crlf', 'lf')
747 if (self.eolmode in ('crlf', 'lf')
748 or self.eolmode == 'auto' and self.eol):
748 or self.eolmode == 'auto' and self.eol):
749 # If new eols are going to be normalized, then normalize
749 # If new eols are going to be normalized, then normalize
750 # hunk data before patching. Otherwise, preserve input
750 # hunk data before patching. Otherwise, preserve input
751 # line-endings.
751 # line-endings.
752 h = h.getnormalized()
752 h = h.getnormalized()
753
753
754 # fast case first, no offsets, no fuzz
754 # fast case first, no offsets, no fuzz
755 old, oldstart, new, newstart = h.fuzzit(0, False)
755 old, oldstart, new, newstart = h.fuzzit(0, False)
756 oldstart += self.offset
756 oldstart += self.offset
757 orig_start = oldstart
757 orig_start = oldstart
758 # if there's skew we want to emit the "(offset %d lines)" even
758 # if there's skew we want to emit the "(offset %d lines)" even
759 # when the hunk cleanly applies at start + skew, so skip the
759 # when the hunk cleanly applies at start + skew, so skip the
760 # fast case code
760 # fast case code
761 if (self.skew == 0 and
761 if (self.skew == 0 and
762 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
762 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
763 if self.remove:
763 if self.remove:
764 self.backend.unlink(self.fname)
764 self.backend.unlink(self.fname)
765 else:
765 else:
766 self.lines[oldstart:oldstart + len(old)] = new
766 self.lines[oldstart:oldstart + len(old)] = new
767 self.offset += len(new) - len(old)
767 self.offset += len(new) - len(old)
768 self.dirty = True
768 self.dirty = True
769 return 0
769 return 0
770
770
771 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
771 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
772 self.hash = {}
772 self.hash = {}
773 for x, s in enumerate(self.lines):
773 for x, s in enumerate(self.lines):
774 self.hash.setdefault(s, []).append(x)
774 self.hash.setdefault(s, []).append(x)
775
775
776 for fuzzlen in xrange(3):
776 for fuzzlen in xrange(3):
777 for toponly in [True, False]:
777 for toponly in [True, False]:
778 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
778 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
779 oldstart = oldstart + self.offset + self.skew
779 oldstart = oldstart + self.offset + self.skew
780 oldstart = min(oldstart, len(self.lines))
780 oldstart = min(oldstart, len(self.lines))
781 if old:
781 if old:
782 cand = self.findlines(old[0][1:], oldstart)
782 cand = self.findlines(old[0][1:], oldstart)
783 else:
783 else:
784 # Only adding lines with no or fuzzed context, just
784 # Only adding lines with no or fuzzed context, just
785 # take the skew in account
785 # take the skew in account
786 cand = [oldstart]
786 cand = [oldstart]
787
787
788 for l in cand:
788 for l in cand:
789 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
789 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
790 self.lines[l : l + len(old)] = new
790 self.lines[l : l + len(old)] = new
791 self.offset += len(new) - len(old)
791 self.offset += len(new) - len(old)
792 self.skew = l - orig_start
792 self.skew = l - orig_start
793 self.dirty = True
793 self.dirty = True
794 offset = l - orig_start - fuzzlen
794 offset = l - orig_start - fuzzlen
795 if fuzzlen:
795 if fuzzlen:
796 msg = _("Hunk #%d succeeded at %d "
796 msg = _("Hunk #%d succeeded at %d "
797 "with fuzz %d "
797 "with fuzz %d "
798 "(offset %d lines).\n")
798 "(offset %d lines).\n")
799 self.printfile(True)
799 self.printfile(True)
800 self.ui.warn(msg %
800 self.ui.warn(msg %
801 (h.number, l + 1, fuzzlen, offset))
801 (h.number, l + 1, fuzzlen, offset))
802 else:
802 else:
803 msg = _("Hunk #%d succeeded at %d "
803 msg = _("Hunk #%d succeeded at %d "
804 "(offset %d lines).\n")
804 "(offset %d lines).\n")
805 self.ui.note(msg % (h.number, l + 1, offset))
805 self.ui.note(msg % (h.number, l + 1, offset))
806 return fuzzlen
806 return fuzzlen
807 self.printfile(True)
807 self.printfile(True)
808 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
808 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
809 self.rej.append(horig)
809 self.rej.append(horig)
810 return -1
810 return -1
811
811
812 def close(self):
812 def close(self):
813 if self.dirty:
813 if self.dirty:
814 self.writelines(self.fname, self.lines, self.mode)
814 self.writelines(self.fname, self.lines, self.mode)
815 self.write_rej()
815 self.write_rej()
816 return len(self.rej)
816 return len(self.rej)
817
817
818 class header(object):
818 class header(object):
819 """patch header
819 """patch header
820 """
820 """
821 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
821 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
822 diff_re = re.compile('diff -r .* (.*)$')
822 diff_re = re.compile('diff -r .* (.*)$')
823 allhunks_re = re.compile('(?:index|deleted file) ')
823 allhunks_re = re.compile('(?:index|new file|deleted file) ')
824 pretty_re = re.compile('(?:new file|deleted file) ')
824 pretty_re = re.compile('(?:new file|deleted file) ')
825 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
825 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
826
826
827 def __init__(self, header):
827 def __init__(self, header):
828 self.header = header
828 self.header = header
829 self.hunks = []
829 self.hunks = []
830
830
831 def binary(self):
831 def binary(self):
832 return util.any(h.startswith('index ') for h in self.header)
832 return util.any(h.startswith('index ') for h in self.header)
833
833
834 def pretty(self, fp):
834 def pretty(self, fp):
835 for h in self.header:
835 for h in self.header:
836 if h.startswith('index '):
836 if h.startswith('index '):
837 fp.write(_('this modifies a binary file (all or nothing)\n'))
837 fp.write(_('this modifies a binary file (all or nothing)\n'))
838 break
838 break
839 if self.pretty_re.match(h):
839 if self.pretty_re.match(h):
840 fp.write(h)
840 fp.write(h)
841 if self.binary():
841 if self.binary():
842 fp.write(_('this is a binary file\n'))
842 fp.write(_('this is a binary file\n'))
843 break
843 break
844 if h.startswith('---'):
844 if h.startswith('---'):
845 fp.write(_('%d hunks, %d lines changed\n') %
845 fp.write(_('%d hunks, %d lines changed\n') %
846 (len(self.hunks),
846 (len(self.hunks),
847 sum([max(h.added, h.removed) for h in self.hunks])))
847 sum([max(h.added, h.removed) for h in self.hunks])))
848 break
848 break
849 fp.write(h)
849 fp.write(h)
850
850
851 def write(self, fp):
851 def write(self, fp):
852 fp.write(''.join(self.header))
852 fp.write(''.join(self.header))
853
853
854 def allhunks(self):
854 def allhunks(self):
855 return util.any(self.allhunks_re.match(h) for h in self.header)
855 return util.any(self.allhunks_re.match(h) for h in self.header)
856
856
857 def files(self):
857 def files(self):
858 match = self.diffgit_re.match(self.header[0])
858 match = self.diffgit_re.match(self.header[0])
859 if match:
859 if match:
860 fromfile, tofile = match.groups()
860 fromfile, tofile = match.groups()
861 if fromfile == tofile:
861 if fromfile == tofile:
862 return [fromfile]
862 return [fromfile]
863 return [fromfile, tofile]
863 return [fromfile, tofile]
864 else:
864 else:
865 return self.diff_re.match(self.header[0]).groups()
865 return self.diff_re.match(self.header[0]).groups()
866
866
867 def filename(self):
867 def filename(self):
868 return self.files()[-1]
868 return self.files()[-1]
869
869
870 def __repr__(self):
870 def __repr__(self):
871 return '<header %s>' % (' '.join(map(repr, self.files())))
871 return '<header %s>' % (' '.join(map(repr, self.files())))
872
872
873 def special(self):
873 def special(self):
874 return util.any(self.special_re.match(h) for h in self.header)
874 return util.any(self.special_re.match(h) for h in self.header)
875
875
876 class recordhunk(object):
876 class recordhunk(object):
877 """patch hunk
877 """patch hunk
878
878
879 XXX shouldn't we merge this with the other hunk class?
879 XXX shouldn't we merge this with the other hunk class?
880 """
880 """
881 maxcontext = 3
881 maxcontext = 3
882
882
883 def __init__(self, header, fromline, toline, proc, before, hunk, after):
883 def __init__(self, header, fromline, toline, proc, before, hunk, after):
884 def trimcontext(number, lines):
884 def trimcontext(number, lines):
885 delta = len(lines) - self.maxcontext
885 delta = len(lines) - self.maxcontext
886 if False and delta > 0:
886 if False and delta > 0:
887 return number + delta, lines[:self.maxcontext]
887 return number + delta, lines[:self.maxcontext]
888 return number, lines
888 return number, lines
889
889
890 self.header = header
890 self.header = header
891 self.fromline, self.before = trimcontext(fromline, before)
891 self.fromline, self.before = trimcontext(fromline, before)
892 self.toline, self.after = trimcontext(toline, after)
892 self.toline, self.after = trimcontext(toline, after)
893 self.proc = proc
893 self.proc = proc
894 self.hunk = hunk
894 self.hunk = hunk
895 self.added, self.removed = self.countchanges(self.hunk)
895 self.added, self.removed = self.countchanges(self.hunk)
896
896
897 def __eq__(self, v):
897 def __eq__(self, v):
898 if not isinstance(v, recordhunk):
898 if not isinstance(v, recordhunk):
899 return False
899 return False
900
900
901 return ((v.hunk == self.hunk) and
901 return ((v.hunk == self.hunk) and
902 (v.proc == self.proc) and
902 (v.proc == self.proc) and
903 (self.fromline == v.fromline) and
903 (self.fromline == v.fromline) and
904 (self.header.files() == v.header.files()))
904 (self.header.files() == v.header.files()))
905
905
906 def __hash__(self):
906 def __hash__(self):
907 return hash((tuple(self.hunk),
907 return hash((tuple(self.hunk),
908 tuple(self.header.files()),
908 tuple(self.header.files()),
909 self.fromline,
909 self.fromline,
910 self.proc))
910 self.proc))
911
911
912 def countchanges(self, hunk):
912 def countchanges(self, hunk):
913 """hunk -> (n+,n-)"""
913 """hunk -> (n+,n-)"""
914 add = len([h for h in hunk if h[0] == '+'])
914 add = len([h for h in hunk if h[0] == '+'])
915 rem = len([h for h in hunk if h[0] == '-'])
915 rem = len([h for h in hunk if h[0] == '-'])
916 return add, rem
916 return add, rem
917
917
918 def write(self, fp):
918 def write(self, fp):
919 delta = len(self.before) + len(self.after)
919 delta = len(self.before) + len(self.after)
920 if self.after and self.after[-1] == '\\ No newline at end of file\n':
920 if self.after and self.after[-1] == '\\ No newline at end of file\n':
921 delta -= 1
921 delta -= 1
922 fromlen = delta + self.removed
922 fromlen = delta + self.removed
923 tolen = delta + self.added
923 tolen = delta + self.added
924 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
924 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
925 (self.fromline, fromlen, self.toline, tolen,
925 (self.fromline, fromlen, self.toline, tolen,
926 self.proc and (' ' + self.proc)))
926 self.proc and (' ' + self.proc)))
927 fp.write(''.join(self.before + self.hunk + self.after))
927 fp.write(''.join(self.before + self.hunk + self.after))
928
928
929 pretty = write
929 pretty = write
930
930
931 def filename(self):
931 def filename(self):
932 return self.header.filename()
932 return self.header.filename()
933
933
934 def __repr__(self):
934 def __repr__(self):
935 return '<hunk %r@%d>' % (self.filename(), self.fromline)
935 return '<hunk %r@%d>' % (self.filename(), self.fromline)
936
936
937 def filterpatch(ui, headers):
937 def filterpatch(ui, headers):
938 """Interactively filter patch chunks into applied-only chunks"""
938 """Interactively filter patch chunks into applied-only chunks"""
939
939
940 def prompt(skipfile, skipall, query, chunk):
940 def prompt(skipfile, skipall, query, chunk):
941 """prompt query, and process base inputs
941 """prompt query, and process base inputs
942
942
943 - y/n for the rest of file
943 - y/n for the rest of file
944 - y/n for the rest
944 - y/n for the rest
945 - ? (help)
945 - ? (help)
946 - q (quit)
946 - q (quit)
947
947
948 Return True/False and possibly updated skipfile and skipall.
948 Return True/False and possibly updated skipfile and skipall.
949 """
949 """
950 newpatches = None
950 newpatches = None
951 if skipall is not None:
951 if skipall is not None:
952 return skipall, skipfile, skipall, newpatches
952 return skipall, skipfile, skipall, newpatches
953 if skipfile is not None:
953 if skipfile is not None:
954 return skipfile, skipfile, skipall, newpatches
954 return skipfile, skipfile, skipall, newpatches
955 while True:
955 while True:
956 resps = _('[Ynesfdaq?]'
956 resps = _('[Ynesfdaq?]'
957 '$$ &Yes, record this change'
957 '$$ &Yes, record this change'
958 '$$ &No, skip this change'
958 '$$ &No, skip this change'
959 '$$ &Edit this change manually'
959 '$$ &Edit this change manually'
960 '$$ &Skip remaining changes to this file'
960 '$$ &Skip remaining changes to this file'
961 '$$ Record remaining changes to this &file'
961 '$$ Record remaining changes to this &file'
962 '$$ &Done, skip remaining changes and files'
962 '$$ &Done, skip remaining changes and files'
963 '$$ Record &all changes to all remaining files'
963 '$$ Record &all changes to all remaining files'
964 '$$ &Quit, recording no changes'
964 '$$ &Quit, recording no changes'
965 '$$ &? (display help)')
965 '$$ &? (display help)')
966 r = ui.promptchoice("%s %s" % (query, resps))
966 r = ui.promptchoice("%s %s" % (query, resps))
967 ui.write("\n")
967 ui.write("\n")
968 if r == 8: # ?
968 if r == 8: # ?
969 for c, t in ui.extractchoices(resps)[1]:
969 for c, t in ui.extractchoices(resps)[1]:
970 ui.write('%s - %s\n' % (c, t.lower()))
970 ui.write('%s - %s\n' % (c, t.lower()))
971 continue
971 continue
972 elif r == 0: # yes
972 elif r == 0: # yes
973 ret = True
973 ret = True
974 elif r == 1: # no
974 elif r == 1: # no
975 ret = False
975 ret = False
976 elif r == 2: # Edit patch
976 elif r == 2: # Edit patch
977 if chunk is None:
977 if chunk is None:
978 ui.write(_('cannot edit patch for whole file'))
978 ui.write(_('cannot edit patch for whole file'))
979 ui.write("\n")
979 ui.write("\n")
980 continue
980 continue
981 if chunk.header.binary():
981 if chunk.header.binary():
982 ui.write(_('cannot edit patch for binary file'))
982 ui.write(_('cannot edit patch for binary file'))
983 ui.write("\n")
983 ui.write("\n")
984 continue
984 continue
985 # Patch comment based on the Git one (based on comment at end of
985 # Patch comment based on the Git one (based on comment at end of
986 # http://mercurial.selenic.com/wiki/RecordExtension)
986 # http://mercurial.selenic.com/wiki/RecordExtension)
987 phelp = '---' + _("""
987 phelp = '---' + _("""
988 To remove '-' lines, make them ' ' lines (context).
988 To remove '-' lines, make them ' ' lines (context).
989 To remove '+' lines, delete them.
989 To remove '+' lines, delete them.
990 Lines starting with # will be removed from the patch.
990 Lines starting with # will be removed from the patch.
991
991
992 If the patch applies cleanly, the edited hunk will immediately be
992 If the patch applies cleanly, the edited hunk will immediately be
993 added to the record list. If it does not apply cleanly, a rejects
993 added to the record list. If it does not apply cleanly, a rejects
994 file will be generated: you can use that when you try again. If
994 file will be generated: you can use that when you try again. If
995 all lines of the hunk are removed, then the edit is aborted and
995 all lines of the hunk are removed, then the edit is aborted and
996 the hunk is left unchanged.
996 the hunk is left unchanged.
997 """)
997 """)
998 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
998 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
999 suffix=".diff", text=True)
999 suffix=".diff", text=True)
1000 ncpatchfp = None
1000 ncpatchfp = None
1001 try:
1001 try:
1002 # Write the initial patch
1002 # Write the initial patch
1003 f = os.fdopen(patchfd, "w")
1003 f = os.fdopen(patchfd, "w")
1004 chunk.header.write(f)
1004 chunk.header.write(f)
1005 chunk.write(f)
1005 chunk.write(f)
1006 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1006 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1007 f.close()
1007 f.close()
1008 # Start the editor and wait for it to complete
1008 # Start the editor and wait for it to complete
1009 editor = ui.geteditor()
1009 editor = ui.geteditor()
1010 ui.system("%s \"%s\"" % (editor, patchfn),
1010 ui.system("%s \"%s\"" % (editor, patchfn),
1011 environ={'HGUSER': ui.username()},
1011 environ={'HGUSER': ui.username()},
1012 onerr=util.Abort, errprefix=_("edit failed"))
1012 onerr=util.Abort, errprefix=_("edit failed"))
1013 # Remove comment lines
1013 # Remove comment lines
1014 patchfp = open(patchfn)
1014 patchfp = open(patchfn)
1015 ncpatchfp = cStringIO.StringIO()
1015 ncpatchfp = cStringIO.StringIO()
1016 for line in patchfp:
1016 for line in patchfp:
1017 if not line.startswith('#'):
1017 if not line.startswith('#'):
1018 ncpatchfp.write(line)
1018 ncpatchfp.write(line)
1019 patchfp.close()
1019 patchfp.close()
1020 ncpatchfp.seek(0)
1020 ncpatchfp.seek(0)
1021 newpatches = parsepatch(ncpatchfp)
1021 newpatches = parsepatch(ncpatchfp)
1022 finally:
1022 finally:
1023 os.unlink(patchfn)
1023 os.unlink(patchfn)
1024 del ncpatchfp
1024 del ncpatchfp
1025 # Signal that the chunk shouldn't be applied as-is, but
1025 # Signal that the chunk shouldn't be applied as-is, but
1026 # provide the new patch to be used instead.
1026 # provide the new patch to be used instead.
1027 ret = False
1027 ret = False
1028 elif r == 3: # Skip
1028 elif r == 3: # Skip
1029 ret = skipfile = False
1029 ret = skipfile = False
1030 elif r == 4: # file (Record remaining)
1030 elif r == 4: # file (Record remaining)
1031 ret = skipfile = True
1031 ret = skipfile = True
1032 elif r == 5: # done, skip remaining
1032 elif r == 5: # done, skip remaining
1033 ret = skipall = False
1033 ret = skipall = False
1034 elif r == 6: # all
1034 elif r == 6: # all
1035 ret = skipall = True
1035 ret = skipall = True
1036 elif r == 7: # quit
1036 elif r == 7: # quit
1037 raise util.Abort(_('user quit'))
1037 raise util.Abort(_('user quit'))
1038 return ret, skipfile, skipall, newpatches
1038 return ret, skipfile, skipall, newpatches
1039
1039
1040 seen = set()
1040 seen = set()
1041 applied = {} # 'filename' -> [] of chunks
1041 applied = {} # 'filename' -> [] of chunks
1042 skipfile, skipall = None, None
1042 skipfile, skipall = None, None
1043 pos, total = 1, sum(len(h.hunks) for h in headers)
1043 pos, total = 1, sum(len(h.hunks) for h in headers)
1044 for h in headers:
1044 for h in headers:
1045 pos += len(h.hunks)
1045 pos += len(h.hunks)
1046 skipfile = None
1046 skipfile = None
1047 fixoffset = 0
1047 fixoffset = 0
1048 hdr = ''.join(h.header)
1048 hdr = ''.join(h.header)
1049 if hdr in seen:
1049 if hdr in seen:
1050 continue
1050 continue
1051 seen.add(hdr)
1051 seen.add(hdr)
1052 if skipall is None:
1052 if skipall is None:
1053 h.pretty(ui)
1053 h.pretty(ui)
1054 msg = (_('examine changes to %s?') %
1054 msg = (_('examine changes to %s?') %
1055 _(' and ').join("'%s'" % f for f in h.files()))
1055 _(' and ').join("'%s'" % f for f in h.files()))
1056 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1056 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1057 if not r:
1057 if not r:
1058 continue
1058 continue
1059 applied[h.filename()] = [h]
1059 applied[h.filename()] = [h]
1060 if h.allhunks():
1060 if h.allhunks():
1061 applied[h.filename()] += h.hunks
1061 applied[h.filename()] += h.hunks
1062 continue
1062 continue
1063 for i, chunk in enumerate(h.hunks):
1063 for i, chunk in enumerate(h.hunks):
1064 if skipfile is None and skipall is None:
1064 if skipfile is None and skipall is None:
1065 chunk.pretty(ui)
1065 chunk.pretty(ui)
1066 if total == 1:
1066 if total == 1:
1067 msg = _("record this change to '%s'?") % chunk.filename()
1067 msg = _("record this change to '%s'?") % chunk.filename()
1068 else:
1068 else:
1069 idx = pos - len(h.hunks) + i
1069 idx = pos - len(h.hunks) + i
1070 msg = _("record change %d/%d to '%s'?") % (idx, total,
1070 msg = _("record change %d/%d to '%s'?") % (idx, total,
1071 chunk.filename())
1071 chunk.filename())
1072 r, skipfile, skipall, newpatches = prompt(skipfile,
1072 r, skipfile, skipall, newpatches = prompt(skipfile,
1073 skipall, msg, chunk)
1073 skipall, msg, chunk)
1074 if r:
1074 if r:
1075 if fixoffset:
1075 if fixoffset:
1076 chunk = copy.copy(chunk)
1076 chunk = copy.copy(chunk)
1077 chunk.toline += fixoffset
1077 chunk.toline += fixoffset
1078 applied[chunk.filename()].append(chunk)
1078 applied[chunk.filename()].append(chunk)
1079 elif newpatches is not None:
1079 elif newpatches is not None:
1080 for newpatch in newpatches:
1080 for newpatch in newpatches:
1081 for newhunk in newpatch.hunks:
1081 for newhunk in newpatch.hunks:
1082 if fixoffset:
1082 if fixoffset:
1083 newhunk.toline += fixoffset
1083 newhunk.toline += fixoffset
1084 applied[newhunk.filename()].append(newhunk)
1084 applied[newhunk.filename()].append(newhunk)
1085 else:
1085 else:
1086 fixoffset += chunk.removed - chunk.added
1086 fixoffset += chunk.removed - chunk.added
1087 return sum([h for h in applied.itervalues()
1087 return sum([h for h in applied.itervalues()
1088 if h[0].special() or len(h) > 1], [])
1088 if h[0].special() or len(h) > 1], [])
1089 class hunk(object):
1089 class hunk(object):
1090 def __init__(self, desc, num, lr, context):
1090 def __init__(self, desc, num, lr, context):
1091 self.number = num
1091 self.number = num
1092 self.desc = desc
1092 self.desc = desc
1093 self.hunk = [desc]
1093 self.hunk = [desc]
1094 self.a = []
1094 self.a = []
1095 self.b = []
1095 self.b = []
1096 self.starta = self.lena = None
1096 self.starta = self.lena = None
1097 self.startb = self.lenb = None
1097 self.startb = self.lenb = None
1098 if lr is not None:
1098 if lr is not None:
1099 if context:
1099 if context:
1100 self.read_context_hunk(lr)
1100 self.read_context_hunk(lr)
1101 else:
1101 else:
1102 self.read_unified_hunk(lr)
1102 self.read_unified_hunk(lr)
1103
1103
1104 def getnormalized(self):
1104 def getnormalized(self):
1105 """Return a copy with line endings normalized to LF."""
1105 """Return a copy with line endings normalized to LF."""
1106
1106
1107 def normalize(lines):
1107 def normalize(lines):
1108 nlines = []
1108 nlines = []
1109 for line in lines:
1109 for line in lines:
1110 if line.endswith('\r\n'):
1110 if line.endswith('\r\n'):
1111 line = line[:-2] + '\n'
1111 line = line[:-2] + '\n'
1112 nlines.append(line)
1112 nlines.append(line)
1113 return nlines
1113 return nlines
1114
1114
1115 # Dummy object, it is rebuilt manually
1115 # Dummy object, it is rebuilt manually
1116 nh = hunk(self.desc, self.number, None, None)
1116 nh = hunk(self.desc, self.number, None, None)
1117 nh.number = self.number
1117 nh.number = self.number
1118 nh.desc = self.desc
1118 nh.desc = self.desc
1119 nh.hunk = self.hunk
1119 nh.hunk = self.hunk
1120 nh.a = normalize(self.a)
1120 nh.a = normalize(self.a)
1121 nh.b = normalize(self.b)
1121 nh.b = normalize(self.b)
1122 nh.starta = self.starta
1122 nh.starta = self.starta
1123 nh.startb = self.startb
1123 nh.startb = self.startb
1124 nh.lena = self.lena
1124 nh.lena = self.lena
1125 nh.lenb = self.lenb
1125 nh.lenb = self.lenb
1126 return nh
1126 return nh
1127
1127
1128 def read_unified_hunk(self, lr):
1128 def read_unified_hunk(self, lr):
1129 m = unidesc.match(self.desc)
1129 m = unidesc.match(self.desc)
1130 if not m:
1130 if not m:
1131 raise PatchError(_("bad hunk #%d") % self.number)
1131 raise PatchError(_("bad hunk #%d") % self.number)
1132 self.starta, self.lena, self.startb, self.lenb = m.groups()
1132 self.starta, self.lena, self.startb, self.lenb = m.groups()
1133 if self.lena is None:
1133 if self.lena is None:
1134 self.lena = 1
1134 self.lena = 1
1135 else:
1135 else:
1136 self.lena = int(self.lena)
1136 self.lena = int(self.lena)
1137 if self.lenb is None:
1137 if self.lenb is None:
1138 self.lenb = 1
1138 self.lenb = 1
1139 else:
1139 else:
1140 self.lenb = int(self.lenb)
1140 self.lenb = int(self.lenb)
1141 self.starta = int(self.starta)
1141 self.starta = int(self.starta)
1142 self.startb = int(self.startb)
1142 self.startb = int(self.startb)
1143 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1143 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1144 self.b)
1144 self.b)
1145 # if we hit eof before finishing out the hunk, the last line will
1145 # if we hit eof before finishing out the hunk, the last line will
1146 # be zero length. Lets try to fix it up.
1146 # be zero length. Lets try to fix it up.
1147 while len(self.hunk[-1]) == 0:
1147 while len(self.hunk[-1]) == 0:
1148 del self.hunk[-1]
1148 del self.hunk[-1]
1149 del self.a[-1]
1149 del self.a[-1]
1150 del self.b[-1]
1150 del self.b[-1]
1151 self.lena -= 1
1151 self.lena -= 1
1152 self.lenb -= 1
1152 self.lenb -= 1
1153 self._fixnewline(lr)
1153 self._fixnewline(lr)
1154
1154
1155 def read_context_hunk(self, lr):
1155 def read_context_hunk(self, lr):
1156 self.desc = lr.readline()
1156 self.desc = lr.readline()
1157 m = contextdesc.match(self.desc)
1157 m = contextdesc.match(self.desc)
1158 if not m:
1158 if not m:
1159 raise PatchError(_("bad hunk #%d") % self.number)
1159 raise PatchError(_("bad hunk #%d") % self.number)
1160 self.starta, aend = m.groups()
1160 self.starta, aend = m.groups()
1161 self.starta = int(self.starta)
1161 self.starta = int(self.starta)
1162 if aend is None:
1162 if aend is None:
1163 aend = self.starta
1163 aend = self.starta
1164 self.lena = int(aend) - self.starta
1164 self.lena = int(aend) - self.starta
1165 if self.starta:
1165 if self.starta:
1166 self.lena += 1
1166 self.lena += 1
1167 for x in xrange(self.lena):
1167 for x in xrange(self.lena):
1168 l = lr.readline()
1168 l = lr.readline()
1169 if l.startswith('---'):
1169 if l.startswith('---'):
1170 # lines addition, old block is empty
1170 # lines addition, old block is empty
1171 lr.push(l)
1171 lr.push(l)
1172 break
1172 break
1173 s = l[2:]
1173 s = l[2:]
1174 if l.startswith('- ') or l.startswith('! '):
1174 if l.startswith('- ') or l.startswith('! '):
1175 u = '-' + s
1175 u = '-' + s
1176 elif l.startswith(' '):
1176 elif l.startswith(' '):
1177 u = ' ' + s
1177 u = ' ' + s
1178 else:
1178 else:
1179 raise PatchError(_("bad hunk #%d old text line %d") %
1179 raise PatchError(_("bad hunk #%d old text line %d") %
1180 (self.number, x))
1180 (self.number, x))
1181 self.a.append(u)
1181 self.a.append(u)
1182 self.hunk.append(u)
1182 self.hunk.append(u)
1183
1183
1184 l = lr.readline()
1184 l = lr.readline()
1185 if l.startswith('\ '):
1185 if l.startswith('\ '):
1186 s = self.a[-1][:-1]
1186 s = self.a[-1][:-1]
1187 self.a[-1] = s
1187 self.a[-1] = s
1188 self.hunk[-1] = s
1188 self.hunk[-1] = s
1189 l = lr.readline()
1189 l = lr.readline()
1190 m = contextdesc.match(l)
1190 m = contextdesc.match(l)
1191 if not m:
1191 if not m:
1192 raise PatchError(_("bad hunk #%d") % self.number)
1192 raise PatchError(_("bad hunk #%d") % self.number)
1193 self.startb, bend = m.groups()
1193 self.startb, bend = m.groups()
1194 self.startb = int(self.startb)
1194 self.startb = int(self.startb)
1195 if bend is None:
1195 if bend is None:
1196 bend = self.startb
1196 bend = self.startb
1197 self.lenb = int(bend) - self.startb
1197 self.lenb = int(bend) - self.startb
1198 if self.startb:
1198 if self.startb:
1199 self.lenb += 1
1199 self.lenb += 1
1200 hunki = 1
1200 hunki = 1
1201 for x in xrange(self.lenb):
1201 for x in xrange(self.lenb):
1202 l = lr.readline()
1202 l = lr.readline()
1203 if l.startswith('\ '):
1203 if l.startswith('\ '):
1204 # XXX: the only way to hit this is with an invalid line range.
1204 # XXX: the only way to hit this is with an invalid line range.
1205 # The no-eol marker is not counted in the line range, but I
1205 # The no-eol marker is not counted in the line range, but I
1206 # guess there are diff(1) out there which behave differently.
1206 # guess there are diff(1) out there which behave differently.
1207 s = self.b[-1][:-1]
1207 s = self.b[-1][:-1]
1208 self.b[-1] = s
1208 self.b[-1] = s
1209 self.hunk[hunki - 1] = s
1209 self.hunk[hunki - 1] = s
1210 continue
1210 continue
1211 if not l:
1211 if not l:
1212 # line deletions, new block is empty and we hit EOF
1212 # line deletions, new block is empty and we hit EOF
1213 lr.push(l)
1213 lr.push(l)
1214 break
1214 break
1215 s = l[2:]
1215 s = l[2:]
1216 if l.startswith('+ ') or l.startswith('! '):
1216 if l.startswith('+ ') or l.startswith('! '):
1217 u = '+' + s
1217 u = '+' + s
1218 elif l.startswith(' '):
1218 elif l.startswith(' '):
1219 u = ' ' + s
1219 u = ' ' + s
1220 elif len(self.b) == 0:
1220 elif len(self.b) == 0:
1221 # line deletions, new block is empty
1221 # line deletions, new block is empty
1222 lr.push(l)
1222 lr.push(l)
1223 break
1223 break
1224 else:
1224 else:
1225 raise PatchError(_("bad hunk #%d old text line %d") %
1225 raise PatchError(_("bad hunk #%d old text line %d") %
1226 (self.number, x))
1226 (self.number, x))
1227 self.b.append(s)
1227 self.b.append(s)
1228 while True:
1228 while True:
1229 if hunki >= len(self.hunk):
1229 if hunki >= len(self.hunk):
1230 h = ""
1230 h = ""
1231 else:
1231 else:
1232 h = self.hunk[hunki]
1232 h = self.hunk[hunki]
1233 hunki += 1
1233 hunki += 1
1234 if h == u:
1234 if h == u:
1235 break
1235 break
1236 elif h.startswith('-'):
1236 elif h.startswith('-'):
1237 continue
1237 continue
1238 else:
1238 else:
1239 self.hunk.insert(hunki - 1, u)
1239 self.hunk.insert(hunki - 1, u)
1240 break
1240 break
1241
1241
1242 if not self.a:
1242 if not self.a:
1243 # this happens when lines were only added to the hunk
1243 # this happens when lines were only added to the hunk
1244 for x in self.hunk:
1244 for x in self.hunk:
1245 if x.startswith('-') or x.startswith(' '):
1245 if x.startswith('-') or x.startswith(' '):
1246 self.a.append(x)
1246 self.a.append(x)
1247 if not self.b:
1247 if not self.b:
1248 # this happens when lines were only deleted from the hunk
1248 # this happens when lines were only deleted from the hunk
1249 for x in self.hunk:
1249 for x in self.hunk:
1250 if x.startswith('+') or x.startswith(' '):
1250 if x.startswith('+') or x.startswith(' '):
1251 self.b.append(x[1:])
1251 self.b.append(x[1:])
1252 # @@ -start,len +start,len @@
1252 # @@ -start,len +start,len @@
1253 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1253 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1254 self.startb, self.lenb)
1254 self.startb, self.lenb)
1255 self.hunk[0] = self.desc
1255 self.hunk[0] = self.desc
1256 self._fixnewline(lr)
1256 self._fixnewline(lr)
1257
1257
1258 def _fixnewline(self, lr):
1258 def _fixnewline(self, lr):
1259 l = lr.readline()
1259 l = lr.readline()
1260 if l.startswith('\ '):
1260 if l.startswith('\ '):
1261 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1261 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1262 else:
1262 else:
1263 lr.push(l)
1263 lr.push(l)
1264
1264
1265 def complete(self):
1265 def complete(self):
1266 return len(self.a) == self.lena and len(self.b) == self.lenb
1266 return len(self.a) == self.lena and len(self.b) == self.lenb
1267
1267
1268 def _fuzzit(self, old, new, fuzz, toponly):
1268 def _fuzzit(self, old, new, fuzz, toponly):
1269 # this removes context lines from the top and bottom of list 'l'. It
1269 # this removes context lines from the top and bottom of list 'l'. It
1270 # checks the hunk to make sure only context lines are removed, and then
1270 # checks the hunk to make sure only context lines are removed, and then
1271 # returns a new shortened list of lines.
1271 # returns a new shortened list of lines.
1272 fuzz = min(fuzz, len(old))
1272 fuzz = min(fuzz, len(old))
1273 if fuzz:
1273 if fuzz:
1274 top = 0
1274 top = 0
1275 bot = 0
1275 bot = 0
1276 hlen = len(self.hunk)
1276 hlen = len(self.hunk)
1277 for x in xrange(hlen - 1):
1277 for x in xrange(hlen - 1):
1278 # the hunk starts with the @@ line, so use x+1
1278 # the hunk starts with the @@ line, so use x+1
1279 if self.hunk[x + 1][0] == ' ':
1279 if self.hunk[x + 1][0] == ' ':
1280 top += 1
1280 top += 1
1281 else:
1281 else:
1282 break
1282 break
1283 if not toponly:
1283 if not toponly:
1284 for x in xrange(hlen - 1):
1284 for x in xrange(hlen - 1):
1285 if self.hunk[hlen - bot - 1][0] == ' ':
1285 if self.hunk[hlen - bot - 1][0] == ' ':
1286 bot += 1
1286 bot += 1
1287 else:
1287 else:
1288 break
1288 break
1289
1289
1290 bot = min(fuzz, bot)
1290 bot = min(fuzz, bot)
1291 top = min(fuzz, top)
1291 top = min(fuzz, top)
1292 return old[top:len(old) - bot], new[top:len(new) - bot], top
1292 return old[top:len(old) - bot], new[top:len(new) - bot], top
1293 return old, new, 0
1293 return old, new, 0
1294
1294
1295 def fuzzit(self, fuzz, toponly):
1295 def fuzzit(self, fuzz, toponly):
1296 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1296 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1297 oldstart = self.starta + top
1297 oldstart = self.starta + top
1298 newstart = self.startb + top
1298 newstart = self.startb + top
1299 # zero length hunk ranges already have their start decremented
1299 # zero length hunk ranges already have their start decremented
1300 if self.lena and oldstart > 0:
1300 if self.lena and oldstart > 0:
1301 oldstart -= 1
1301 oldstart -= 1
1302 if self.lenb and newstart > 0:
1302 if self.lenb and newstart > 0:
1303 newstart -= 1
1303 newstart -= 1
1304 return old, oldstart, new, newstart
1304 return old, oldstart, new, newstart
1305
1305
1306 class binhunk(object):
1306 class binhunk(object):
1307 'A binary patch file.'
1307 'A binary patch file.'
1308 def __init__(self, lr, fname):
1308 def __init__(self, lr, fname):
1309 self.text = None
1309 self.text = None
1310 self.delta = False
1310 self.delta = False
1311 self.hunk = ['GIT binary patch\n']
1311 self.hunk = ['GIT binary patch\n']
1312 self._fname = fname
1312 self._fname = fname
1313 self._read(lr)
1313 self._read(lr)
1314
1314
1315 def complete(self):
1315 def complete(self):
1316 return self.text is not None
1316 return self.text is not None
1317
1317
1318 def new(self, lines):
1318 def new(self, lines):
1319 if self.delta:
1319 if self.delta:
1320 return [applybindelta(self.text, ''.join(lines))]
1320 return [applybindelta(self.text, ''.join(lines))]
1321 return [self.text]
1321 return [self.text]
1322
1322
1323 def _read(self, lr):
1323 def _read(self, lr):
1324 def getline(lr, hunk):
1324 def getline(lr, hunk):
1325 l = lr.readline()
1325 l = lr.readline()
1326 hunk.append(l)
1326 hunk.append(l)
1327 return l.rstrip('\r\n')
1327 return l.rstrip('\r\n')
1328
1328
1329 size = 0
1329 size = 0
1330 while True:
1330 while True:
1331 line = getline(lr, self.hunk)
1331 line = getline(lr, self.hunk)
1332 if not line:
1332 if not line:
1333 raise PatchError(_('could not extract "%s" binary data')
1333 raise PatchError(_('could not extract "%s" binary data')
1334 % self._fname)
1334 % self._fname)
1335 if line.startswith('literal '):
1335 if line.startswith('literal '):
1336 size = int(line[8:].rstrip())
1336 size = int(line[8:].rstrip())
1337 break
1337 break
1338 if line.startswith('delta '):
1338 if line.startswith('delta '):
1339 size = int(line[6:].rstrip())
1339 size = int(line[6:].rstrip())
1340 self.delta = True
1340 self.delta = True
1341 break
1341 break
1342 dec = []
1342 dec = []
1343 line = getline(lr, self.hunk)
1343 line = getline(lr, self.hunk)
1344 while len(line) > 1:
1344 while len(line) > 1:
1345 l = line[0]
1345 l = line[0]
1346 if l <= 'Z' and l >= 'A':
1346 if l <= 'Z' and l >= 'A':
1347 l = ord(l) - ord('A') + 1
1347 l = ord(l) - ord('A') + 1
1348 else:
1348 else:
1349 l = ord(l) - ord('a') + 27
1349 l = ord(l) - ord('a') + 27
1350 try:
1350 try:
1351 dec.append(base85.b85decode(line[1:])[:l])
1351 dec.append(base85.b85decode(line[1:])[:l])
1352 except ValueError, e:
1352 except ValueError, e:
1353 raise PatchError(_('could not decode "%s" binary patch: %s')
1353 raise PatchError(_('could not decode "%s" binary patch: %s')
1354 % (self._fname, str(e)))
1354 % (self._fname, str(e)))
1355 line = getline(lr, self.hunk)
1355 line = getline(lr, self.hunk)
1356 text = zlib.decompress(''.join(dec))
1356 text = zlib.decompress(''.join(dec))
1357 if len(text) != size:
1357 if len(text) != size:
1358 raise PatchError(_('"%s" length is %d bytes, should be %d')
1358 raise PatchError(_('"%s" length is %d bytes, should be %d')
1359 % (self._fname, len(text), size))
1359 % (self._fname, len(text), size))
1360 self.text = text
1360 self.text = text
1361
1361
1362 def parsefilename(str):
1362 def parsefilename(str):
1363 # --- filename \t|space stuff
1363 # --- filename \t|space stuff
1364 s = str[4:].rstrip('\r\n')
1364 s = str[4:].rstrip('\r\n')
1365 i = s.find('\t')
1365 i = s.find('\t')
1366 if i < 0:
1366 if i < 0:
1367 i = s.find(' ')
1367 i = s.find(' ')
1368 if i < 0:
1368 if i < 0:
1369 return s
1369 return s
1370 return s[:i]
1370 return s[:i]
1371
1371
1372 def parsepatch(originalchunks):
1372 def parsepatch(originalchunks):
1373 """patch -> [] of headers -> [] of hunks """
1373 """patch -> [] of headers -> [] of hunks """
1374 class parser(object):
1374 class parser(object):
1375 """patch parsing state machine"""
1375 """patch parsing state machine"""
1376 def __init__(self):
1376 def __init__(self):
1377 self.fromline = 0
1377 self.fromline = 0
1378 self.toline = 0
1378 self.toline = 0
1379 self.proc = ''
1379 self.proc = ''
1380 self.header = None
1380 self.header = None
1381 self.context = []
1381 self.context = []
1382 self.before = []
1382 self.before = []
1383 self.hunk = []
1383 self.hunk = []
1384 self.headers = []
1384 self.headers = []
1385
1385
1386 def addrange(self, limits):
1386 def addrange(self, limits):
1387 fromstart, fromend, tostart, toend, proc = limits
1387 fromstart, fromend, tostart, toend, proc = limits
1388 self.fromline = int(fromstart)
1388 self.fromline = int(fromstart)
1389 self.toline = int(tostart)
1389 self.toline = int(tostart)
1390 self.proc = proc
1390 self.proc = proc
1391
1391
1392 def addcontext(self, context):
1392 def addcontext(self, context):
1393 if self.hunk:
1393 if self.hunk:
1394 h = recordhunk(self.header, self.fromline, self.toline,
1394 h = recordhunk(self.header, self.fromline, self.toline,
1395 self.proc, self.before, self.hunk, context)
1395 self.proc, self.before, self.hunk, context)
1396 self.header.hunks.append(h)
1396 self.header.hunks.append(h)
1397 self.fromline += len(self.before) + h.removed
1397 self.fromline += len(self.before) + h.removed
1398 self.toline += len(self.before) + h.added
1398 self.toline += len(self.before) + h.added
1399 self.before = []
1399 self.before = []
1400 self.hunk = []
1400 self.hunk = []
1401 self.proc = ''
1401 self.proc = ''
1402 self.context = context
1402 self.context = context
1403
1403
1404 def addhunk(self, hunk):
1404 def addhunk(self, hunk):
1405 if self.context:
1405 if self.context:
1406 self.before = self.context
1406 self.before = self.context
1407 self.context = []
1407 self.context = []
1408 self.hunk = hunk
1408 self.hunk = hunk
1409
1409
1410 def newfile(self, hdr):
1410 def newfile(self, hdr):
1411 self.addcontext([])
1411 self.addcontext([])
1412 h = header(hdr)
1412 h = header(hdr)
1413 self.headers.append(h)
1413 self.headers.append(h)
1414 self.header = h
1414 self.header = h
1415
1415
1416 def addother(self, line):
1416 def addother(self, line):
1417 pass # 'other' lines are ignored
1417 pass # 'other' lines are ignored
1418
1418
1419 def finished(self):
1419 def finished(self):
1420 self.addcontext([])
1420 self.addcontext([])
1421 return self.headers
1421 return self.headers
1422
1422
1423 transitions = {
1423 transitions = {
1424 'file': {'context': addcontext,
1424 'file': {'context': addcontext,
1425 'file': newfile,
1425 'file': newfile,
1426 'hunk': addhunk,
1426 'hunk': addhunk,
1427 'range': addrange},
1427 'range': addrange},
1428 'context': {'file': newfile,
1428 'context': {'file': newfile,
1429 'hunk': addhunk,
1429 'hunk': addhunk,
1430 'range': addrange,
1430 'range': addrange,
1431 'other': addother},
1431 'other': addother},
1432 'hunk': {'context': addcontext,
1432 'hunk': {'context': addcontext,
1433 'file': newfile,
1433 'file': newfile,
1434 'range': addrange},
1434 'range': addrange},
1435 'range': {'context': addcontext,
1435 'range': {'context': addcontext,
1436 'hunk': addhunk},
1436 'hunk': addhunk},
1437 'other': {'other': addother},
1437 'other': {'other': addother},
1438 }
1438 }
1439
1439
1440 p = parser()
1440 p = parser()
1441 fp = cStringIO.StringIO()
1441 fp = cStringIO.StringIO()
1442 fp.write(''.join(originalchunks))
1442 fp.write(''.join(originalchunks))
1443 fp.seek(0)
1443 fp.seek(0)
1444
1444
1445 state = 'context'
1445 state = 'context'
1446 for newstate, data in scanpatch(fp):
1446 for newstate, data in scanpatch(fp):
1447 try:
1447 try:
1448 p.transitions[state][newstate](p, data)
1448 p.transitions[state][newstate](p, data)
1449 except KeyError:
1449 except KeyError:
1450 raise PatchError('unhandled transition: %s -> %s' %
1450 raise PatchError('unhandled transition: %s -> %s' %
1451 (state, newstate))
1451 (state, newstate))
1452 state = newstate
1452 state = newstate
1453 del fp
1453 del fp
1454 return p.finished()
1454 return p.finished()
1455
1455
1456 def pathtransform(path, strip, prefix):
1456 def pathtransform(path, strip, prefix):
1457 '''turn a path from a patch into a path suitable for the repository
1457 '''turn a path from a patch into a path suitable for the repository
1458
1458
1459 prefix, if not empty, is expected to be normalized with a / at the end.
1459 prefix, if not empty, is expected to be normalized with a / at the end.
1460
1460
1461 Returns (stripped components, path in repository).
1461 Returns (stripped components, path in repository).
1462
1462
1463 >>> pathtransform('a/b/c', 0, '')
1463 >>> pathtransform('a/b/c', 0, '')
1464 ('', 'a/b/c')
1464 ('', 'a/b/c')
1465 >>> pathtransform(' a/b/c ', 0, '')
1465 >>> pathtransform(' a/b/c ', 0, '')
1466 ('', ' a/b/c')
1466 ('', ' a/b/c')
1467 >>> pathtransform(' a/b/c ', 2, '')
1467 >>> pathtransform(' a/b/c ', 2, '')
1468 ('a/b/', 'c')
1468 ('a/b/', 'c')
1469 >>> pathtransform('a/b/c', 0, 'd/e/')
1469 >>> pathtransform('a/b/c', 0, 'd/e/')
1470 ('', 'd/e/a/b/c')
1470 ('', 'd/e/a/b/c')
1471 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1471 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1472 ('a//b/', 'd/e/c')
1472 ('a//b/', 'd/e/c')
1473 >>> pathtransform('a/b/c', 3, '')
1473 >>> pathtransform('a/b/c', 3, '')
1474 Traceback (most recent call last):
1474 Traceback (most recent call last):
1475 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1475 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1476 '''
1476 '''
1477 pathlen = len(path)
1477 pathlen = len(path)
1478 i = 0
1478 i = 0
1479 if strip == 0:
1479 if strip == 0:
1480 return '', prefix + path.rstrip()
1480 return '', prefix + path.rstrip()
1481 count = strip
1481 count = strip
1482 while count > 0:
1482 while count > 0:
1483 i = path.find('/', i)
1483 i = path.find('/', i)
1484 if i == -1:
1484 if i == -1:
1485 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1485 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1486 (count, strip, path))
1486 (count, strip, path))
1487 i += 1
1487 i += 1
1488 # consume '//' in the path
1488 # consume '//' in the path
1489 while i < pathlen - 1 and path[i] == '/':
1489 while i < pathlen - 1 and path[i] == '/':
1490 i += 1
1490 i += 1
1491 count -= 1
1491 count -= 1
1492 return path[:i].lstrip(), prefix + path[i:].rstrip()
1492 return path[:i].lstrip(), prefix + path[i:].rstrip()
1493
1493
1494 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1494 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1495 nulla = afile_orig == "/dev/null"
1495 nulla = afile_orig == "/dev/null"
1496 nullb = bfile_orig == "/dev/null"
1496 nullb = bfile_orig == "/dev/null"
1497 create = nulla and hunk.starta == 0 and hunk.lena == 0
1497 create = nulla and hunk.starta == 0 and hunk.lena == 0
1498 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1498 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1499 abase, afile = pathtransform(afile_orig, strip, prefix)
1499 abase, afile = pathtransform(afile_orig, strip, prefix)
1500 gooda = not nulla and backend.exists(afile)
1500 gooda = not nulla and backend.exists(afile)
1501 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1501 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1502 if afile == bfile:
1502 if afile == bfile:
1503 goodb = gooda
1503 goodb = gooda
1504 else:
1504 else:
1505 goodb = not nullb and backend.exists(bfile)
1505 goodb = not nullb and backend.exists(bfile)
1506 missing = not goodb and not gooda and not create
1506 missing = not goodb and not gooda and not create
1507
1507
1508 # some diff programs apparently produce patches where the afile is
1508 # some diff programs apparently produce patches where the afile is
1509 # not /dev/null, but afile starts with bfile
1509 # not /dev/null, but afile starts with bfile
1510 abasedir = afile[:afile.rfind('/') + 1]
1510 abasedir = afile[:afile.rfind('/') + 1]
1511 bbasedir = bfile[:bfile.rfind('/') + 1]
1511 bbasedir = bfile[:bfile.rfind('/') + 1]
1512 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1512 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1513 and hunk.starta == 0 and hunk.lena == 0):
1513 and hunk.starta == 0 and hunk.lena == 0):
1514 create = True
1514 create = True
1515 missing = False
1515 missing = False
1516
1516
1517 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1517 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1518 # diff is between a file and its backup. In this case, the original
1518 # diff is between a file and its backup. In this case, the original
1519 # file should be patched (see original mpatch code).
1519 # file should be patched (see original mpatch code).
1520 isbackup = (abase == bbase and bfile.startswith(afile))
1520 isbackup = (abase == bbase and bfile.startswith(afile))
1521 fname = None
1521 fname = None
1522 if not missing:
1522 if not missing:
1523 if gooda and goodb:
1523 if gooda and goodb:
1524 if isbackup:
1524 if isbackup:
1525 fname = afile
1525 fname = afile
1526 else:
1526 else:
1527 fname = bfile
1527 fname = bfile
1528 elif gooda:
1528 elif gooda:
1529 fname = afile
1529 fname = afile
1530
1530
1531 if not fname:
1531 if not fname:
1532 if not nullb:
1532 if not nullb:
1533 if isbackup:
1533 if isbackup:
1534 fname = afile
1534 fname = afile
1535 else:
1535 else:
1536 fname = bfile
1536 fname = bfile
1537 elif not nulla:
1537 elif not nulla:
1538 fname = afile
1538 fname = afile
1539 else:
1539 else:
1540 raise PatchError(_("undefined source and destination files"))
1540 raise PatchError(_("undefined source and destination files"))
1541
1541
1542 gp = patchmeta(fname)
1542 gp = patchmeta(fname)
1543 if create:
1543 if create:
1544 gp.op = 'ADD'
1544 gp.op = 'ADD'
1545 elif remove:
1545 elif remove:
1546 gp.op = 'DELETE'
1546 gp.op = 'DELETE'
1547 return gp
1547 return gp
1548
1548
1549 def scanpatch(fp):
1549 def scanpatch(fp):
1550 """like patch.iterhunks, but yield different events
1550 """like patch.iterhunks, but yield different events
1551
1551
1552 - ('file', [header_lines + fromfile + tofile])
1552 - ('file', [header_lines + fromfile + tofile])
1553 - ('context', [context_lines])
1553 - ('context', [context_lines])
1554 - ('hunk', [hunk_lines])
1554 - ('hunk', [hunk_lines])
1555 - ('range', (-start,len, +start,len, proc))
1555 - ('range', (-start,len, +start,len, proc))
1556 """
1556 """
1557 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1557 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1558 lr = linereader(fp)
1558 lr = linereader(fp)
1559
1559
1560 def scanwhile(first, p):
1560 def scanwhile(first, p):
1561 """scan lr while predicate holds"""
1561 """scan lr while predicate holds"""
1562 lines = [first]
1562 lines = [first]
1563 while True:
1563 while True:
1564 line = lr.readline()
1564 line = lr.readline()
1565 if not line:
1565 if not line:
1566 break
1566 break
1567 if p(line):
1567 if p(line):
1568 lines.append(line)
1568 lines.append(line)
1569 else:
1569 else:
1570 lr.push(line)
1570 lr.push(line)
1571 break
1571 break
1572 return lines
1572 return lines
1573
1573
1574 while True:
1574 while True:
1575 line = lr.readline()
1575 line = lr.readline()
1576 if not line:
1576 if not line:
1577 break
1577 break
1578 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1578 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1579 def notheader(line):
1579 def notheader(line):
1580 s = line.split(None, 1)
1580 s = line.split(None, 1)
1581 return not s or s[0] not in ('---', 'diff')
1581 return not s or s[0] not in ('---', 'diff')
1582 header = scanwhile(line, notheader)
1582 header = scanwhile(line, notheader)
1583 fromfile = lr.readline()
1583 fromfile = lr.readline()
1584 if fromfile.startswith('---'):
1584 if fromfile.startswith('---'):
1585 tofile = lr.readline()
1585 tofile = lr.readline()
1586 header += [fromfile, tofile]
1586 header += [fromfile, tofile]
1587 else:
1587 else:
1588 lr.push(fromfile)
1588 lr.push(fromfile)
1589 yield 'file', header
1589 yield 'file', header
1590 elif line[0] == ' ':
1590 elif line[0] == ' ':
1591 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1591 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1592 elif line[0] in '-+':
1592 elif line[0] in '-+':
1593 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1593 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1594 else:
1594 else:
1595 m = lines_re.match(line)
1595 m = lines_re.match(line)
1596 if m:
1596 if m:
1597 yield 'range', m.groups()
1597 yield 'range', m.groups()
1598 else:
1598 else:
1599 yield 'other', line
1599 yield 'other', line
1600
1600
1601 def scangitpatch(lr, firstline):
1601 def scangitpatch(lr, firstline):
1602 """
1602 """
1603 Git patches can emit:
1603 Git patches can emit:
1604 - rename a to b
1604 - rename a to b
1605 - change b
1605 - change b
1606 - copy a to c
1606 - copy a to c
1607 - change c
1607 - change c
1608
1608
1609 We cannot apply this sequence as-is, the renamed 'a' could not be
1609 We cannot apply this sequence as-is, the renamed 'a' could not be
1610 found for it would have been renamed already. And we cannot copy
1610 found for it would have been renamed already. And we cannot copy
1611 from 'b' instead because 'b' would have been changed already. So
1611 from 'b' instead because 'b' would have been changed already. So
1612 we scan the git patch for copy and rename commands so we can
1612 we scan the git patch for copy and rename commands so we can
1613 perform the copies ahead of time.
1613 perform the copies ahead of time.
1614 """
1614 """
1615 pos = 0
1615 pos = 0
1616 try:
1616 try:
1617 pos = lr.fp.tell()
1617 pos = lr.fp.tell()
1618 fp = lr.fp
1618 fp = lr.fp
1619 except IOError:
1619 except IOError:
1620 fp = cStringIO.StringIO(lr.fp.read())
1620 fp = cStringIO.StringIO(lr.fp.read())
1621 gitlr = linereader(fp)
1621 gitlr = linereader(fp)
1622 gitlr.push(firstline)
1622 gitlr.push(firstline)
1623 gitpatches = readgitpatch(gitlr)
1623 gitpatches = readgitpatch(gitlr)
1624 fp.seek(pos)
1624 fp.seek(pos)
1625 return gitpatches
1625 return gitpatches
1626
1626
1627 def iterhunks(fp):
1627 def iterhunks(fp):
1628 """Read a patch and yield the following events:
1628 """Read a patch and yield the following events:
1629 - ("file", afile, bfile, firsthunk): select a new target file.
1629 - ("file", afile, bfile, firsthunk): select a new target file.
1630 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1630 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1631 "file" event.
1631 "file" event.
1632 - ("git", gitchanges): current diff is in git format, gitchanges
1632 - ("git", gitchanges): current diff is in git format, gitchanges
1633 maps filenames to gitpatch records. Unique event.
1633 maps filenames to gitpatch records. Unique event.
1634 """
1634 """
1635 afile = ""
1635 afile = ""
1636 bfile = ""
1636 bfile = ""
1637 state = None
1637 state = None
1638 hunknum = 0
1638 hunknum = 0
1639 emitfile = newfile = False
1639 emitfile = newfile = False
1640 gitpatches = None
1640 gitpatches = None
1641
1641
1642 # our states
1642 # our states
1643 BFILE = 1
1643 BFILE = 1
1644 context = None
1644 context = None
1645 lr = linereader(fp)
1645 lr = linereader(fp)
1646
1646
1647 while True:
1647 while True:
1648 x = lr.readline()
1648 x = lr.readline()
1649 if not x:
1649 if not x:
1650 break
1650 break
1651 if state == BFILE and (
1651 if state == BFILE and (
1652 (not context and x[0] == '@')
1652 (not context and x[0] == '@')
1653 or (context is not False and x.startswith('***************'))
1653 or (context is not False and x.startswith('***************'))
1654 or x.startswith('GIT binary patch')):
1654 or x.startswith('GIT binary patch')):
1655 gp = None
1655 gp = None
1656 if (gitpatches and
1656 if (gitpatches and
1657 gitpatches[-1].ispatching(afile, bfile)):
1657 gitpatches[-1].ispatching(afile, bfile)):
1658 gp = gitpatches.pop()
1658 gp = gitpatches.pop()
1659 if x.startswith('GIT binary patch'):
1659 if x.startswith('GIT binary patch'):
1660 h = binhunk(lr, gp.path)
1660 h = binhunk(lr, gp.path)
1661 else:
1661 else:
1662 if context is None and x.startswith('***************'):
1662 if context is None and x.startswith('***************'):
1663 context = True
1663 context = True
1664 h = hunk(x, hunknum + 1, lr, context)
1664 h = hunk(x, hunknum + 1, lr, context)
1665 hunknum += 1
1665 hunknum += 1
1666 if emitfile:
1666 if emitfile:
1667 emitfile = False
1667 emitfile = False
1668 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1668 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1669 yield 'hunk', h
1669 yield 'hunk', h
1670 elif x.startswith('diff --git a/'):
1670 elif x.startswith('diff --git a/'):
1671 m = gitre.match(x.rstrip(' \r\n'))
1671 m = gitre.match(x.rstrip(' \r\n'))
1672 if not m:
1672 if not m:
1673 continue
1673 continue
1674 if gitpatches is None:
1674 if gitpatches is None:
1675 # scan whole input for git metadata
1675 # scan whole input for git metadata
1676 gitpatches = scangitpatch(lr, x)
1676 gitpatches = scangitpatch(lr, x)
1677 yield 'git', [g.copy() for g in gitpatches
1677 yield 'git', [g.copy() for g in gitpatches
1678 if g.op in ('COPY', 'RENAME')]
1678 if g.op in ('COPY', 'RENAME')]
1679 gitpatches.reverse()
1679 gitpatches.reverse()
1680 afile = 'a/' + m.group(1)
1680 afile = 'a/' + m.group(1)
1681 bfile = 'b/' + m.group(2)
1681 bfile = 'b/' + m.group(2)
1682 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1682 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1683 gp = gitpatches.pop()
1683 gp = gitpatches.pop()
1684 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1684 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1685 if not gitpatches:
1685 if not gitpatches:
1686 raise PatchError(_('failed to synchronize metadata for "%s"')
1686 raise PatchError(_('failed to synchronize metadata for "%s"')
1687 % afile[2:])
1687 % afile[2:])
1688 gp = gitpatches[-1]
1688 gp = gitpatches[-1]
1689 newfile = True
1689 newfile = True
1690 elif x.startswith('---'):
1690 elif x.startswith('---'):
1691 # check for a unified diff
1691 # check for a unified diff
1692 l2 = lr.readline()
1692 l2 = lr.readline()
1693 if not l2.startswith('+++'):
1693 if not l2.startswith('+++'):
1694 lr.push(l2)
1694 lr.push(l2)
1695 continue
1695 continue
1696 newfile = True
1696 newfile = True
1697 context = False
1697 context = False
1698 afile = parsefilename(x)
1698 afile = parsefilename(x)
1699 bfile = parsefilename(l2)
1699 bfile = parsefilename(l2)
1700 elif x.startswith('***'):
1700 elif x.startswith('***'):
1701 # check for a context diff
1701 # check for a context diff
1702 l2 = lr.readline()
1702 l2 = lr.readline()
1703 if not l2.startswith('---'):
1703 if not l2.startswith('---'):
1704 lr.push(l2)
1704 lr.push(l2)
1705 continue
1705 continue
1706 l3 = lr.readline()
1706 l3 = lr.readline()
1707 lr.push(l3)
1707 lr.push(l3)
1708 if not l3.startswith("***************"):
1708 if not l3.startswith("***************"):
1709 lr.push(l2)
1709 lr.push(l2)
1710 continue
1710 continue
1711 newfile = True
1711 newfile = True
1712 context = True
1712 context = True
1713 afile = parsefilename(x)
1713 afile = parsefilename(x)
1714 bfile = parsefilename(l2)
1714 bfile = parsefilename(l2)
1715
1715
1716 if newfile:
1716 if newfile:
1717 newfile = False
1717 newfile = False
1718 emitfile = True
1718 emitfile = True
1719 state = BFILE
1719 state = BFILE
1720 hunknum = 0
1720 hunknum = 0
1721
1721
1722 while gitpatches:
1722 while gitpatches:
1723 gp = gitpatches.pop()
1723 gp = gitpatches.pop()
1724 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1724 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1725
1725
1726 def applybindelta(binchunk, data):
1726 def applybindelta(binchunk, data):
1727 """Apply a binary delta hunk
1727 """Apply a binary delta hunk
1728 The algorithm used is the algorithm from git's patch-delta.c
1728 The algorithm used is the algorithm from git's patch-delta.c
1729 """
1729 """
1730 def deltahead(binchunk):
1730 def deltahead(binchunk):
1731 i = 0
1731 i = 0
1732 for c in binchunk:
1732 for c in binchunk:
1733 i += 1
1733 i += 1
1734 if not (ord(c) & 0x80):
1734 if not (ord(c) & 0x80):
1735 return i
1735 return i
1736 return i
1736 return i
1737 out = ""
1737 out = ""
1738 s = deltahead(binchunk)
1738 s = deltahead(binchunk)
1739 binchunk = binchunk[s:]
1739 binchunk = binchunk[s:]
1740 s = deltahead(binchunk)
1740 s = deltahead(binchunk)
1741 binchunk = binchunk[s:]
1741 binchunk = binchunk[s:]
1742 i = 0
1742 i = 0
1743 while i < len(binchunk):
1743 while i < len(binchunk):
1744 cmd = ord(binchunk[i])
1744 cmd = ord(binchunk[i])
1745 i += 1
1745 i += 1
1746 if (cmd & 0x80):
1746 if (cmd & 0x80):
1747 offset = 0
1747 offset = 0
1748 size = 0
1748 size = 0
1749 if (cmd & 0x01):
1749 if (cmd & 0x01):
1750 offset = ord(binchunk[i])
1750 offset = ord(binchunk[i])
1751 i += 1
1751 i += 1
1752 if (cmd & 0x02):
1752 if (cmd & 0x02):
1753 offset |= ord(binchunk[i]) << 8
1753 offset |= ord(binchunk[i]) << 8
1754 i += 1
1754 i += 1
1755 if (cmd & 0x04):
1755 if (cmd & 0x04):
1756 offset |= ord(binchunk[i]) << 16
1756 offset |= ord(binchunk[i]) << 16
1757 i += 1
1757 i += 1
1758 if (cmd & 0x08):
1758 if (cmd & 0x08):
1759 offset |= ord(binchunk[i]) << 24
1759 offset |= ord(binchunk[i]) << 24
1760 i += 1
1760 i += 1
1761 if (cmd & 0x10):
1761 if (cmd & 0x10):
1762 size = ord(binchunk[i])
1762 size = ord(binchunk[i])
1763 i += 1
1763 i += 1
1764 if (cmd & 0x20):
1764 if (cmd & 0x20):
1765 size |= ord(binchunk[i]) << 8
1765 size |= ord(binchunk[i]) << 8
1766 i += 1
1766 i += 1
1767 if (cmd & 0x40):
1767 if (cmd & 0x40):
1768 size |= ord(binchunk[i]) << 16
1768 size |= ord(binchunk[i]) << 16
1769 i += 1
1769 i += 1
1770 if size == 0:
1770 if size == 0:
1771 size = 0x10000
1771 size = 0x10000
1772 offset_end = offset + size
1772 offset_end = offset + size
1773 out += data[offset:offset_end]
1773 out += data[offset:offset_end]
1774 elif cmd != 0:
1774 elif cmd != 0:
1775 offset_end = i + cmd
1775 offset_end = i + cmd
1776 out += binchunk[i:offset_end]
1776 out += binchunk[i:offset_end]
1777 i += cmd
1777 i += cmd
1778 else:
1778 else:
1779 raise PatchError(_('unexpected delta opcode 0'))
1779 raise PatchError(_('unexpected delta opcode 0'))
1780 return out
1780 return out
1781
1781
1782 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1782 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1783 """Reads a patch from fp and tries to apply it.
1783 """Reads a patch from fp and tries to apply it.
1784
1784
1785 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1785 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1786 there was any fuzz.
1786 there was any fuzz.
1787
1787
1788 If 'eolmode' is 'strict', the patch content and patched file are
1788 If 'eolmode' is 'strict', the patch content and patched file are
1789 read in binary mode. Otherwise, line endings are ignored when
1789 read in binary mode. Otherwise, line endings are ignored when
1790 patching then normalized according to 'eolmode'.
1790 patching then normalized according to 'eolmode'.
1791 """
1791 """
1792 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1792 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1793 prefix=prefix, eolmode=eolmode)
1793 prefix=prefix, eolmode=eolmode)
1794
1794
1795 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1795 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1796 eolmode='strict'):
1796 eolmode='strict'):
1797
1797
1798 if prefix:
1798 if prefix:
1799 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1799 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1800 prefix)
1800 prefix)
1801 if prefix != '':
1801 if prefix != '':
1802 prefix += '/'
1802 prefix += '/'
1803 def pstrip(p):
1803 def pstrip(p):
1804 return pathtransform(p, strip - 1, prefix)[1]
1804 return pathtransform(p, strip - 1, prefix)[1]
1805
1805
1806 rejects = 0
1806 rejects = 0
1807 err = 0
1807 err = 0
1808 current_file = None
1808 current_file = None
1809
1809
1810 for state, values in iterhunks(fp):
1810 for state, values in iterhunks(fp):
1811 if state == 'hunk':
1811 if state == 'hunk':
1812 if not current_file:
1812 if not current_file:
1813 continue
1813 continue
1814 ret = current_file.apply(values)
1814 ret = current_file.apply(values)
1815 if ret > 0:
1815 if ret > 0:
1816 err = 1
1816 err = 1
1817 elif state == 'file':
1817 elif state == 'file':
1818 if current_file:
1818 if current_file:
1819 rejects += current_file.close()
1819 rejects += current_file.close()
1820 current_file = None
1820 current_file = None
1821 afile, bfile, first_hunk, gp = values
1821 afile, bfile, first_hunk, gp = values
1822 if gp:
1822 if gp:
1823 gp.path = pstrip(gp.path)
1823 gp.path = pstrip(gp.path)
1824 if gp.oldpath:
1824 if gp.oldpath:
1825 gp.oldpath = pstrip(gp.oldpath)
1825 gp.oldpath = pstrip(gp.oldpath)
1826 else:
1826 else:
1827 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1827 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1828 prefix)
1828 prefix)
1829 if gp.op == 'RENAME':
1829 if gp.op == 'RENAME':
1830 backend.unlink(gp.oldpath)
1830 backend.unlink(gp.oldpath)
1831 if not first_hunk:
1831 if not first_hunk:
1832 if gp.op == 'DELETE':
1832 if gp.op == 'DELETE':
1833 backend.unlink(gp.path)
1833 backend.unlink(gp.path)
1834 continue
1834 continue
1835 data, mode = None, None
1835 data, mode = None, None
1836 if gp.op in ('RENAME', 'COPY'):
1836 if gp.op in ('RENAME', 'COPY'):
1837 data, mode = store.getfile(gp.oldpath)[:2]
1837 data, mode = store.getfile(gp.oldpath)[:2]
1838 # FIXME: failing getfile has never been handled here
1838 # FIXME: failing getfile has never been handled here
1839 assert data is not None
1839 assert data is not None
1840 if gp.mode:
1840 if gp.mode:
1841 mode = gp.mode
1841 mode = gp.mode
1842 if gp.op == 'ADD':
1842 if gp.op == 'ADD':
1843 # Added files without content have no hunk and
1843 # Added files without content have no hunk and
1844 # must be created
1844 # must be created
1845 data = ''
1845 data = ''
1846 if data or mode:
1846 if data or mode:
1847 if (gp.op in ('ADD', 'RENAME', 'COPY')
1847 if (gp.op in ('ADD', 'RENAME', 'COPY')
1848 and backend.exists(gp.path)):
1848 and backend.exists(gp.path)):
1849 raise PatchError(_("cannot create %s: destination "
1849 raise PatchError(_("cannot create %s: destination "
1850 "already exists") % gp.path)
1850 "already exists") % gp.path)
1851 backend.setfile(gp.path, data, mode, gp.oldpath)
1851 backend.setfile(gp.path, data, mode, gp.oldpath)
1852 continue
1852 continue
1853 try:
1853 try:
1854 current_file = patcher(ui, gp, backend, store,
1854 current_file = patcher(ui, gp, backend, store,
1855 eolmode=eolmode)
1855 eolmode=eolmode)
1856 except PatchError, inst:
1856 except PatchError, inst:
1857 ui.warn(str(inst) + '\n')
1857 ui.warn(str(inst) + '\n')
1858 current_file = None
1858 current_file = None
1859 rejects += 1
1859 rejects += 1
1860 continue
1860 continue
1861 elif state == 'git':
1861 elif state == 'git':
1862 for gp in values:
1862 for gp in values:
1863 path = pstrip(gp.oldpath)
1863 path = pstrip(gp.oldpath)
1864 data, mode = backend.getfile(path)
1864 data, mode = backend.getfile(path)
1865 if data is None:
1865 if data is None:
1866 # The error ignored here will trigger a getfile()
1866 # The error ignored here will trigger a getfile()
1867 # error in a place more appropriate for error
1867 # error in a place more appropriate for error
1868 # handling, and will not interrupt the patching
1868 # handling, and will not interrupt the patching
1869 # process.
1869 # process.
1870 pass
1870 pass
1871 else:
1871 else:
1872 store.setfile(path, data, mode)
1872 store.setfile(path, data, mode)
1873 else:
1873 else:
1874 raise util.Abort(_('unsupported parser state: %s') % state)
1874 raise util.Abort(_('unsupported parser state: %s') % state)
1875
1875
1876 if current_file:
1876 if current_file:
1877 rejects += current_file.close()
1877 rejects += current_file.close()
1878
1878
1879 if rejects:
1879 if rejects:
1880 return -1
1880 return -1
1881 return err
1881 return err
1882
1882
1883 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1883 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1884 similarity):
1884 similarity):
1885 """use <patcher> to apply <patchname> to the working directory.
1885 """use <patcher> to apply <patchname> to the working directory.
1886 returns whether patch was applied with fuzz factor."""
1886 returns whether patch was applied with fuzz factor."""
1887
1887
1888 fuzz = False
1888 fuzz = False
1889 args = []
1889 args = []
1890 cwd = repo.root
1890 cwd = repo.root
1891 if cwd:
1891 if cwd:
1892 args.append('-d %s' % util.shellquote(cwd))
1892 args.append('-d %s' % util.shellquote(cwd))
1893 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1893 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1894 util.shellquote(patchname)))
1894 util.shellquote(patchname)))
1895 try:
1895 try:
1896 for line in fp:
1896 for line in fp:
1897 line = line.rstrip()
1897 line = line.rstrip()
1898 ui.note(line + '\n')
1898 ui.note(line + '\n')
1899 if line.startswith('patching file '):
1899 if line.startswith('patching file '):
1900 pf = util.parsepatchoutput(line)
1900 pf = util.parsepatchoutput(line)
1901 printed_file = False
1901 printed_file = False
1902 files.add(pf)
1902 files.add(pf)
1903 elif line.find('with fuzz') >= 0:
1903 elif line.find('with fuzz') >= 0:
1904 fuzz = True
1904 fuzz = True
1905 if not printed_file:
1905 if not printed_file:
1906 ui.warn(pf + '\n')
1906 ui.warn(pf + '\n')
1907 printed_file = True
1907 printed_file = True
1908 ui.warn(line + '\n')
1908 ui.warn(line + '\n')
1909 elif line.find('saving rejects to file') >= 0:
1909 elif line.find('saving rejects to file') >= 0:
1910 ui.warn(line + '\n')
1910 ui.warn(line + '\n')
1911 elif line.find('FAILED') >= 0:
1911 elif line.find('FAILED') >= 0:
1912 if not printed_file:
1912 if not printed_file:
1913 ui.warn(pf + '\n')
1913 ui.warn(pf + '\n')
1914 printed_file = True
1914 printed_file = True
1915 ui.warn(line + '\n')
1915 ui.warn(line + '\n')
1916 finally:
1916 finally:
1917 if files:
1917 if files:
1918 scmutil.marktouched(repo, files, similarity)
1918 scmutil.marktouched(repo, files, similarity)
1919 code = fp.close()
1919 code = fp.close()
1920 if code:
1920 if code:
1921 raise PatchError(_("patch command failed: %s") %
1921 raise PatchError(_("patch command failed: %s") %
1922 util.explainexit(code)[0])
1922 util.explainexit(code)[0])
1923 return fuzz
1923 return fuzz
1924
1924
1925 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1925 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1926 eolmode='strict'):
1926 eolmode='strict'):
1927 if files is None:
1927 if files is None:
1928 files = set()
1928 files = set()
1929 if eolmode is None:
1929 if eolmode is None:
1930 eolmode = ui.config('patch', 'eol', 'strict')
1930 eolmode = ui.config('patch', 'eol', 'strict')
1931 if eolmode.lower() not in eolmodes:
1931 if eolmode.lower() not in eolmodes:
1932 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1932 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1933 eolmode = eolmode.lower()
1933 eolmode = eolmode.lower()
1934
1934
1935 store = filestore()
1935 store = filestore()
1936 try:
1936 try:
1937 fp = open(patchobj, 'rb')
1937 fp = open(patchobj, 'rb')
1938 except TypeError:
1938 except TypeError:
1939 fp = patchobj
1939 fp = patchobj
1940 try:
1940 try:
1941 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1941 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1942 eolmode=eolmode)
1942 eolmode=eolmode)
1943 finally:
1943 finally:
1944 if fp != patchobj:
1944 if fp != patchobj:
1945 fp.close()
1945 fp.close()
1946 files.update(backend.close())
1946 files.update(backend.close())
1947 store.close()
1947 store.close()
1948 if ret < 0:
1948 if ret < 0:
1949 raise PatchError(_('patch failed to apply'))
1949 raise PatchError(_('patch failed to apply'))
1950 return ret > 0
1950 return ret > 0
1951
1951
1952 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
1952 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
1953 eolmode='strict', similarity=0):
1953 eolmode='strict', similarity=0):
1954 """use builtin patch to apply <patchobj> to the working directory.
1954 """use builtin patch to apply <patchobj> to the working directory.
1955 returns whether patch was applied with fuzz factor."""
1955 returns whether patch was applied with fuzz factor."""
1956 backend = workingbackend(ui, repo, similarity)
1956 backend = workingbackend(ui, repo, similarity)
1957 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1957 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1958
1958
1959 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
1959 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
1960 eolmode='strict'):
1960 eolmode='strict'):
1961 backend = repobackend(ui, repo, ctx, store)
1961 backend = repobackend(ui, repo, ctx, store)
1962 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1962 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1963
1963
1964 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1964 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1965 similarity=0):
1965 similarity=0):
1966 """Apply <patchname> to the working directory.
1966 """Apply <patchname> to the working directory.
1967
1967
1968 'eolmode' specifies how end of lines should be handled. It can be:
1968 'eolmode' specifies how end of lines should be handled. It can be:
1969 - 'strict': inputs are read in binary mode, EOLs are preserved
1969 - 'strict': inputs are read in binary mode, EOLs are preserved
1970 - 'crlf': EOLs are ignored when patching and reset to CRLF
1970 - 'crlf': EOLs are ignored when patching and reset to CRLF
1971 - 'lf': EOLs are ignored when patching and reset to LF
1971 - 'lf': EOLs are ignored when patching and reset to LF
1972 - None: get it from user settings, default to 'strict'
1972 - None: get it from user settings, default to 'strict'
1973 'eolmode' is ignored when using an external patcher program.
1973 'eolmode' is ignored when using an external patcher program.
1974
1974
1975 Returns whether patch was applied with fuzz factor.
1975 Returns whether patch was applied with fuzz factor.
1976 """
1976 """
1977 patcher = ui.config('ui', 'patch')
1977 patcher = ui.config('ui', 'patch')
1978 if files is None:
1978 if files is None:
1979 files = set()
1979 files = set()
1980 if patcher:
1980 if patcher:
1981 return _externalpatch(ui, repo, patcher, patchname, strip,
1981 return _externalpatch(ui, repo, patcher, patchname, strip,
1982 files, similarity)
1982 files, similarity)
1983 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1983 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1984 similarity)
1984 similarity)
1985
1985
1986 def changedfiles(ui, repo, patchpath, strip=1):
1986 def changedfiles(ui, repo, patchpath, strip=1):
1987 backend = fsbackend(ui, repo.root)
1987 backend = fsbackend(ui, repo.root)
1988 fp = open(patchpath, 'rb')
1988 fp = open(patchpath, 'rb')
1989 try:
1989 try:
1990 changed = set()
1990 changed = set()
1991 for state, values in iterhunks(fp):
1991 for state, values in iterhunks(fp):
1992 if state == 'file':
1992 if state == 'file':
1993 afile, bfile, first_hunk, gp = values
1993 afile, bfile, first_hunk, gp = values
1994 if gp:
1994 if gp:
1995 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1995 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1996 if gp.oldpath:
1996 if gp.oldpath:
1997 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1997 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1998 else:
1998 else:
1999 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1999 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2000 '')
2000 '')
2001 changed.add(gp.path)
2001 changed.add(gp.path)
2002 if gp.op == 'RENAME':
2002 if gp.op == 'RENAME':
2003 changed.add(gp.oldpath)
2003 changed.add(gp.oldpath)
2004 elif state not in ('hunk', 'git'):
2004 elif state not in ('hunk', 'git'):
2005 raise util.Abort(_('unsupported parser state: %s') % state)
2005 raise util.Abort(_('unsupported parser state: %s') % state)
2006 return changed
2006 return changed
2007 finally:
2007 finally:
2008 fp.close()
2008 fp.close()
2009
2009
2010 class GitDiffRequired(Exception):
2010 class GitDiffRequired(Exception):
2011 pass
2011 pass
2012
2012
2013 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2013 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2014 '''return diffopts with all features supported and parsed'''
2014 '''return diffopts with all features supported and parsed'''
2015 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2015 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2016 git=True, whitespace=True, formatchanging=True)
2016 git=True, whitespace=True, formatchanging=True)
2017
2017
2018 diffopts = diffallopts
2018 diffopts = diffallopts
2019
2019
2020 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2020 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2021 whitespace=False, formatchanging=False):
2021 whitespace=False, formatchanging=False):
2022 '''return diffopts with only opted-in features parsed
2022 '''return diffopts with only opted-in features parsed
2023
2023
2024 Features:
2024 Features:
2025 - git: git-style diffs
2025 - git: git-style diffs
2026 - whitespace: whitespace options like ignoreblanklines and ignorews
2026 - whitespace: whitespace options like ignoreblanklines and ignorews
2027 - formatchanging: options that will likely break or cause correctness issues
2027 - formatchanging: options that will likely break or cause correctness issues
2028 with most diff parsers
2028 with most diff parsers
2029 '''
2029 '''
2030 def get(key, name=None, getter=ui.configbool, forceplain=None):
2030 def get(key, name=None, getter=ui.configbool, forceplain=None):
2031 if opts:
2031 if opts:
2032 v = opts.get(key)
2032 v = opts.get(key)
2033 if v:
2033 if v:
2034 return v
2034 return v
2035 if forceplain is not None and ui.plain():
2035 if forceplain is not None and ui.plain():
2036 return forceplain
2036 return forceplain
2037 return getter(section, name or key, None, untrusted=untrusted)
2037 return getter(section, name or key, None, untrusted=untrusted)
2038
2038
2039 # core options, expected to be understood by every diff parser
2039 # core options, expected to be understood by every diff parser
2040 buildopts = {
2040 buildopts = {
2041 'nodates': get('nodates'),
2041 'nodates': get('nodates'),
2042 'showfunc': get('show_function', 'showfunc'),
2042 'showfunc': get('show_function', 'showfunc'),
2043 'context': get('unified', getter=ui.config),
2043 'context': get('unified', getter=ui.config),
2044 }
2044 }
2045
2045
2046 if git:
2046 if git:
2047 buildopts['git'] = get('git')
2047 buildopts['git'] = get('git')
2048 if whitespace:
2048 if whitespace:
2049 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2049 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2050 buildopts['ignorewsamount'] = get('ignore_space_change',
2050 buildopts['ignorewsamount'] = get('ignore_space_change',
2051 'ignorewsamount')
2051 'ignorewsamount')
2052 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2052 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2053 'ignoreblanklines')
2053 'ignoreblanklines')
2054 if formatchanging:
2054 if formatchanging:
2055 buildopts['text'] = opts and opts.get('text')
2055 buildopts['text'] = opts and opts.get('text')
2056 buildopts['nobinary'] = get('nobinary')
2056 buildopts['nobinary'] = get('nobinary')
2057 buildopts['noprefix'] = get('noprefix', forceplain=False)
2057 buildopts['noprefix'] = get('noprefix', forceplain=False)
2058
2058
2059 return mdiff.diffopts(**buildopts)
2059 return mdiff.diffopts(**buildopts)
2060
2060
2061 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2061 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2062 losedatafn=None, prefix='', relroot=''):
2062 losedatafn=None, prefix='', relroot=''):
2063 '''yields diff of changes to files between two nodes, or node and
2063 '''yields diff of changes to files between two nodes, or node and
2064 working directory.
2064 working directory.
2065
2065
2066 if node1 is None, use first dirstate parent instead.
2066 if node1 is None, use first dirstate parent instead.
2067 if node2 is None, compare node1 with working directory.
2067 if node2 is None, compare node1 with working directory.
2068
2068
2069 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2069 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2070 every time some change cannot be represented with the current
2070 every time some change cannot be represented with the current
2071 patch format. Return False to upgrade to git patch format, True to
2071 patch format. Return False to upgrade to git patch format, True to
2072 accept the loss or raise an exception to abort the diff. It is
2072 accept the loss or raise an exception to abort the diff. It is
2073 called with the name of current file being diffed as 'fn'. If set
2073 called with the name of current file being diffed as 'fn'. If set
2074 to None, patches will always be upgraded to git format when
2074 to None, patches will always be upgraded to git format when
2075 necessary.
2075 necessary.
2076
2076
2077 prefix is a filename prefix that is prepended to all filenames on
2077 prefix is a filename prefix that is prepended to all filenames on
2078 display (used for subrepos).
2078 display (used for subrepos).
2079
2079
2080 relroot, if not empty, must be normalized with a trailing /. Any match
2080 relroot, if not empty, must be normalized with a trailing /. Any match
2081 patterns that fall outside it will be ignored.'''
2081 patterns that fall outside it will be ignored.'''
2082
2082
2083 if opts is None:
2083 if opts is None:
2084 opts = mdiff.defaultopts
2084 opts = mdiff.defaultopts
2085
2085
2086 if not node1 and not node2:
2086 if not node1 and not node2:
2087 node1 = repo.dirstate.p1()
2087 node1 = repo.dirstate.p1()
2088
2088
2089 def lrugetfilectx():
2089 def lrugetfilectx():
2090 cache = {}
2090 cache = {}
2091 order = util.deque()
2091 order = util.deque()
2092 def getfilectx(f, ctx):
2092 def getfilectx(f, ctx):
2093 fctx = ctx.filectx(f, filelog=cache.get(f))
2093 fctx = ctx.filectx(f, filelog=cache.get(f))
2094 if f not in cache:
2094 if f not in cache:
2095 if len(cache) > 20:
2095 if len(cache) > 20:
2096 del cache[order.popleft()]
2096 del cache[order.popleft()]
2097 cache[f] = fctx.filelog()
2097 cache[f] = fctx.filelog()
2098 else:
2098 else:
2099 order.remove(f)
2099 order.remove(f)
2100 order.append(f)
2100 order.append(f)
2101 return fctx
2101 return fctx
2102 return getfilectx
2102 return getfilectx
2103 getfilectx = lrugetfilectx()
2103 getfilectx = lrugetfilectx()
2104
2104
2105 ctx1 = repo[node1]
2105 ctx1 = repo[node1]
2106 ctx2 = repo[node2]
2106 ctx2 = repo[node2]
2107
2107
2108 relfiltered = False
2108 relfiltered = False
2109 if relroot != '' and match.always():
2109 if relroot != '' and match.always():
2110 # as a special case, create a new matcher with just the relroot
2110 # as a special case, create a new matcher with just the relroot
2111 pats = [relroot]
2111 pats = [relroot]
2112 match = scmutil.match(ctx2, pats, default='path')
2112 match = scmutil.match(ctx2, pats, default='path')
2113 relfiltered = True
2113 relfiltered = True
2114
2114
2115 if not changes:
2115 if not changes:
2116 changes = repo.status(ctx1, ctx2, match=match)
2116 changes = repo.status(ctx1, ctx2, match=match)
2117 modified, added, removed = changes[:3]
2117 modified, added, removed = changes[:3]
2118
2118
2119 if not modified and not added and not removed:
2119 if not modified and not added and not removed:
2120 return []
2120 return []
2121
2121
2122 if repo.ui.debugflag:
2122 if repo.ui.debugflag:
2123 hexfunc = hex
2123 hexfunc = hex
2124 else:
2124 else:
2125 hexfunc = short
2125 hexfunc = short
2126 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2126 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2127
2127
2128 copy = {}
2128 copy = {}
2129 if opts.git or opts.upgrade:
2129 if opts.git or opts.upgrade:
2130 copy = copies.pathcopies(ctx1, ctx2, match=match)
2130 copy = copies.pathcopies(ctx1, ctx2, match=match)
2131
2131
2132 if relroot is not None:
2132 if relroot is not None:
2133 if not relfiltered:
2133 if not relfiltered:
2134 # XXX this would ideally be done in the matcher, but that is
2134 # XXX this would ideally be done in the matcher, but that is
2135 # generally meant to 'or' patterns, not 'and' them. In this case we
2135 # generally meant to 'or' patterns, not 'and' them. In this case we
2136 # need to 'and' all the patterns from the matcher with relroot.
2136 # need to 'and' all the patterns from the matcher with relroot.
2137 def filterrel(l):
2137 def filterrel(l):
2138 return [f for f in l if f.startswith(relroot)]
2138 return [f for f in l if f.startswith(relroot)]
2139 modified = filterrel(modified)
2139 modified = filterrel(modified)
2140 added = filterrel(added)
2140 added = filterrel(added)
2141 removed = filterrel(removed)
2141 removed = filterrel(removed)
2142 relfiltered = True
2142 relfiltered = True
2143 # filter out copies where either side isn't inside the relative root
2143 # filter out copies where either side isn't inside the relative root
2144 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2144 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2145 if dst.startswith(relroot)
2145 if dst.startswith(relroot)
2146 and src.startswith(relroot)))
2146 and src.startswith(relroot)))
2147
2147
2148 def difffn(opts, losedata):
2148 def difffn(opts, losedata):
2149 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2149 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2150 copy, getfilectx, opts, losedata, prefix, relroot)
2150 copy, getfilectx, opts, losedata, prefix, relroot)
2151 if opts.upgrade and not opts.git:
2151 if opts.upgrade and not opts.git:
2152 try:
2152 try:
2153 def losedata(fn):
2153 def losedata(fn):
2154 if not losedatafn or not losedatafn(fn=fn):
2154 if not losedatafn or not losedatafn(fn=fn):
2155 raise GitDiffRequired
2155 raise GitDiffRequired
2156 # Buffer the whole output until we are sure it can be generated
2156 # Buffer the whole output until we are sure it can be generated
2157 return list(difffn(opts.copy(git=False), losedata))
2157 return list(difffn(opts.copy(git=False), losedata))
2158 except GitDiffRequired:
2158 except GitDiffRequired:
2159 return difffn(opts.copy(git=True), None)
2159 return difffn(opts.copy(git=True), None)
2160 else:
2160 else:
2161 return difffn(opts, None)
2161 return difffn(opts, None)
2162
2162
2163 def difflabel(func, *args, **kw):
2163 def difflabel(func, *args, **kw):
2164 '''yields 2-tuples of (output, label) based on the output of func()'''
2164 '''yields 2-tuples of (output, label) based on the output of func()'''
2165 headprefixes = [('diff', 'diff.diffline'),
2165 headprefixes = [('diff', 'diff.diffline'),
2166 ('copy', 'diff.extended'),
2166 ('copy', 'diff.extended'),
2167 ('rename', 'diff.extended'),
2167 ('rename', 'diff.extended'),
2168 ('old', 'diff.extended'),
2168 ('old', 'diff.extended'),
2169 ('new', 'diff.extended'),
2169 ('new', 'diff.extended'),
2170 ('deleted', 'diff.extended'),
2170 ('deleted', 'diff.extended'),
2171 ('---', 'diff.file_a'),
2171 ('---', 'diff.file_a'),
2172 ('+++', 'diff.file_b')]
2172 ('+++', 'diff.file_b')]
2173 textprefixes = [('@', 'diff.hunk'),
2173 textprefixes = [('@', 'diff.hunk'),
2174 ('-', 'diff.deleted'),
2174 ('-', 'diff.deleted'),
2175 ('+', 'diff.inserted')]
2175 ('+', 'diff.inserted')]
2176 head = False
2176 head = False
2177 for chunk in func(*args, **kw):
2177 for chunk in func(*args, **kw):
2178 lines = chunk.split('\n')
2178 lines = chunk.split('\n')
2179 for i, line in enumerate(lines):
2179 for i, line in enumerate(lines):
2180 if i != 0:
2180 if i != 0:
2181 yield ('\n', '')
2181 yield ('\n', '')
2182 if head:
2182 if head:
2183 if line.startswith('@'):
2183 if line.startswith('@'):
2184 head = False
2184 head = False
2185 else:
2185 else:
2186 if line and line[0] not in ' +-@\\':
2186 if line and line[0] not in ' +-@\\':
2187 head = True
2187 head = True
2188 stripline = line
2188 stripline = line
2189 diffline = False
2189 diffline = False
2190 if not head and line and line[0] in '+-':
2190 if not head and line and line[0] in '+-':
2191 # highlight tabs and trailing whitespace, but only in
2191 # highlight tabs and trailing whitespace, but only in
2192 # changed lines
2192 # changed lines
2193 stripline = line.rstrip()
2193 stripline = line.rstrip()
2194 diffline = True
2194 diffline = True
2195
2195
2196 prefixes = textprefixes
2196 prefixes = textprefixes
2197 if head:
2197 if head:
2198 prefixes = headprefixes
2198 prefixes = headprefixes
2199 for prefix, label in prefixes:
2199 for prefix, label in prefixes:
2200 if stripline.startswith(prefix):
2200 if stripline.startswith(prefix):
2201 if diffline:
2201 if diffline:
2202 for token in tabsplitter.findall(stripline):
2202 for token in tabsplitter.findall(stripline):
2203 if '\t' == token[0]:
2203 if '\t' == token[0]:
2204 yield (token, 'diff.tab')
2204 yield (token, 'diff.tab')
2205 else:
2205 else:
2206 yield (token, label)
2206 yield (token, label)
2207 else:
2207 else:
2208 yield (stripline, label)
2208 yield (stripline, label)
2209 break
2209 break
2210 else:
2210 else:
2211 yield (line, '')
2211 yield (line, '')
2212 if line != stripline:
2212 if line != stripline:
2213 yield (line[len(stripline):], 'diff.trailingwhitespace')
2213 yield (line[len(stripline):], 'diff.trailingwhitespace')
2214
2214
2215 def diffui(*args, **kw):
2215 def diffui(*args, **kw):
2216 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2216 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2217 return difflabel(diff, *args, **kw)
2217 return difflabel(diff, *args, **kw)
2218
2218
2219 def _filepairs(ctx1, modified, added, removed, copy, opts):
2219 def _filepairs(ctx1, modified, added, removed, copy, opts):
2220 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2220 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2221 before and f2 is the the name after. For added files, f1 will be None,
2221 before and f2 is the the name after. For added files, f1 will be None,
2222 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2222 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2223 or 'rename' (the latter two only if opts.git is set).'''
2223 or 'rename' (the latter two only if opts.git is set).'''
2224 gone = set()
2224 gone = set()
2225
2225
2226 copyto = dict([(v, k) for k, v in copy.items()])
2226 copyto = dict([(v, k) for k, v in copy.items()])
2227
2227
2228 addedset, removedset = set(added), set(removed)
2228 addedset, removedset = set(added), set(removed)
2229 # Fix up added, since merged-in additions appear as
2229 # Fix up added, since merged-in additions appear as
2230 # modifications during merges
2230 # modifications during merges
2231 for f in modified:
2231 for f in modified:
2232 if f not in ctx1:
2232 if f not in ctx1:
2233 addedset.add(f)
2233 addedset.add(f)
2234
2234
2235 for f in sorted(modified + added + removed):
2235 for f in sorted(modified + added + removed):
2236 copyop = None
2236 copyop = None
2237 f1, f2 = f, f
2237 f1, f2 = f, f
2238 if f in addedset:
2238 if f in addedset:
2239 f1 = None
2239 f1 = None
2240 if f in copy:
2240 if f in copy:
2241 if opts.git:
2241 if opts.git:
2242 f1 = copy[f]
2242 f1 = copy[f]
2243 if f1 in removedset and f1 not in gone:
2243 if f1 in removedset and f1 not in gone:
2244 copyop = 'rename'
2244 copyop = 'rename'
2245 gone.add(f1)
2245 gone.add(f1)
2246 else:
2246 else:
2247 copyop = 'copy'
2247 copyop = 'copy'
2248 elif f in removedset:
2248 elif f in removedset:
2249 f2 = None
2249 f2 = None
2250 if opts.git:
2250 if opts.git:
2251 # have we already reported a copy above?
2251 # have we already reported a copy above?
2252 if (f in copyto and copyto[f] in addedset
2252 if (f in copyto and copyto[f] in addedset
2253 and copy[copyto[f]] == f):
2253 and copy[copyto[f]] == f):
2254 continue
2254 continue
2255 yield f1, f2, copyop
2255 yield f1, f2, copyop
2256
2256
2257 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2257 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2258 copy, getfilectx, opts, losedatafn, prefix, relroot):
2258 copy, getfilectx, opts, losedatafn, prefix, relroot):
2259 '''given input data, generate a diff and yield it in blocks
2259 '''given input data, generate a diff and yield it in blocks
2260
2260
2261 If generating a diff would lose data like flags or binary data and
2261 If generating a diff would lose data like flags or binary data and
2262 losedatafn is not None, it will be called.
2262 losedatafn is not None, it will be called.
2263
2263
2264 relroot is removed and prefix is added to every path in the diff output.
2264 relroot is removed and prefix is added to every path in the diff output.
2265
2265
2266 If relroot is not empty, this function expects every path in modified,
2266 If relroot is not empty, this function expects every path in modified,
2267 added, removed and copy to start with it.'''
2267 added, removed and copy to start with it.'''
2268
2268
2269 def gitindex(text):
2269 def gitindex(text):
2270 if not text:
2270 if not text:
2271 text = ""
2271 text = ""
2272 l = len(text)
2272 l = len(text)
2273 s = util.sha1('blob %d\0' % l)
2273 s = util.sha1('blob %d\0' % l)
2274 s.update(text)
2274 s.update(text)
2275 return s.hexdigest()
2275 return s.hexdigest()
2276
2276
2277 if opts.noprefix:
2277 if opts.noprefix:
2278 aprefix = bprefix = ''
2278 aprefix = bprefix = ''
2279 else:
2279 else:
2280 aprefix = 'a/'
2280 aprefix = 'a/'
2281 bprefix = 'b/'
2281 bprefix = 'b/'
2282
2282
2283 def diffline(f, revs):
2283 def diffline(f, revs):
2284 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2284 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2285 return 'diff %s %s' % (revinfo, f)
2285 return 'diff %s %s' % (revinfo, f)
2286
2286
2287 date1 = util.datestr(ctx1.date())
2287 date1 = util.datestr(ctx1.date())
2288 date2 = util.datestr(ctx2.date())
2288 date2 = util.datestr(ctx2.date())
2289
2289
2290 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2290 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2291
2291
2292 if relroot != '' and (repo.ui.configbool('devel', 'all')
2292 if relroot != '' and (repo.ui.configbool('devel', 'all')
2293 or repo.ui.configbool('devel', 'check-relroot')):
2293 or repo.ui.configbool('devel', 'check-relroot')):
2294 for f in modified + added + removed + copy.keys() + copy.values():
2294 for f in modified + added + removed + copy.keys() + copy.values():
2295 if f is not None and not f.startswith(relroot):
2295 if f is not None and not f.startswith(relroot):
2296 raise AssertionError(
2296 raise AssertionError(
2297 "file %s doesn't start with relroot %s" % (f, relroot))
2297 "file %s doesn't start with relroot %s" % (f, relroot))
2298
2298
2299 for f1, f2, copyop in _filepairs(
2299 for f1, f2, copyop in _filepairs(
2300 ctx1, modified, added, removed, copy, opts):
2300 ctx1, modified, added, removed, copy, opts):
2301 content1 = None
2301 content1 = None
2302 content2 = None
2302 content2 = None
2303 flag1 = None
2303 flag1 = None
2304 flag2 = None
2304 flag2 = None
2305 if f1:
2305 if f1:
2306 content1 = getfilectx(f1, ctx1).data()
2306 content1 = getfilectx(f1, ctx1).data()
2307 if opts.git or losedatafn:
2307 if opts.git or losedatafn:
2308 flag1 = ctx1.flags(f1)
2308 flag1 = ctx1.flags(f1)
2309 if f2:
2309 if f2:
2310 content2 = getfilectx(f2, ctx2).data()
2310 content2 = getfilectx(f2, ctx2).data()
2311 if opts.git or losedatafn:
2311 if opts.git or losedatafn:
2312 flag2 = ctx2.flags(f2)
2312 flag2 = ctx2.flags(f2)
2313 binary = False
2313 binary = False
2314 if opts.git or losedatafn:
2314 if opts.git or losedatafn:
2315 binary = util.binary(content1) or util.binary(content2)
2315 binary = util.binary(content1) or util.binary(content2)
2316
2316
2317 if losedatafn and not opts.git:
2317 if losedatafn and not opts.git:
2318 if (binary or
2318 if (binary or
2319 # copy/rename
2319 # copy/rename
2320 f2 in copy or
2320 f2 in copy or
2321 # empty file creation
2321 # empty file creation
2322 (not f1 and not content2) or
2322 (not f1 and not content2) or
2323 # empty file deletion
2323 # empty file deletion
2324 (not content1 and not f2) or
2324 (not content1 and not f2) or
2325 # create with flags
2325 # create with flags
2326 (not f1 and flag2) or
2326 (not f1 and flag2) or
2327 # change flags
2327 # change flags
2328 (f1 and f2 and flag1 != flag2)):
2328 (f1 and f2 and flag1 != flag2)):
2329 losedatafn(f2 or f1)
2329 losedatafn(f2 or f1)
2330
2330
2331 path1 = f1 or f2
2331 path1 = f1 or f2
2332 path2 = f2 or f1
2332 path2 = f2 or f1
2333 path1 = posixpath.join(prefix, path1[len(relroot):])
2333 path1 = posixpath.join(prefix, path1[len(relroot):])
2334 path2 = posixpath.join(prefix, path2[len(relroot):])
2334 path2 = posixpath.join(prefix, path2[len(relroot):])
2335 header = []
2335 header = []
2336 if opts.git:
2336 if opts.git:
2337 header.append('diff --git %s%s %s%s' %
2337 header.append('diff --git %s%s %s%s' %
2338 (aprefix, path1, bprefix, path2))
2338 (aprefix, path1, bprefix, path2))
2339 if not f1: # added
2339 if not f1: # added
2340 header.append('new file mode %s' % gitmode[flag2])
2340 header.append('new file mode %s' % gitmode[flag2])
2341 elif not f2: # removed
2341 elif not f2: # removed
2342 header.append('deleted file mode %s' % gitmode[flag1])
2342 header.append('deleted file mode %s' % gitmode[flag1])
2343 else: # modified/copied/renamed
2343 else: # modified/copied/renamed
2344 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2344 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2345 if mode1 != mode2:
2345 if mode1 != mode2:
2346 header.append('old mode %s' % mode1)
2346 header.append('old mode %s' % mode1)
2347 header.append('new mode %s' % mode2)
2347 header.append('new mode %s' % mode2)
2348 if copyop is not None:
2348 if copyop is not None:
2349 header.append('%s from %s' % (copyop, path1))
2349 header.append('%s from %s' % (copyop, path1))
2350 header.append('%s to %s' % (copyop, path2))
2350 header.append('%s to %s' % (copyop, path2))
2351 elif revs and not repo.ui.quiet:
2351 elif revs and not repo.ui.quiet:
2352 header.append(diffline(path1, revs))
2352 header.append(diffline(path1, revs))
2353
2353
2354 if binary and opts.git and not opts.nobinary:
2354 if binary and opts.git and not opts.nobinary:
2355 text = mdiff.b85diff(content1, content2)
2355 text = mdiff.b85diff(content1, content2)
2356 if text:
2356 if text:
2357 header.append('index %s..%s' %
2357 header.append('index %s..%s' %
2358 (gitindex(content1), gitindex(content2)))
2358 (gitindex(content1), gitindex(content2)))
2359 else:
2359 else:
2360 text = mdiff.unidiff(content1, date1,
2360 text = mdiff.unidiff(content1, date1,
2361 content2, date2,
2361 content2, date2,
2362 path1, path2, opts=opts)
2362 path1, path2, opts=opts)
2363 if header and (text or len(header) > 1):
2363 if header and (text or len(header) > 1):
2364 yield '\n'.join(header) + '\n'
2364 yield '\n'.join(header) + '\n'
2365 if text:
2365 if text:
2366 yield text
2366 yield text
2367
2367
2368 def diffstatsum(stats):
2368 def diffstatsum(stats):
2369 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2369 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2370 for f, a, r, b in stats:
2370 for f, a, r, b in stats:
2371 maxfile = max(maxfile, encoding.colwidth(f))
2371 maxfile = max(maxfile, encoding.colwidth(f))
2372 maxtotal = max(maxtotal, a + r)
2372 maxtotal = max(maxtotal, a + r)
2373 addtotal += a
2373 addtotal += a
2374 removetotal += r
2374 removetotal += r
2375 binary = binary or b
2375 binary = binary or b
2376
2376
2377 return maxfile, maxtotal, addtotal, removetotal, binary
2377 return maxfile, maxtotal, addtotal, removetotal, binary
2378
2378
2379 def diffstatdata(lines):
2379 def diffstatdata(lines):
2380 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2380 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2381
2381
2382 results = []
2382 results = []
2383 filename, adds, removes, isbinary = None, 0, 0, False
2383 filename, adds, removes, isbinary = None, 0, 0, False
2384
2384
2385 def addresult():
2385 def addresult():
2386 if filename:
2386 if filename:
2387 results.append((filename, adds, removes, isbinary))
2387 results.append((filename, adds, removes, isbinary))
2388
2388
2389 for line in lines:
2389 for line in lines:
2390 if line.startswith('diff'):
2390 if line.startswith('diff'):
2391 addresult()
2391 addresult()
2392 # set numbers to 0 anyway when starting new file
2392 # set numbers to 0 anyway when starting new file
2393 adds, removes, isbinary = 0, 0, False
2393 adds, removes, isbinary = 0, 0, False
2394 if line.startswith('diff --git a/'):
2394 if line.startswith('diff --git a/'):
2395 filename = gitre.search(line).group(2)
2395 filename = gitre.search(line).group(2)
2396 elif line.startswith('diff -r'):
2396 elif line.startswith('diff -r'):
2397 # format: "diff -r ... -r ... filename"
2397 # format: "diff -r ... -r ... filename"
2398 filename = diffre.search(line).group(1)
2398 filename = diffre.search(line).group(1)
2399 elif line.startswith('+') and not line.startswith('+++ '):
2399 elif line.startswith('+') and not line.startswith('+++ '):
2400 adds += 1
2400 adds += 1
2401 elif line.startswith('-') and not line.startswith('--- '):
2401 elif line.startswith('-') and not line.startswith('--- '):
2402 removes += 1
2402 removes += 1
2403 elif (line.startswith('GIT binary patch') or
2403 elif (line.startswith('GIT binary patch') or
2404 line.startswith('Binary file')):
2404 line.startswith('Binary file')):
2405 isbinary = True
2405 isbinary = True
2406 addresult()
2406 addresult()
2407 return results
2407 return results
2408
2408
2409 def diffstat(lines, width=80, git=False):
2409 def diffstat(lines, width=80, git=False):
2410 output = []
2410 output = []
2411 stats = diffstatdata(lines)
2411 stats = diffstatdata(lines)
2412 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2412 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2413
2413
2414 countwidth = len(str(maxtotal))
2414 countwidth = len(str(maxtotal))
2415 if hasbinary and countwidth < 3:
2415 if hasbinary and countwidth < 3:
2416 countwidth = 3
2416 countwidth = 3
2417 graphwidth = width - countwidth - maxname - 6
2417 graphwidth = width - countwidth - maxname - 6
2418 if graphwidth < 10:
2418 if graphwidth < 10:
2419 graphwidth = 10
2419 graphwidth = 10
2420
2420
2421 def scale(i):
2421 def scale(i):
2422 if maxtotal <= graphwidth:
2422 if maxtotal <= graphwidth:
2423 return i
2423 return i
2424 # If diffstat runs out of room it doesn't print anything,
2424 # If diffstat runs out of room it doesn't print anything,
2425 # which isn't very useful, so always print at least one + or -
2425 # which isn't very useful, so always print at least one + or -
2426 # if there were at least some changes.
2426 # if there were at least some changes.
2427 return max(i * graphwidth // maxtotal, int(bool(i)))
2427 return max(i * graphwidth // maxtotal, int(bool(i)))
2428
2428
2429 for filename, adds, removes, isbinary in stats:
2429 for filename, adds, removes, isbinary in stats:
2430 if isbinary:
2430 if isbinary:
2431 count = 'Bin'
2431 count = 'Bin'
2432 else:
2432 else:
2433 count = adds + removes
2433 count = adds + removes
2434 pluses = '+' * scale(adds)
2434 pluses = '+' * scale(adds)
2435 minuses = '-' * scale(removes)
2435 minuses = '-' * scale(removes)
2436 output.append(' %s%s | %*s %s%s\n' %
2436 output.append(' %s%s | %*s %s%s\n' %
2437 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2437 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2438 countwidth, count, pluses, minuses))
2438 countwidth, count, pluses, minuses))
2439
2439
2440 if stats:
2440 if stats:
2441 output.append(_(' %d files changed, %d insertions(+), '
2441 output.append(_(' %d files changed, %d insertions(+), '
2442 '%d deletions(-)\n')
2442 '%d deletions(-)\n')
2443 % (len(stats), totaladds, totalremoves))
2443 % (len(stats), totaladds, totalremoves))
2444
2444
2445 return ''.join(output)
2445 return ''.join(output)
2446
2446
2447 def diffstatui(*args, **kw):
2447 def diffstatui(*args, **kw):
2448 '''like diffstat(), but yields 2-tuples of (output, label) for
2448 '''like diffstat(), but yields 2-tuples of (output, label) for
2449 ui.write()
2449 ui.write()
2450 '''
2450 '''
2451
2451
2452 for line in diffstat(*args, **kw).splitlines():
2452 for line in diffstat(*args, **kw).splitlines():
2453 if line and line[-1] in '+-':
2453 if line and line[-1] in '+-':
2454 name, graph = line.rsplit(' ', 1)
2454 name, graph = line.rsplit(' ', 1)
2455 yield (name + ' ', '')
2455 yield (name + ' ', '')
2456 m = re.search(r'\++', graph)
2456 m = re.search(r'\++', graph)
2457 if m:
2457 if m:
2458 yield (m.group(0), 'diffstat.inserted')
2458 yield (m.group(0), 'diffstat.inserted')
2459 m = re.search(r'-+', graph)
2459 m = re.search(r'-+', graph)
2460 if m:
2460 if m:
2461 yield (m.group(0), 'diffstat.deleted')
2461 yield (m.group(0), 'diffstat.deleted')
2462 else:
2462 else:
2463 yield (line, '')
2463 yield (line, '')
2464 yield ('\n', '')
2464 yield ('\n', '')
@@ -1,203 +1,163 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 > [experimental]
6 > [experimental]
7 > crecord = true
7 > crecord = true
8 > crecordtest = testModeCommands
8 > crecordtest = testModeCommands
9 > EOF
9 > EOF
10
10
11 $ hg init a
11 $ hg init a
12 $ cd a
12 $ cd a
13
13
14 Committing some changes but stopping on the way
14 Committing some changes but stopping on the way
15
15
16 $ echo "a" > a
16 $ echo "a" > a
17 $ hg add a
17 $ hg add a
18 $ cat <<EOF >testModeCommands
18 $ cat <<EOF >testModeCommands
19 > TOGGLE
19 > TOGGLE
20 > X
20 > X
21 > EOF
21 > EOF
22 $ hg commit -i -m "a" -d "0 0"
22 $ hg commit -i -m "a" -d "0 0"
23 no changes to record
23 no changes to record
24 $ hg tip
24 $ hg tip
25 changeset: -1:000000000000
25 changeset: -1:000000000000
26 tag: tip
26 tag: tip
27 user:
27 user:
28 date: Thu Jan 01 00:00:00 1970 +0000
28 date: Thu Jan 01 00:00:00 1970 +0000
29
29
30
30
31 Committing some changes
31 Committing some changes
32
32
33 $ cat <<EOF >testModeCommands
33 $ cat <<EOF >testModeCommands
34 > X
34 > X
35 > EOF
35 > EOF
36 $ hg commit -i -m "a" -d "0 0"
36 $ hg commit -i -m "a" -d "0 0"
37 $ hg tip
37 $ hg tip
38 changeset: 0:cb9a9f314b8b
38 changeset: 0:cb9a9f314b8b
39 tag: tip
39 tag: tip
40 user: test
40 user: test
41 date: Thu Jan 01 00:00:00 1970 +0000
41 date: Thu Jan 01 00:00:00 1970 +0000
42 summary: a
42 summary: a
43
43
44 Committing only one file
44 Committing only one file
45
45
46 $ echo "a" >> a
46 $ echo "a" >> a
47 >>> open('b', 'wb').write("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n")
47 >>> open('b', 'wb').write("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n")
48 $ hg add b
48 $ hg add b
49 $ cat <<EOF >testModeCommands
49 $ cat <<EOF >testModeCommands
50 > TOGGLE
50 > TOGGLE
51 > KEY_DOWN
51 > KEY_DOWN
52 > X
52 > X
53 > EOF
53 > EOF
54 $ hg commit -i -m "one file" -d "0 0"
54 $ hg commit -i -m "one file" -d "0 0"
55 $ hg tip
55 $ hg tip
56 changeset: 1:fb2705a663ea
56 changeset: 1:fb2705a663ea
57 tag: tip
57 tag: tip
58 user: test
58 user: test
59 date: Thu Jan 01 00:00:00 1970 +0000
59 date: Thu Jan 01 00:00:00 1970 +0000
60 summary: one file
60 summary: one file
61
61
62 $ hg cat -r tip a
62 $ hg cat -r tip a
63 a
63 a
64 $ cat a
64 $ cat a
65 a
65 a
66 a
66 a
67
67
68 Committing only one hunk
68 Committing only one hunk
69
69
70 - Untoggle all the hunks, go down to the second file
70 - Untoggle all the hunks, go down to the second file
71 - unfold it
71 - unfold it
72 - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike)
72 - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike)
73 - toggle the second hunk
73 - toggle the second hunk
74 - commit
74 - commit
75
75
76 $ echo "x" > c
76 $ echo "x" > c
77 $ cat b >> c
77 $ cat b >> c
78 $ echo "y" >> c
78 $ echo "y" >> c
79 $ mv c b
79 $ mv c b
80 $ cat <<EOF >testModeCommands
80 $ cat <<EOF >testModeCommands
81 > A
81 > A
82 > KEY_DOWN
82 > KEY_DOWN
83 > f
83 > f
84 > KEY_DOWN
84 > KEY_DOWN
85 > KEY_DOWN
85 > KEY_DOWN
86 > KEY_DOWN
86 > KEY_DOWN
87 > KEY_DOWN
87 > KEY_DOWN
88 > TOGGLE
88 > TOGGLE
89 > X
89 > X
90 > EOF
90 > EOF
91 $ hg commit -i -m "one hunk" -d "0 0"
91 $ hg commit -i -m "one hunk" -d "0 0"
92 $ hg tip
92 $ hg tip
93 changeset: 2:7d10dfe755a8
93 changeset: 2:7d10dfe755a8
94 tag: tip
94 tag: tip
95 user: test
95 user: test
96 date: Thu Jan 01 00:00:00 1970 +0000
96 date: Thu Jan 01 00:00:00 1970 +0000
97 summary: one hunk
97 summary: one hunk
98
98
99 $ hg cat -r tip b
99 $ hg cat -r tip b
100 1
100 1
101 2
101 2
102 3
102 3
103 4
103 4
104 5
104 5
105 6
105 6
106 7
106 7
107 8
107 8
108 9
108 9
109 10
109 10
110 y
110 y
111 $ cat b
111 $ cat b
112 x
112 x
113 1
113 1
114 2
114 2
115 3
115 3
116 4
116 4
117 5
117 5
118 6
118 6
119 7
119 7
120 8
120 8
121 9
121 9
122 10
122 10
123 y
123 y
124 $ hg commit -m "other hunks"
124 $ hg commit -m "other hunks"
125 $ hg tip
125 $ hg tip
126 changeset: 3:a6735021574d
126 changeset: 3:a6735021574d
127 tag: tip
127 tag: tip
128 user: test
128 user: test
129 date: Thu Jan 01 00:00:00 1970 +0000
129 date: Thu Jan 01 00:00:00 1970 +0000
130 summary: other hunks
130 summary: other hunks
131
131
132 $ hg cat -r tip b
132 $ hg cat -r tip b
133 x
133 x
134 1
134 1
135 2
135 2
136 3
136 3
137 4
137 4
138 5
138 5
139 6
139 6
140 7
140 7
141 8
141 8
142 9
142 9
143 10
143 10
144 y
144 y
145
145
146 Editing patch of newly added file
147
148 $ cat > editor.sh << '__EOF__'
149 > cat "$1" | sed "s/first/very/g" > tt
150 > mv tt "$1"
151 > __EOF__
152 $ cat > newfile << '__EOF__'
153 > This is the first line
154 > This is the second line
155 > This is the third line
156 > __EOF__
157 $ hg add newfile
158 $ cat <<EOF >testModeCommands
159 > f
160 > KEY_DOWN
161 > KEY_DOWN
162 > KEY_DOWN
163 > e
164 > X
165 > EOF
166 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new
167 $ hg tip
168 changeset: 4:6a0a43e9eff5
169 tag: tip
170 user: test
171 date: Thu Jan 01 00:00:23 1970 +0000
172 summary: edit-patch-new
173
174 $ hg cat -r tip newfile
175 This is the very line
176 This is the second line
177 This is the third line
178
179 $ cat newfile
180 This is the first line
181 This is the second line
182 This is the third line
183
184 Newly added files can be selected with the curses interface
146 Newly added files can be selected with the curses interface
185
147
186 $ hg update -C .
148 $ hg update -C .
187 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
188 $ echo "hello" > x
150 $ echo "hello" > x
189 $ hg add x
151 $ hg add x
190 $ cat <<EOF >testModeCommands
152 $ cat <<EOF >testModeCommands
191 > TOGGLE
153 > TOGGLE
192 > TOGGLE
154 > TOGGLE
193 > X
155 > X
194 > EOF
156 > EOF
195 $ hg st
157 $ hg st
196 A x
158 A x
197 ? editor.sh
198 ? testModeCommands
159 ? testModeCommands
199 $ hg commit -i -m "newly added file" -d "0 0"
160 $ hg commit -i -m "newly added file" -d "0 0"
200 $ hg st
161 $ hg st
201 ? editor.sh
202 ? testModeCommands
162 ? testModeCommands
203
163
@@ -1,1434 +1,1410 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 commit -i empty-rw<<EOF
18 $ hg commit -i empty-rw<<EOF
19 > n
19 > n
20 > EOF
20 > EOF
21 diff --git a/empty-rw b/empty-rw
21 diff --git a/empty-rw b/empty-rw
22 new file mode 100644
22 new file mode 100644
23 examine changes to 'empty-rw'? [Ynesfdaq?] n
23 examine changes to 'empty-rw'? [Ynesfdaq?] n
24
24
25 no changes to record
25 no changes to record
26
26
27 $ hg tip -p
27 $ hg tip -p
28 changeset: -1:000000000000
28 changeset: -1:000000000000
29 tag: tip
29 tag: tip
30 user:
30 user:
31 date: Thu Jan 01 00:00:00 1970 +0000
31 date: Thu Jan 01 00:00:00 1970 +0000
32
32
33
33
34
34
35 Select files but no hunks
35 Select files but no hunks
36
36
37 $ hg commit -i empty-rw<<EOF
37 $ hg commit -i empty-rw<<EOF
38 > y
38 > y
39 > n
39 > n
40 > EOF
40 > EOF
41 diff --git a/empty-rw b/empty-rw
41 diff --git a/empty-rw b/empty-rw
42 new file mode 100644
42 new file mode 100644
43 examine changes to 'empty-rw'? [Ynesfdaq?] y
43 examine changes to 'empty-rw'? [Ynesfdaq?] y
44
44
45 abort: empty commit message
45 abort: empty commit message
46 [255]
46 [255]
47
47
48 $ hg tip -p
48 $ hg tip -p
49 changeset: -1:000000000000
49 changeset: -1:000000000000
50 tag: tip
50 tag: tip
51 user:
51 user:
52 date: Thu Jan 01 00:00:00 1970 +0000
52 date: Thu Jan 01 00:00:00 1970 +0000
53
53
54
54
55
55
56 Record empty file
56 Record empty file
57
57
58 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
58 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
59 > y
59 > y
60 > y
60 > y
61 > EOF
61 > EOF
62 diff --git a/empty-rw b/empty-rw
62 diff --git a/empty-rw b/empty-rw
63 new file mode 100644
63 new file mode 100644
64 examine changes to 'empty-rw'? [Ynesfdaq?] y
64 examine changes to 'empty-rw'? [Ynesfdaq?] y
65
65
66
66
67 $ hg tip -p
67 $ hg tip -p
68 changeset: 0:c0708cf4e46e
68 changeset: 0:c0708cf4e46e
69 tag: tip
69 tag: tip
70 user: test
70 user: test
71 date: Thu Jan 01 00:00:00 1970 +0000
71 date: Thu Jan 01 00:00:00 1970 +0000
72 summary: empty
72 summary: empty
73
73
74
74
75
75
76 Summary shows we updated to the new cset
76 Summary shows we updated to the new cset
77
77
78 $ hg summary
78 $ hg summary
79 parent: 0:c0708cf4e46e tip
79 parent: 0:c0708cf4e46e tip
80 empty
80 empty
81 branch: default
81 branch: default
82 commit: (clean)
82 commit: (clean)
83 update: (current)
83 update: (current)
84
84
85 Rename empty file
85 Rename empty file
86
86
87 $ hg mv empty-rw empty-rename
87 $ hg mv empty-rw empty-rename
88 $ hg commit -i -d '1 0' -m rename<<EOF
88 $ hg commit -i -d '1 0' -m rename<<EOF
89 > y
89 > y
90 > EOF
90 > EOF
91 diff --git a/empty-rw b/empty-rename
91 diff --git a/empty-rw b/empty-rename
92 rename from empty-rw
92 rename from empty-rw
93 rename to empty-rename
93 rename to empty-rename
94 examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
94 examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
95
95
96
96
97 $ hg tip -p
97 $ hg tip -p
98 changeset: 1:d695e8dcb197
98 changeset: 1:d695e8dcb197
99 tag: tip
99 tag: tip
100 user: test
100 user: test
101 date: Thu Jan 01 00:00:01 1970 +0000
101 date: Thu Jan 01 00:00:01 1970 +0000
102 summary: rename
102 summary: rename
103
103
104
104
105
105
106 Copy empty file
106 Copy empty file
107
107
108 $ hg cp empty-rename empty-copy
108 $ hg cp empty-rename empty-copy
109 $ hg commit -i -d '2 0' -m copy<<EOF
109 $ hg commit -i -d '2 0' -m copy<<EOF
110 > y
110 > y
111 > EOF
111 > EOF
112 diff --git a/empty-rename b/empty-copy
112 diff --git a/empty-rename b/empty-copy
113 copy from empty-rename
113 copy from empty-rename
114 copy to empty-copy
114 copy to empty-copy
115 examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
115 examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
116
116
117
117
118 $ hg tip -p
118 $ hg tip -p
119 changeset: 2:1d4b90bea524
119 changeset: 2:1d4b90bea524
120 tag: tip
120 tag: tip
121 user: test
121 user: test
122 date: Thu Jan 01 00:00:02 1970 +0000
122 date: Thu Jan 01 00:00:02 1970 +0000
123 summary: copy
123 summary: copy
124
124
125
125
126
126
127 Delete empty file
127 Delete empty file
128
128
129 $ hg rm empty-copy
129 $ hg rm empty-copy
130 $ hg commit -i -d '3 0' -m delete<<EOF
130 $ hg commit -i -d '3 0' -m delete<<EOF
131 > y
131 > y
132 > EOF
132 > EOF
133 diff --git a/empty-copy b/empty-copy
133 diff --git a/empty-copy b/empty-copy
134 deleted file mode 100644
134 deleted file mode 100644
135 examine changes to 'empty-copy'? [Ynesfdaq?] y
135 examine changes to 'empty-copy'? [Ynesfdaq?] y
136
136
137
137
138 $ hg tip -p
138 $ hg tip -p
139 changeset: 3:b39a238f01a1
139 changeset: 3:b39a238f01a1
140 tag: tip
140 tag: tip
141 user: test
141 user: test
142 date: Thu Jan 01 00:00:03 1970 +0000
142 date: Thu Jan 01 00:00:03 1970 +0000
143 summary: delete
143 summary: delete
144
144
145
145
146
146
147 Add binary file
147 Add binary file
148
148
149 $ hg bundle --base -2 tip.bundle
149 $ hg bundle --base -2 tip.bundle
150 1 changesets found
150 1 changesets found
151 $ hg add tip.bundle
151 $ hg add tip.bundle
152 $ hg commit -i -d '4 0' -m binary<<EOF
152 $ hg commit -i -d '4 0' -m binary<<EOF
153 > y
153 > y
154 > EOF
154 > EOF
155 diff --git a/tip.bundle b/tip.bundle
155 diff --git a/tip.bundle b/tip.bundle
156 new file mode 100644
156 new file mode 100644
157 this is a binary file
157 this is a binary file
158 examine changes to 'tip.bundle'? [Ynesfdaq?] y
158 examine changes to 'tip.bundle'? [Ynesfdaq?] y
159
159
160
160
161 $ hg tip -p
161 $ hg tip -p
162 changeset: 4:ad816da3711e
162 changeset: 4:ad816da3711e
163 tag: tip
163 tag: tip
164 user: test
164 user: test
165 date: Thu Jan 01 00:00:04 1970 +0000
165 date: Thu Jan 01 00:00:04 1970 +0000
166 summary: binary
166 summary: binary
167
167
168 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
168 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
169 Binary file tip.bundle has changed
169 Binary file tip.bundle has changed
170
170
171
171
172 Change binary file
172 Change binary file
173
173
174 $ hg bundle --base -2 tip.bundle
174 $ hg bundle --base -2 tip.bundle
175 1 changesets found
175 1 changesets found
176 $ hg commit -i -d '5 0' -m binary-change<<EOF
176 $ hg commit -i -d '5 0' -m binary-change<<EOF
177 > y
177 > y
178 > EOF
178 > EOF
179 diff --git a/tip.bundle b/tip.bundle
179 diff --git a/tip.bundle b/tip.bundle
180 this modifies a binary file (all or nothing)
180 this modifies a binary file (all or nothing)
181 examine changes to 'tip.bundle'? [Ynesfdaq?] y
181 examine changes to 'tip.bundle'? [Ynesfdaq?] y
182
182
183
183
184 $ hg tip -p
184 $ hg tip -p
185 changeset: 5:dccd6f3eb485
185 changeset: 5:dccd6f3eb485
186 tag: tip
186 tag: tip
187 user: test
187 user: test
188 date: Thu Jan 01 00:00:05 1970 +0000
188 date: Thu Jan 01 00:00:05 1970 +0000
189 summary: binary-change
189 summary: binary-change
190
190
191 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
191 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
192 Binary file tip.bundle has changed
192 Binary file tip.bundle has changed
193
193
194
194
195 Rename and change binary file
195 Rename and change binary file
196
196
197 $ hg mv tip.bundle top.bundle
197 $ hg mv tip.bundle top.bundle
198 $ hg bundle --base -2 top.bundle
198 $ hg bundle --base -2 top.bundle
199 1 changesets found
199 1 changesets found
200 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
200 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
201 > y
201 > y
202 > EOF
202 > EOF
203 diff --git a/tip.bundle b/top.bundle
203 diff --git a/tip.bundle b/top.bundle
204 rename from tip.bundle
204 rename from tip.bundle
205 rename to top.bundle
205 rename to top.bundle
206 this modifies a binary file (all or nothing)
206 this modifies a binary file (all or nothing)
207 examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
207 examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
208
208
209
209
210 $ hg tip -p
210 $ hg tip -p
211 changeset: 6:7fa44105f5b3
211 changeset: 6:7fa44105f5b3
212 tag: tip
212 tag: tip
213 user: test
213 user: test
214 date: Thu Jan 01 00:00:06 1970 +0000
214 date: Thu Jan 01 00:00:06 1970 +0000
215 summary: binary-change-rename
215 summary: binary-change-rename
216
216
217 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
217 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
218 Binary file tip.bundle has changed
218 Binary file tip.bundle has changed
219 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
219 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
220 Binary file top.bundle has changed
220 Binary file top.bundle has changed
221
221
222
222
223 Add plain file
223 Add plain file
224
224
225 $ for i in 1 2 3 4 5 6 7 8 9 10; do
225 $ for i in 1 2 3 4 5 6 7 8 9 10; do
226 > echo $i >> plain
226 > echo $i >> plain
227 > done
227 > done
228
228
229 $ hg add plain
229 $ hg add plain
230 $ hg commit -i -d '7 0' -m plain plain<<EOF
230 $ hg commit -i -d '7 0' -m plain plain<<EOF
231 > y
231 > y
232 > y
233 > EOF
232 > EOF
234 diff --git a/plain b/plain
233 diff --git a/plain b/plain
235 new file mode 100644
234 new file mode 100644
236 examine changes to 'plain'? [Ynesfdaq?] y
235 examine changes to 'plain'? [Ynesfdaq?] y
237
236
238 @@ -0,0 +1,10 @@
239 +1
240 +2
241 +3
242 +4
243 +5
244 +6
245 +7
246 +8
247 +9
248 +10
249 record this change to 'plain'? [Ynesfdaq?] y
250
251 $ hg tip -p
237 $ hg tip -p
252 changeset: 7:11fb457c1be4
238 changeset: 7:11fb457c1be4
253 tag: tip
239 tag: tip
254 user: test
240 user: test
255 date: Thu Jan 01 00:00:07 1970 +0000
241 date: Thu Jan 01 00:00:07 1970 +0000
256 summary: plain
242 summary: plain
257
243
258 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
244 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
259 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
245 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
260 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
246 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
261 @@ -0,0 +1,10 @@
247 @@ -0,0 +1,10 @@
262 +1
248 +1
263 +2
249 +2
264 +3
250 +3
265 +4
251 +4
266 +5
252 +5
267 +6
253 +6
268 +7
254 +7
269 +8
255 +8
270 +9
256 +9
271 +10
257 +10
272
258
273 Modify end of plain file with username unset
259 Modify end of plain file with username unset
274
260
275 $ echo 11 >> plain
261 $ echo 11 >> plain
276 $ unset HGUSER
262 $ unset HGUSER
277 $ hg commit -i --config ui.username= -d '8 0' -m end plain
263 $ hg commit -i --config ui.username= -d '8 0' -m end plain
278 abort: no username supplied
264 abort: no username supplied
279 (use "hg config --edit" to set your username)
265 (use "hg config --edit" to set your username)
280 [255]
266 [255]
281
267
282
268
283 Modify end of plain file, also test that diffopts are accounted for
269 Modify end of plain file, also test that diffopts are accounted for
284
270
285 $ HGUSER="test"
271 $ HGUSER="test"
286 $ export HGUSER
272 $ export HGUSER
287 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
273 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
288 > y
274 > y
289 > y
275 > y
290 > EOF
276 > EOF
291 diff --git a/plain b/plain
277 diff --git a/plain b/plain
292 1 hunks, 1 lines changed
278 1 hunks, 1 lines changed
293 examine changes to 'plain'? [Ynesfdaq?] y
279 examine changes to 'plain'? [Ynesfdaq?] y
294
280
295 @@ -8,3 +8,4 @@ 7
281 @@ -8,3 +8,4 @@ 7
296 8
282 8
297 9
283 9
298 10
284 10
299 +11
285 +11
300 record this change to 'plain'? [Ynesfdaq?] y
286 record this change to 'plain'? [Ynesfdaq?] y
301
287
302
288
303 Modify end of plain file, no EOL
289 Modify end of plain file, no EOL
304
290
305 $ hg tip --template '{node}' >> plain
291 $ hg tip --template '{node}' >> plain
306 $ hg commit -i -d '9 0' -m noeol plain <<EOF
292 $ hg commit -i -d '9 0' -m noeol plain <<EOF
307 > y
293 > y
308 > y
294 > y
309 > EOF
295 > EOF
310 diff --git a/plain b/plain
296 diff --git a/plain b/plain
311 1 hunks, 1 lines changed
297 1 hunks, 1 lines changed
312 examine changes to 'plain'? [Ynesfdaq?] y
298 examine changes to 'plain'? [Ynesfdaq?] y
313
299
314 @@ -9,3 +9,4 @@
300 @@ -9,3 +9,4 @@
315 9
301 9
316 10
302 10
317 11
303 11
318 +7264f99c5f5ff3261504828afa4fb4d406c3af54
304 +7264f99c5f5ff3261504828afa4fb4d406c3af54
319 \ No newline at end of file
305 \ No newline at end of file
320 record this change to 'plain'? [Ynesfdaq?] y
306 record this change to 'plain'? [Ynesfdaq?] y
321
307
322
308
323 Modify end of plain file, add EOL
309 Modify end of plain file, add EOL
324
310
325 $ echo >> plain
311 $ echo >> plain
326 $ echo 1 > plain2
312 $ echo 1 > plain2
327 $ hg add plain2
313 $ hg add plain2
328 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
314 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
329 > y
315 > y
330 > y
316 > y
331 > y
317 > y
332 > y
318 > y
333 > EOF
319 > EOF
334 diff --git a/plain b/plain
320 diff --git a/plain b/plain
335 1 hunks, 1 lines changed
321 1 hunks, 1 lines changed
336 examine changes to 'plain'? [Ynesfdaq?] y
322 examine changes to 'plain'? [Ynesfdaq?] y
337
323
338 @@ -9,4 +9,4 @@
324 @@ -9,4 +9,4 @@
339 9
325 9
340 10
326 10
341 11
327 11
342 -7264f99c5f5ff3261504828afa4fb4d406c3af54
328 -7264f99c5f5ff3261504828afa4fb4d406c3af54
343 \ No newline at end of file
329 \ No newline at end of file
344 +7264f99c5f5ff3261504828afa4fb4d406c3af54
330 +7264f99c5f5ff3261504828afa4fb4d406c3af54
345 record change 1/2 to 'plain'? [Ynesfdaq?] y
331 record change 1/2 to 'plain'? [Ynesfdaq?] y
346
332
347 diff --git a/plain2 b/plain2
333 diff --git a/plain2 b/plain2
348 new file mode 100644
334 new file mode 100644
349 examine changes to 'plain2'? [Ynesfdaq?] y
335 examine changes to 'plain2'? [Ynesfdaq?] y
350
336
351 @@ -0,0 +1,1 @@
352 +1
353 record change 2/2 to 'plain2'? [Ynesfdaq?] y
354
355 Modify beginning, trim end, record both, add another file to test
337 Modify beginning, trim end, record both, add another file to test
356 changes numbering
338 changes numbering
357
339
358 $ rm plain
340 $ rm plain
359 $ for i in 2 2 3 4 5 6 7 8 9 10; do
341 $ for i in 2 2 3 4 5 6 7 8 9 10; do
360 > echo $i >> plain
342 > echo $i >> plain
361 > done
343 > done
362 $ echo 2 >> plain2
344 $ echo 2 >> plain2
363
345
364 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
346 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
365 > y
347 > y
366 > y
348 > y
367 > y
349 > y
368 > y
350 > y
369 > y
351 > y
370 > EOF
352 > EOF
371 diff --git a/plain b/plain
353 diff --git a/plain b/plain
372 2 hunks, 3 lines changed
354 2 hunks, 3 lines changed
373 examine changes to 'plain'? [Ynesfdaq?] y
355 examine changes to 'plain'? [Ynesfdaq?] y
374
356
375 @@ -1,4 +1,4 @@
357 @@ -1,4 +1,4 @@
376 -1
358 -1
377 +2
359 +2
378 2
360 2
379 3
361 3
380 4
362 4
381 record change 1/3 to 'plain'? [Ynesfdaq?] y
363 record change 1/3 to 'plain'? [Ynesfdaq?] y
382
364
383 @@ -8,5 +8,3 @@
365 @@ -8,5 +8,3 @@
384 8
366 8
385 9
367 9
386 10
368 10
387 -11
369 -11
388 -7264f99c5f5ff3261504828afa4fb4d406c3af54
370 -7264f99c5f5ff3261504828afa4fb4d406c3af54
389 record change 2/3 to 'plain'? [Ynesfdaq?] y
371 record change 2/3 to 'plain'? [Ynesfdaq?] y
390
372
391 diff --git a/plain2 b/plain2
373 diff --git a/plain2 b/plain2
392 1 hunks, 1 lines changed
374 1 hunks, 1 lines changed
393 examine changes to 'plain2'? [Ynesfdaq?] y
375 examine changes to 'plain2'? [Ynesfdaq?] y
394
376
395 @@ -1,1 +1,2 @@
377 @@ -1,1 +1,2 @@
396 1
378 1
397 +2
379 +2
398 record change 3/3 to 'plain2'? [Ynesfdaq?] y
380 record change 3/3 to 'plain2'? [Ynesfdaq?] y
399
381
400
382
401 $ hg tip -p
383 $ hg tip -p
402 changeset: 11:21df83db12b8
384 changeset: 11:21df83db12b8
403 tag: tip
385 tag: tip
404 user: test
386 user: test
405 date: Thu Jan 01 00:00:10 1970 +0000
387 date: Thu Jan 01 00:00:10 1970 +0000
406 summary: begin-and-end
388 summary: begin-and-end
407
389
408 diff -r ddb8b281c3ff -r 21df83db12b8 plain
390 diff -r ddb8b281c3ff -r 21df83db12b8 plain
409 --- a/plain Thu Jan 01 00:00:10 1970 +0000
391 --- a/plain Thu Jan 01 00:00:10 1970 +0000
410 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
392 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
411 @@ -1,4 +1,4 @@
393 @@ -1,4 +1,4 @@
412 -1
394 -1
413 +2
395 +2
414 2
396 2
415 3
397 3
416 4
398 4
417 @@ -8,5 +8,3 @@
399 @@ -8,5 +8,3 @@
418 8
400 8
419 9
401 9
420 10
402 10
421 -11
403 -11
422 -7264f99c5f5ff3261504828afa4fb4d406c3af54
404 -7264f99c5f5ff3261504828afa4fb4d406c3af54
423 diff -r ddb8b281c3ff -r 21df83db12b8 plain2
405 diff -r ddb8b281c3ff -r 21df83db12b8 plain2
424 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
406 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
425 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
407 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
426 @@ -1,1 +1,2 @@
408 @@ -1,1 +1,2 @@
427 1
409 1
428 +2
410 +2
429
411
430
412
431 Trim beginning, modify end
413 Trim beginning, modify end
432
414
433 $ rm plain
415 $ rm plain
434 > for i in 4 5 6 7 8 9 10.new; do
416 > for i in 4 5 6 7 8 9 10.new; do
435 > echo $i >> plain
417 > echo $i >> plain
436 > done
418 > done
437
419
438 Record end
420 Record end
439
421
440 $ hg commit -i -d '11 0' -m end-only plain <<EOF
422 $ hg commit -i -d '11 0' -m end-only plain <<EOF
441 > y
423 > y
442 > n
424 > n
443 > y
425 > y
444 > EOF
426 > EOF
445 diff --git a/plain b/plain
427 diff --git a/plain b/plain
446 2 hunks, 4 lines changed
428 2 hunks, 4 lines changed
447 examine changes to 'plain'? [Ynesfdaq?] y
429 examine changes to 'plain'? [Ynesfdaq?] y
448
430
449 @@ -1,9 +1,6 @@
431 @@ -1,9 +1,6 @@
450 -2
432 -2
451 -2
433 -2
452 -3
434 -3
453 4
435 4
454 5
436 5
455 6
437 6
456 7
438 7
457 8
439 8
458 9
440 9
459 record change 1/2 to 'plain'? [Ynesfdaq?] n
441 record change 1/2 to 'plain'? [Ynesfdaq?] n
460
442
461 @@ -4,7 +1,7 @@
443 @@ -4,7 +1,7 @@
462 4
444 4
463 5
445 5
464 6
446 6
465 7
447 7
466 8
448 8
467 9
449 9
468 -10
450 -10
469 +10.new
451 +10.new
470 record change 2/2 to 'plain'? [Ynesfdaq?] y
452 record change 2/2 to 'plain'? [Ynesfdaq?] y
471
453
472
454
473 $ hg tip -p
455 $ hg tip -p
474 changeset: 12:99337501826f
456 changeset: 12:99337501826f
475 tag: tip
457 tag: tip
476 user: test
458 user: test
477 date: Thu Jan 01 00:00:11 1970 +0000
459 date: Thu Jan 01 00:00:11 1970 +0000
478 summary: end-only
460 summary: end-only
479
461
480 diff -r 21df83db12b8 -r 99337501826f plain
462 diff -r 21df83db12b8 -r 99337501826f plain
481 --- a/plain Thu Jan 01 00:00:10 1970 +0000
463 --- a/plain Thu Jan 01 00:00:10 1970 +0000
482 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
464 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
483 @@ -7,4 +7,4 @@
465 @@ -7,4 +7,4 @@
484 7
466 7
485 8
467 8
486 9
468 9
487 -10
469 -10
488 +10.new
470 +10.new
489
471
490
472
491 Record beginning
473 Record beginning
492
474
493 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
475 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
494 > y
476 > y
495 > y
477 > y
496 > EOF
478 > EOF
497 diff --git a/plain b/plain
479 diff --git a/plain b/plain
498 1 hunks, 3 lines changed
480 1 hunks, 3 lines changed
499 examine changes to 'plain'? [Ynesfdaq?] y
481 examine changes to 'plain'? [Ynesfdaq?] y
500
482
501 @@ -1,6 +1,3 @@
483 @@ -1,6 +1,3 @@
502 -2
484 -2
503 -2
485 -2
504 -3
486 -3
505 4
487 4
506 5
488 5
507 6
489 6
508 record this change to 'plain'? [Ynesfdaq?] y
490 record this change to 'plain'? [Ynesfdaq?] y
509
491
510
492
511 $ hg tip -p
493 $ hg tip -p
512 changeset: 13:bbd45465d540
494 changeset: 13:bbd45465d540
513 tag: tip
495 tag: tip
514 user: test
496 user: test
515 date: Thu Jan 01 00:00:12 1970 +0000
497 date: Thu Jan 01 00:00:12 1970 +0000
516 summary: begin-only
498 summary: begin-only
517
499
518 diff -r 99337501826f -r bbd45465d540 plain
500 diff -r 99337501826f -r bbd45465d540 plain
519 --- a/plain Thu Jan 01 00:00:11 1970 +0000
501 --- a/plain Thu Jan 01 00:00:11 1970 +0000
520 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
502 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
521 @@ -1,6 +1,3 @@
503 @@ -1,6 +1,3 @@
522 -2
504 -2
523 -2
505 -2
524 -3
506 -3
525 4
507 4
526 5
508 5
527 6
509 6
528
510
529
511
530 Add to beginning, trim from end
512 Add to beginning, trim from end
531
513
532 $ rm plain
514 $ rm plain
533 $ for i in 1 2 3 4 5 6 7 8 9; do
515 $ for i in 1 2 3 4 5 6 7 8 9; do
534 > echo $i >> plain
516 > echo $i >> plain
535 > done
517 > done
536
518
537 Record end
519 Record end
538
520
539 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
521 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
540 > y
522 > y
541 > n
523 > n
542 > y
524 > y
543 > EOF
525 > EOF
544 diff --git a/plain b/plain
526 diff --git a/plain b/plain
545 2 hunks, 4 lines changed
527 2 hunks, 4 lines changed
546 examine changes to 'plain'? [Ynesfdaq?] y
528 examine changes to 'plain'? [Ynesfdaq?] y
547
529
548 @@ -1,6 +1,9 @@
530 @@ -1,6 +1,9 @@
549 +1
531 +1
550 +2
532 +2
551 +3
533 +3
552 4
534 4
553 5
535 5
554 6
536 6
555 7
537 7
556 8
538 8
557 9
539 9
558 record change 1/2 to 'plain'? [Ynesfdaq?] n
540 record change 1/2 to 'plain'? [Ynesfdaq?] n
559
541
560 @@ -1,7 +4,6 @@
542 @@ -1,7 +4,6 @@
561 4
543 4
562 5
544 5
563 6
545 6
564 7
546 7
565 8
547 8
566 9
548 9
567 -10.new
549 -10.new
568 record change 2/2 to 'plain'? [Ynesfdaq?] y
550 record change 2/2 to 'plain'? [Ynesfdaq?] y
569
551
570
552
571 Add to beginning, middle, end
553 Add to beginning, middle, end
572
554
573 $ rm plain
555 $ rm plain
574 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
556 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
575 > echo $i >> plain
557 > echo $i >> plain
576 > done
558 > done
577
559
578 Record beginning, middle, and test that format-breaking diffopts are ignored
560 Record beginning, middle, and test that format-breaking diffopts are ignored
579
561
580 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
562 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
581 > y
563 > y
582 > y
564 > y
583 > y
565 > y
584 > n
566 > n
585 > EOF
567 > EOF
586 diff --git a/plain b/plain
568 diff --git a/plain b/plain
587 3 hunks, 7 lines changed
569 3 hunks, 7 lines changed
588 examine changes to 'plain'? [Ynesfdaq?] y
570 examine changes to 'plain'? [Ynesfdaq?] y
589
571
590 @@ -1,2 +1,5 @@
572 @@ -1,2 +1,5 @@
591 +1
573 +1
592 +2
574 +2
593 +3
575 +3
594 4
576 4
595 5
577 5
596 record change 1/3 to 'plain'? [Ynesfdaq?] y
578 record change 1/3 to 'plain'? [Ynesfdaq?] y
597
579
598 @@ -1,6 +4,8 @@
580 @@ -1,6 +4,8 @@
599 4
581 4
600 5
582 5
601 +5.new
583 +5.new
602 +5.reallynew
584 +5.reallynew
603 6
585 6
604 7
586 7
605 8
587 8
606 9
588 9
607 record change 2/3 to 'plain'? [Ynesfdaq?] y
589 record change 2/3 to 'plain'? [Ynesfdaq?] y
608
590
609 @@ -3,4 +8,6 @@
591 @@ -3,4 +8,6 @@
610 6
592 6
611 7
593 7
612 8
594 8
613 9
595 9
614 +10
596 +10
615 +11
597 +11
616 record change 3/3 to 'plain'? [Ynesfdaq?] n
598 record change 3/3 to 'plain'? [Ynesfdaq?] n
617
599
618
600
619 $ hg tip -p
601 $ hg tip -p
620 changeset: 15:f34a7937ec33
602 changeset: 15:f34a7937ec33
621 tag: tip
603 tag: tip
622 user: test
604 user: test
623 date: Thu Jan 01 00:00:14 1970 +0000
605 date: Thu Jan 01 00:00:14 1970 +0000
624 summary: middle-only
606 summary: middle-only
625
607
626 diff -r 82c065d0b850 -r f34a7937ec33 plain
608 diff -r 82c065d0b850 -r f34a7937ec33 plain
627 --- a/plain Thu Jan 01 00:00:13 1970 +0000
609 --- a/plain Thu Jan 01 00:00:13 1970 +0000
628 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
610 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
629 @@ -1,5 +1,10 @@
611 @@ -1,5 +1,10 @@
630 +1
612 +1
631 +2
613 +2
632 +3
614 +3
633 4
615 4
634 5
616 5
635 +5.new
617 +5.new
636 +5.reallynew
618 +5.reallynew
637 6
619 6
638 7
620 7
639 8
621 8
640
622
641
623
642 Record end
624 Record end
643
625
644 $ hg commit -i -d '15 0' -m end-only plain <<EOF
626 $ hg commit -i -d '15 0' -m end-only plain <<EOF
645 > y
627 > y
646 > y
628 > y
647 > EOF
629 > EOF
648 diff --git a/plain b/plain
630 diff --git a/plain b/plain
649 1 hunks, 2 lines changed
631 1 hunks, 2 lines changed
650 examine changes to 'plain'? [Ynesfdaq?] y
632 examine changes to 'plain'? [Ynesfdaq?] y
651
633
652 @@ -9,3 +9,5 @@
634 @@ -9,3 +9,5 @@
653 7
635 7
654 8
636 8
655 9
637 9
656 +10
638 +10
657 +11
639 +11
658 record this change to 'plain'? [Ynesfdaq?] y
640 record this change to 'plain'? [Ynesfdaq?] y
659
641
660
642
661 $ hg tip -p
643 $ hg tip -p
662 changeset: 16:f9900b71a04c
644 changeset: 16:f9900b71a04c
663 tag: tip
645 tag: tip
664 user: test
646 user: test
665 date: Thu Jan 01 00:00:15 1970 +0000
647 date: Thu Jan 01 00:00:15 1970 +0000
666 summary: end-only
648 summary: end-only
667
649
668 diff -r f34a7937ec33 -r f9900b71a04c plain
650 diff -r f34a7937ec33 -r f9900b71a04c plain
669 --- a/plain Thu Jan 01 00:00:14 1970 +0000
651 --- a/plain Thu Jan 01 00:00:14 1970 +0000
670 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
652 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
671 @@ -9,3 +9,5 @@
653 @@ -9,3 +9,5 @@
672 7
654 7
673 8
655 8
674 9
656 9
675 +10
657 +10
676 +11
658 +11
677
659
678
660
679 $ mkdir subdir
661 $ mkdir subdir
680 $ cd subdir
662 $ cd subdir
681 $ echo a > a
663 $ echo a > a
682 $ hg ci -d '16 0' -Amsubdir
664 $ hg ci -d '16 0' -Amsubdir
683 adding subdir/a
665 adding subdir/a
684
666
685 $ echo a >> a
667 $ echo a >> a
686 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
668 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
687 > y
669 > y
688 > y
670 > y
689 > EOF
671 > EOF
690 diff --git a/subdir/a b/subdir/a
672 diff --git a/subdir/a b/subdir/a
691 1 hunks, 1 lines changed
673 1 hunks, 1 lines changed
692 examine changes to 'subdir/a'? [Ynesfdaq?] y
674 examine changes to 'subdir/a'? [Ynesfdaq?] y
693
675
694 @@ -1,1 +1,2 @@
676 @@ -1,1 +1,2 @@
695 a
677 a
696 +a
678 +a
697 record this change to 'subdir/a'? [Ynesfdaq?] y
679 record this change to 'subdir/a'? [Ynesfdaq?] y
698
680
699
681
700 $ hg tip -p
682 $ hg tip -p
701 changeset: 18:61be427a9deb
683 changeset: 18:61be427a9deb
702 tag: tip
684 tag: tip
703 user: test
685 user: test
704 date: Thu Jan 01 00:00:16 1970 +0000
686 date: Thu Jan 01 00:00:16 1970 +0000
705 summary: subdir-change
687 summary: subdir-change
706
688
707 diff -r a7ffae4d61cb -r 61be427a9deb subdir/a
689 diff -r a7ffae4d61cb -r 61be427a9deb subdir/a
708 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
690 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
709 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
691 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
710 @@ -1,1 +1,2 @@
692 @@ -1,1 +1,2 @@
711 a
693 a
712 +a
694 +a
713
695
714
696
715 $ echo a > f1
697 $ echo a > f1
716 $ echo b > f2
698 $ echo b > f2
717 $ hg add f1 f2
699 $ hg add f1 f2
718
700
719 $ hg ci -mz -d '17 0'
701 $ hg ci -mz -d '17 0'
720
702
721 $ echo a >> f1
703 $ echo a >> f1
722 $ echo b >> f2
704 $ echo b >> f2
723
705
724 Help, quit
706 Help, quit
725
707
726 $ hg commit -i <<EOF
708 $ hg commit -i <<EOF
727 > ?
709 > ?
728 > q
710 > q
729 > EOF
711 > EOF
730 diff --git a/subdir/f1 b/subdir/f1
712 diff --git a/subdir/f1 b/subdir/f1
731 1 hunks, 1 lines changed
713 1 hunks, 1 lines changed
732 examine changes to 'subdir/f1'? [Ynesfdaq?] ?
714 examine changes to 'subdir/f1'? [Ynesfdaq?] ?
733
715
734 y - yes, record this change
716 y - yes, record this change
735 n - no, skip this change
717 n - no, skip this change
736 e - edit this change manually
718 e - edit this change manually
737 s - skip remaining changes to this file
719 s - skip remaining changes to this file
738 f - record remaining changes to this file
720 f - record remaining changes to this file
739 d - done, skip remaining changes and files
721 d - done, skip remaining changes and files
740 a - record all changes to all remaining files
722 a - record all changes to all remaining files
741 q - quit, recording no changes
723 q - quit, recording no changes
742 ? - ? (display help)
724 ? - ? (display help)
743 examine changes to 'subdir/f1'? [Ynesfdaq?] q
725 examine changes to 'subdir/f1'? [Ynesfdaq?] q
744
726
745 abort: user quit
727 abort: user quit
746 [255]
728 [255]
747
729
748 Skip
730 Skip
749
731
750 $ hg commit -i <<EOF
732 $ hg commit -i <<EOF
751 > s
733 > s
752 > EOF
734 > EOF
753 diff --git a/subdir/f1 b/subdir/f1
735 diff --git a/subdir/f1 b/subdir/f1
754 1 hunks, 1 lines changed
736 1 hunks, 1 lines changed
755 examine changes to 'subdir/f1'? [Ynesfdaq?] s
737 examine changes to 'subdir/f1'? [Ynesfdaq?] s
756
738
757 diff --git a/subdir/f2 b/subdir/f2
739 diff --git a/subdir/f2 b/subdir/f2
758 1 hunks, 1 lines changed
740 1 hunks, 1 lines changed
759 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
741 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
760 [255]
742 [255]
761
743
762 No
744 No
763
745
764 $ hg commit -i <<EOF
746 $ hg commit -i <<EOF
765 > n
747 > n
766 > EOF
748 > EOF
767 diff --git a/subdir/f1 b/subdir/f1
749 diff --git a/subdir/f1 b/subdir/f1
768 1 hunks, 1 lines changed
750 1 hunks, 1 lines changed
769 examine changes to 'subdir/f1'? [Ynesfdaq?] n
751 examine changes to 'subdir/f1'? [Ynesfdaq?] n
770
752
771 diff --git a/subdir/f2 b/subdir/f2
753 diff --git a/subdir/f2 b/subdir/f2
772 1 hunks, 1 lines changed
754 1 hunks, 1 lines changed
773 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
755 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
774 [255]
756 [255]
775
757
776 f, quit
758 f, quit
777
759
778 $ hg commit -i <<EOF
760 $ hg commit -i <<EOF
779 > f
761 > f
780 > q
762 > q
781 > EOF
763 > EOF
782 diff --git a/subdir/f1 b/subdir/f1
764 diff --git a/subdir/f1 b/subdir/f1
783 1 hunks, 1 lines changed
765 1 hunks, 1 lines changed
784 examine changes to 'subdir/f1'? [Ynesfdaq?] f
766 examine changes to 'subdir/f1'? [Ynesfdaq?] f
785
767
786 diff --git a/subdir/f2 b/subdir/f2
768 diff --git a/subdir/f2 b/subdir/f2
787 1 hunks, 1 lines changed
769 1 hunks, 1 lines changed
788 examine changes to 'subdir/f2'? [Ynesfdaq?] q
770 examine changes to 'subdir/f2'? [Ynesfdaq?] q
789
771
790 abort: user quit
772 abort: user quit
791 [255]
773 [255]
792
774
793 s, all
775 s, all
794
776
795 $ hg commit -i -d '18 0' -mx <<EOF
777 $ hg commit -i -d '18 0' -mx <<EOF
796 > s
778 > s
797 > a
779 > a
798 > EOF
780 > EOF
799 diff --git a/subdir/f1 b/subdir/f1
781 diff --git a/subdir/f1 b/subdir/f1
800 1 hunks, 1 lines changed
782 1 hunks, 1 lines changed
801 examine changes to 'subdir/f1'? [Ynesfdaq?] s
783 examine changes to 'subdir/f1'? [Ynesfdaq?] s
802
784
803 diff --git a/subdir/f2 b/subdir/f2
785 diff --git a/subdir/f2 b/subdir/f2
804 1 hunks, 1 lines changed
786 1 hunks, 1 lines changed
805 examine changes to 'subdir/f2'? [Ynesfdaq?] a
787 examine changes to 'subdir/f2'? [Ynesfdaq?] a
806
788
807
789
808 $ hg tip -p
790 $ hg tip -p
809 changeset: 20:b3df3dda369a
791 changeset: 20:b3df3dda369a
810 tag: tip
792 tag: tip
811 user: test
793 user: test
812 date: Thu Jan 01 00:00:18 1970 +0000
794 date: Thu Jan 01 00:00:18 1970 +0000
813 summary: x
795 summary: x
814
796
815 diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2
797 diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2
816 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
798 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
817 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
799 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
818 @@ -1,1 +1,2 @@
800 @@ -1,1 +1,2 @@
819 b
801 b
820 +b
802 +b
821
803
822
804
823 f
805 f
824
806
825 $ hg commit -i -d '19 0' -my <<EOF
807 $ hg commit -i -d '19 0' -my <<EOF
826 > f
808 > f
827 > EOF
809 > EOF
828 diff --git a/subdir/f1 b/subdir/f1
810 diff --git a/subdir/f1 b/subdir/f1
829 1 hunks, 1 lines changed
811 1 hunks, 1 lines changed
830 examine changes to 'subdir/f1'? [Ynesfdaq?] f
812 examine changes to 'subdir/f1'? [Ynesfdaq?] f
831
813
832
814
833 $ hg tip -p
815 $ hg tip -p
834 changeset: 21:38ec577f126b
816 changeset: 21:38ec577f126b
835 tag: tip
817 tag: tip
836 user: test
818 user: test
837 date: Thu Jan 01 00:00:19 1970 +0000
819 date: Thu Jan 01 00:00:19 1970 +0000
838 summary: y
820 summary: y
839
821
840 diff -r b3df3dda369a -r 38ec577f126b subdir/f1
822 diff -r b3df3dda369a -r 38ec577f126b subdir/f1
841 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
823 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
842 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
824 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
843 @@ -1,1 +1,2 @@
825 @@ -1,1 +1,2 @@
844 a
826 a
845 +a
827 +a
846
828
847
829
848 #if execbit
830 #if execbit
849
831
850 Preserve chmod +x
832 Preserve chmod +x
851
833
852 $ chmod +x f1
834 $ chmod +x f1
853 $ echo a >> f1
835 $ echo a >> f1
854 $ hg commit -i -d '20 0' -mz <<EOF
836 $ hg commit -i -d '20 0' -mz <<EOF
855 > y
837 > y
856 > y
838 > y
857 > y
839 > y
858 > EOF
840 > EOF
859 diff --git a/subdir/f1 b/subdir/f1
841 diff --git a/subdir/f1 b/subdir/f1
860 old mode 100644
842 old mode 100644
861 new mode 100755
843 new mode 100755
862 1 hunks, 1 lines changed
844 1 hunks, 1 lines changed
863 examine changes to 'subdir/f1'? [Ynesfdaq?] y
845 examine changes to 'subdir/f1'? [Ynesfdaq?] y
864
846
865 @@ -1,2 +1,3 @@
847 @@ -1,2 +1,3 @@
866 a
848 a
867 a
849 a
868 +a
850 +a
869 record this change to 'subdir/f1'? [Ynesfdaq?] y
851 record this change to 'subdir/f1'? [Ynesfdaq?] y
870
852
871
853
872 $ hg tip --config diff.git=True -p
854 $ hg tip --config diff.git=True -p
873 changeset: 22:3261adceb075
855 changeset: 22:3261adceb075
874 tag: tip
856 tag: tip
875 user: test
857 user: test
876 date: Thu Jan 01 00:00:20 1970 +0000
858 date: Thu Jan 01 00:00:20 1970 +0000
877 summary: z
859 summary: z
878
860
879 diff --git a/subdir/f1 b/subdir/f1
861 diff --git a/subdir/f1 b/subdir/f1
880 old mode 100644
862 old mode 100644
881 new mode 100755
863 new mode 100755
882 --- a/subdir/f1
864 --- a/subdir/f1
883 +++ b/subdir/f1
865 +++ b/subdir/f1
884 @@ -1,2 +1,3 @@
866 @@ -1,2 +1,3 @@
885 a
867 a
886 a
868 a
887 +a
869 +a
888
870
889
871
890 Preserve execute permission on original
872 Preserve execute permission on original
891
873
892 $ echo b >> f1
874 $ echo b >> f1
893 $ hg commit -i -d '21 0' -maa <<EOF
875 $ hg commit -i -d '21 0' -maa <<EOF
894 > y
876 > y
895 > y
877 > y
896 > y
878 > y
897 > EOF
879 > EOF
898 diff --git a/subdir/f1 b/subdir/f1
880 diff --git a/subdir/f1 b/subdir/f1
899 1 hunks, 1 lines changed
881 1 hunks, 1 lines changed
900 examine changes to 'subdir/f1'? [Ynesfdaq?] y
882 examine changes to 'subdir/f1'? [Ynesfdaq?] y
901
883
902 @@ -1,3 +1,4 @@
884 @@ -1,3 +1,4 @@
903 a
885 a
904 a
886 a
905 a
887 a
906 +b
888 +b
907 record this change to 'subdir/f1'? [Ynesfdaq?] y
889 record this change to 'subdir/f1'? [Ynesfdaq?] y
908
890
909
891
910 $ hg tip --config diff.git=True -p
892 $ hg tip --config diff.git=True -p
911 changeset: 23:b429867550db
893 changeset: 23:b429867550db
912 tag: tip
894 tag: tip
913 user: test
895 user: test
914 date: Thu Jan 01 00:00:21 1970 +0000
896 date: Thu Jan 01 00:00:21 1970 +0000
915 summary: aa
897 summary: aa
916
898
917 diff --git a/subdir/f1 b/subdir/f1
899 diff --git a/subdir/f1 b/subdir/f1
918 --- a/subdir/f1
900 --- a/subdir/f1
919 +++ b/subdir/f1
901 +++ b/subdir/f1
920 @@ -1,3 +1,4 @@
902 @@ -1,3 +1,4 @@
921 a
903 a
922 a
904 a
923 a
905 a
924 +b
906 +b
925
907
926
908
927 Preserve chmod -x
909 Preserve chmod -x
928
910
929 $ chmod -x f1
911 $ chmod -x f1
930 $ echo c >> f1
912 $ echo c >> f1
931 $ hg commit -i -d '22 0' -mab <<EOF
913 $ hg commit -i -d '22 0' -mab <<EOF
932 > y
914 > y
933 > y
915 > y
934 > y
916 > y
935 > EOF
917 > EOF
936 diff --git a/subdir/f1 b/subdir/f1
918 diff --git a/subdir/f1 b/subdir/f1
937 old mode 100755
919 old mode 100755
938 new mode 100644
920 new mode 100644
939 1 hunks, 1 lines changed
921 1 hunks, 1 lines changed
940 examine changes to 'subdir/f1'? [Ynesfdaq?] y
922 examine changes to 'subdir/f1'? [Ynesfdaq?] y
941
923
942 @@ -2,3 +2,4 @@
924 @@ -2,3 +2,4 @@
943 a
925 a
944 a
926 a
945 b
927 b
946 +c
928 +c
947 record this change to 'subdir/f1'? [Ynesfdaq?] y
929 record this change to 'subdir/f1'? [Ynesfdaq?] y
948
930
949
931
950 $ hg tip --config diff.git=True -p
932 $ hg tip --config diff.git=True -p
951 changeset: 24:0b082130c20a
933 changeset: 24:0b082130c20a
952 tag: tip
934 tag: tip
953 user: test
935 user: test
954 date: Thu Jan 01 00:00:22 1970 +0000
936 date: Thu Jan 01 00:00:22 1970 +0000
955 summary: ab
937 summary: ab
956
938
957 diff --git a/subdir/f1 b/subdir/f1
939 diff --git a/subdir/f1 b/subdir/f1
958 old mode 100755
940 old mode 100755
959 new mode 100644
941 new mode 100644
960 --- a/subdir/f1
942 --- a/subdir/f1
961 +++ b/subdir/f1
943 +++ b/subdir/f1
962 @@ -2,3 +2,4 @@
944 @@ -2,3 +2,4 @@
963 a
945 a
964 a
946 a
965 b
947 b
966 +c
948 +c
967
949
968
950
969 #else
951 #else
970
952
971 Slightly bogus tests to get almost same repo structure as when x bit is used
953 Slightly bogus tests to get almost same repo structure as when x bit is used
972 - but with different hashes.
954 - but with different hashes.
973
955
974 Mock "Preserve chmod +x"
956 Mock "Preserve chmod +x"
975
957
976 $ echo a >> f1
958 $ echo a >> f1
977 $ hg commit -i -d '20 0' -mz <<EOF
959 $ hg commit -i -d '20 0' -mz <<EOF
978 > y
960 > y
979 > y
961 > y
980 > y
962 > y
981 > EOF
963 > EOF
982 diff --git a/subdir/f1 b/subdir/f1
964 diff --git a/subdir/f1 b/subdir/f1
983 1 hunks, 1 lines changed
965 1 hunks, 1 lines changed
984 examine changes to 'subdir/f1'? [Ynesfdaq?] y
966 examine changes to 'subdir/f1'? [Ynesfdaq?] y
985
967
986 @@ -1,2 +1,3 @@
968 @@ -1,2 +1,3 @@
987 a
969 a
988 a
970 a
989 +a
971 +a
990 record this change to 'subdir/f1'? [Ynesfdaq?] y
972 record this change to 'subdir/f1'? [Ynesfdaq?] y
991
973
992
974
993 $ hg tip --config diff.git=True -p
975 $ hg tip --config diff.git=True -p
994 changeset: 22:0d463bd428f5
976 changeset: 22:0d463bd428f5
995 tag: tip
977 tag: tip
996 user: test
978 user: test
997 date: Thu Jan 01 00:00:20 1970 +0000
979 date: Thu Jan 01 00:00:20 1970 +0000
998 summary: z
980 summary: z
999
981
1000 diff --git a/subdir/f1 b/subdir/f1
982 diff --git a/subdir/f1 b/subdir/f1
1001 --- a/subdir/f1
983 --- a/subdir/f1
1002 +++ b/subdir/f1
984 +++ b/subdir/f1
1003 @@ -1,2 +1,3 @@
985 @@ -1,2 +1,3 @@
1004 a
986 a
1005 a
987 a
1006 +a
988 +a
1007
989
1008
990
1009 Mock "Preserve execute permission on original"
991 Mock "Preserve execute permission on original"
1010
992
1011 $ echo b >> f1
993 $ echo b >> f1
1012 $ hg commit -i -d '21 0' -maa <<EOF
994 $ hg commit -i -d '21 0' -maa <<EOF
1013 > y
995 > y
1014 > y
996 > y
1015 > y
997 > y
1016 > EOF
998 > EOF
1017 diff --git a/subdir/f1 b/subdir/f1
999 diff --git a/subdir/f1 b/subdir/f1
1018 1 hunks, 1 lines changed
1000 1 hunks, 1 lines changed
1019 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1001 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1020
1002
1021 @@ -1,3 +1,4 @@
1003 @@ -1,3 +1,4 @@
1022 a
1004 a
1023 a
1005 a
1024 a
1006 a
1025 +b
1007 +b
1026 record this change to 'subdir/f1'? [Ynesfdaq?] y
1008 record this change to 'subdir/f1'? [Ynesfdaq?] y
1027
1009
1028
1010
1029 $ hg tip --config diff.git=True -p
1011 $ hg tip --config diff.git=True -p
1030 changeset: 23:0eab41a3e524
1012 changeset: 23:0eab41a3e524
1031 tag: tip
1013 tag: tip
1032 user: test
1014 user: test
1033 date: Thu Jan 01 00:00:21 1970 +0000
1015 date: Thu Jan 01 00:00:21 1970 +0000
1034 summary: aa
1016 summary: aa
1035
1017
1036 diff --git a/subdir/f1 b/subdir/f1
1018 diff --git a/subdir/f1 b/subdir/f1
1037 --- a/subdir/f1
1019 --- a/subdir/f1
1038 +++ b/subdir/f1
1020 +++ b/subdir/f1
1039 @@ -1,3 +1,4 @@
1021 @@ -1,3 +1,4 @@
1040 a
1022 a
1041 a
1023 a
1042 a
1024 a
1043 +b
1025 +b
1044
1026
1045
1027
1046 Mock "Preserve chmod -x"
1028 Mock "Preserve chmod -x"
1047
1029
1048 $ chmod -x f1
1030 $ chmod -x f1
1049 $ echo c >> f1
1031 $ echo c >> f1
1050 $ hg commit -i -d '22 0' -mab <<EOF
1032 $ hg commit -i -d '22 0' -mab <<EOF
1051 > y
1033 > y
1052 > y
1034 > y
1053 > y
1035 > y
1054 > EOF
1036 > EOF
1055 diff --git a/subdir/f1 b/subdir/f1
1037 diff --git a/subdir/f1 b/subdir/f1
1056 1 hunks, 1 lines changed
1038 1 hunks, 1 lines changed
1057 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1039 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1058
1040
1059 @@ -2,3 +2,4 @@
1041 @@ -2,3 +2,4 @@
1060 a
1042 a
1061 a
1043 a
1062 b
1044 b
1063 +c
1045 +c
1064 record this change to 'subdir/f1'? [Ynesfdaq?] y
1046 record this change to 'subdir/f1'? [Ynesfdaq?] y
1065
1047
1066
1048
1067 $ hg tip --config diff.git=True -p
1049 $ hg tip --config diff.git=True -p
1068 changeset: 24:f4f718f27b7c
1050 changeset: 24:f4f718f27b7c
1069 tag: tip
1051 tag: tip
1070 user: test
1052 user: test
1071 date: Thu Jan 01 00:00:22 1970 +0000
1053 date: Thu Jan 01 00:00:22 1970 +0000
1072 summary: ab
1054 summary: ab
1073
1055
1074 diff --git a/subdir/f1 b/subdir/f1
1056 diff --git a/subdir/f1 b/subdir/f1
1075 --- a/subdir/f1
1057 --- a/subdir/f1
1076 +++ b/subdir/f1
1058 +++ b/subdir/f1
1077 @@ -2,3 +2,4 @@
1059 @@ -2,3 +2,4 @@
1078 a
1060 a
1079 a
1061 a
1080 b
1062 b
1081 +c
1063 +c
1082
1064
1083
1065
1084 #endif
1066 #endif
1085
1067
1086 $ cd ..
1068 $ cd ..
1087
1069
1088
1070
1089 Abort early when a merge is in progress
1071 Abort early when a merge is in progress
1090
1072
1091 $ hg up 4
1073 $ hg up 4
1092 1 files updated, 0 files merged, 6 files removed, 0 files unresolved
1074 1 files updated, 0 files merged, 6 files removed, 0 files unresolved
1093
1075
1094 $ touch iwillmergethat
1076 $ touch iwillmergethat
1095 $ hg add iwillmergethat
1077 $ hg add iwillmergethat
1096
1078
1097 $ hg branch thatbranch
1079 $ hg branch thatbranch
1098 marked working directory as branch thatbranch
1080 marked working directory as branch thatbranch
1099 (branches are permanent and global, did you want a bookmark?)
1081 (branches are permanent and global, did you want a bookmark?)
1100
1082
1101 $ hg ci -m'new head'
1083 $ hg ci -m'new head'
1102
1084
1103 $ hg up default
1085 $ hg up default
1104 6 files updated, 0 files merged, 2 files removed, 0 files unresolved
1086 6 files updated, 0 files merged, 2 files removed, 0 files unresolved
1105
1087
1106 $ hg merge thatbranch
1088 $ hg merge thatbranch
1107 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1089 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1108 (branch merge, don't forget to commit)
1090 (branch merge, don't forget to commit)
1109
1091
1110 $ hg commit -i -m'will abort'
1092 $ hg commit -i -m'will abort'
1111 abort: cannot partially commit a merge (use "hg commit" instead)
1093 abort: cannot partially commit a merge (use "hg commit" instead)
1112 [255]
1094 [255]
1113
1095
1114 $ hg up -C
1096 $ hg up -C
1115 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1097 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1116
1098
1117 Editing patch (and ignoring trailing text)
1099 Editing patch (and ignoring trailing text)
1118
1100
1119 $ cat > editor.sh << '__EOF__'
1101 $ cat > editor.sh << '__EOF__'
1120 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1102 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1121 > trailing\nditto' "$1" > tmp
1103 > trailing\nditto' "$1" > tmp
1122 > mv tmp "$1"
1104 > mv tmp "$1"
1123 > __EOF__
1105 > __EOF__
1124 $ cat > editedfile << '__EOF__'
1106 $ cat > editedfile << '__EOF__'
1125 > This is the first line
1107 > This is the first line
1126 > This is the second line
1108 > This is the second line
1127 > This is the third line
1109 > This is the third line
1128 > __EOF__
1110 > __EOF__
1129 $ hg add editedfile
1111 $ hg add editedfile
1130 $ hg commit -medit-patch-1
1112 $ hg commit -medit-patch-1
1131 $ cat > editedfile << '__EOF__'
1113 $ cat > editedfile << '__EOF__'
1132 > This line has changed
1114 > This line has changed
1133 > This change will be committed
1115 > This change will be committed
1134 > This is the third line
1116 > This is the third line
1135 > __EOF__
1117 > __EOF__
1136 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1118 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1137 > y
1119 > y
1138 > e
1120 > e
1139 > EOF
1121 > EOF
1140 diff --git a/editedfile b/editedfile
1122 diff --git a/editedfile b/editedfile
1141 1 hunks, 2 lines changed
1123 1 hunks, 2 lines changed
1142 examine changes to 'editedfile'? [Ynesfdaq?] y
1124 examine changes to 'editedfile'? [Ynesfdaq?] y
1143
1125
1144 @@ -1,3 +1,3 @@
1126 @@ -1,3 +1,3 @@
1145 -This is the first line
1127 -This is the first line
1146 -This is the second line
1128 -This is the second line
1147 +This line has changed
1129 +This line has changed
1148 +This change will be committed
1130 +This change will be committed
1149 This is the third line
1131 This is the third line
1150 record this change to 'editedfile'? [Ynesfdaq?] e
1132 record this change to 'editedfile'? [Ynesfdaq?] e
1151
1133
1152 $ cat editedfile
1134 $ cat editedfile
1153 This line has changed
1135 This line has changed
1154 This change will be committed
1136 This change will be committed
1155 This is the third line
1137 This is the third line
1156 $ hg cat -r tip editedfile
1138 $ hg cat -r tip editedfile
1157 This is the first line
1139 This is the first line
1158 This change will be committed
1140 This change will be committed
1159 This is the third line
1141 This is the third line
1160 $ hg revert editedfile
1142 $ hg revert editedfile
1161
1143
1162 Trying to edit patch for whole file
1144 Trying to edit patch for whole file
1163
1145
1164 $ echo "This is the fourth line" >> editedfile
1146 $ echo "This is the fourth line" >> editedfile
1165 $ hg commit -i <<EOF
1147 $ hg commit -i <<EOF
1166 > e
1148 > e
1167 > q
1149 > q
1168 > EOF
1150 > EOF
1169 diff --git a/editedfile b/editedfile
1151 diff --git a/editedfile b/editedfile
1170 1 hunks, 1 lines changed
1152 1 hunks, 1 lines changed
1171 examine changes to 'editedfile'? [Ynesfdaq?] e
1153 examine changes to 'editedfile'? [Ynesfdaq?] e
1172
1154
1173 cannot edit patch for whole file
1155 cannot edit patch for whole file
1174 examine changes to 'editedfile'? [Ynesfdaq?] q
1156 examine changes to 'editedfile'? [Ynesfdaq?] q
1175
1157
1176 abort: user quit
1158 abort: user quit
1177 [255]
1159 [255]
1178 $ hg revert editedfile
1160 $ hg revert editedfile
1179
1161
1180 Removing changes from patch
1162 Removing changes from patch
1181
1163
1182 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1164 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1183 $ mv tmp editedfile
1165 $ mv tmp editedfile
1184 $ echo "This line has been added" >> editedfile
1166 $ echo "This line has been added" >> editedfile
1185 $ cat > editor.sh << '__EOF__'
1167 $ cat > editor.sh << '__EOF__'
1186 > sed -e 's/^[-+]/ /' "$1" > tmp
1168 > sed -e 's/^[-+]/ /' "$1" > tmp
1187 > mv tmp "$1"
1169 > mv tmp "$1"
1188 > __EOF__
1170 > __EOF__
1189 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1171 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1190 > y
1172 > y
1191 > e
1173 > e
1192 > EOF
1174 > EOF
1193 diff --git a/editedfile b/editedfile
1175 diff --git a/editedfile b/editedfile
1194 1 hunks, 3 lines changed
1176 1 hunks, 3 lines changed
1195 examine changes to 'editedfile'? [Ynesfdaq?] y
1177 examine changes to 'editedfile'? [Ynesfdaq?] y
1196
1178
1197 @@ -1,3 +1,3 @@
1179 @@ -1,3 +1,3 @@
1198 -This is the first line
1180 -This is the first line
1199 -This change will be committed
1181 -This change will be committed
1200 -This is the third line
1182 -This is the third line
1201 +This change will not be committed
1183 +This change will not be committed
1202 +This is the second line
1184 +This is the second line
1203 +This line has been added
1185 +This line has been added
1204 record this change to 'editedfile'? [Ynesfdaq?] e
1186 record this change to 'editedfile'? [Ynesfdaq?] e
1205
1187
1206 no changes to record
1188 no changes to record
1207 $ cat editedfile
1189 $ cat editedfile
1208 This change will not be committed
1190 This change will not be committed
1209 This is the second line
1191 This is the second line
1210 This line has been added
1192 This line has been added
1211 $ hg cat -r tip editedfile
1193 $ hg cat -r tip editedfile
1212 This is the first line
1194 This is the first line
1213 This change will be committed
1195 This change will be committed
1214 This is the third line
1196 This is the third line
1215 $ hg revert editedfile
1197 $ hg revert editedfile
1216
1198
1217 Invalid patch
1199 Invalid patch
1218
1200
1219 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1201 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1220 $ mv tmp editedfile
1202 $ mv tmp editedfile
1221 $ echo "This line has been added" >> editedfile
1203 $ echo "This line has been added" >> editedfile
1222 $ cat > editor.sh << '__EOF__'
1204 $ cat > editor.sh << '__EOF__'
1223 > sed s/This/That/ "$1" > tmp
1205 > sed s/This/That/ "$1" > tmp
1224 > mv tmp "$1"
1206 > mv tmp "$1"
1225 > __EOF__
1207 > __EOF__
1226 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1208 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1227 > y
1209 > y
1228 > e
1210 > e
1229 > EOF
1211 > EOF
1230 diff --git a/editedfile b/editedfile
1212 diff --git a/editedfile b/editedfile
1231 1 hunks, 3 lines changed
1213 1 hunks, 3 lines changed
1232 examine changes to 'editedfile'? [Ynesfdaq?] y
1214 examine changes to 'editedfile'? [Ynesfdaq?] y
1233
1215
1234 @@ -1,3 +1,3 @@
1216 @@ -1,3 +1,3 @@
1235 -This is the first line
1217 -This is the first line
1236 -This change will be committed
1218 -This change will be committed
1237 -This is the third line
1219 -This is the third line
1238 +This change will not be committed
1220 +This change will not be committed
1239 +This is the second line
1221 +This is the second line
1240 +This line has been added
1222 +This line has been added
1241 record this change to 'editedfile'? [Ynesfdaq?] e
1223 record this change to 'editedfile'? [Ynesfdaq?] e
1242
1224
1243 patching file editedfile
1225 patching file editedfile
1244 Hunk #1 FAILED at 0
1226 Hunk #1 FAILED at 0
1245 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1227 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1246 abort: patch failed to apply
1228 abort: patch failed to apply
1247 [255]
1229 [255]
1248 $ cat editedfile
1230 $ cat editedfile
1249 This change will not be committed
1231 This change will not be committed
1250 This is the second line
1232 This is the second line
1251 This line has been added
1233 This line has been added
1252 $ hg cat -r tip editedfile
1234 $ hg cat -r tip editedfile
1253 This is the first line
1235 This is the first line
1254 This change will be committed
1236 This change will be committed
1255 This is the third line
1237 This is the third line
1256 $ cat editedfile.rej
1238 $ cat editedfile.rej
1257 --- editedfile
1239 --- editedfile
1258 +++ editedfile
1240 +++ editedfile
1259 @@ -1,3 +1,3 @@
1241 @@ -1,3 +1,3 @@
1260 -That is the first line
1242 -That is the first line
1261 -That change will be committed
1243 -That change will be committed
1262 -That is the third line
1244 -That is the third line
1263 +That change will not be committed
1245 +That change will not be committed
1264 +That is the second line
1246 +That is the second line
1265 +That line has been added
1247 +That line has been added
1266
1248
1267 Malformed patch - error handling
1249 Malformed patch - error handling
1268
1250
1269 $ cat > editor.sh << '__EOF__'
1251 $ cat > editor.sh << '__EOF__'
1270 > sed -e '/^@/p' "$1" > tmp
1252 > sed -e '/^@/p' "$1" > tmp
1271 > mv tmp "$1"
1253 > mv tmp "$1"
1272 > __EOF__
1254 > __EOF__
1273 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1255 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1274 > y
1256 > y
1275 > e
1257 > e
1276 > EOF
1258 > EOF
1277 diff --git a/editedfile b/editedfile
1259 diff --git a/editedfile b/editedfile
1278 1 hunks, 3 lines changed
1260 1 hunks, 3 lines changed
1279 examine changes to 'editedfile'? [Ynesfdaq?] y
1261 examine changes to 'editedfile'? [Ynesfdaq?] y
1280
1262
1281 @@ -1,3 +1,3 @@
1263 @@ -1,3 +1,3 @@
1282 -This is the first line
1264 -This is the first line
1283 -This change will be committed
1265 -This change will be committed
1284 -This is the third line
1266 -This is the third line
1285 +This change will not be committed
1267 +This change will not be committed
1286 +This is the second line
1268 +This is the second line
1287 +This line has been added
1269 +This line has been added
1288 record this change to 'editedfile'? [Ynesfdaq?] e
1270 record this change to 'editedfile'? [Ynesfdaq?] e
1289
1271
1290 abort: error parsing patch: unhandled transition: range -> range
1272 abort: error parsing patch: unhandled transition: range -> range
1291 [255]
1273 [255]
1292
1274
1293 random text in random positions is still an error
1275 random text in random positions is still an error
1294
1276
1295 $ cat > editor.sh << '__EOF__'
1277 $ cat > editor.sh << '__EOF__'
1296 > sed -e '/^@/i\
1278 > sed -e '/^@/i\
1297 > other' "$1" > tmp
1279 > other' "$1" > tmp
1298 > mv tmp "$1"
1280 > mv tmp "$1"
1299 > __EOF__
1281 > __EOF__
1300 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1282 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1301 > y
1283 > y
1302 > e
1284 > e
1303 > EOF
1285 > EOF
1304 diff --git a/editedfile b/editedfile
1286 diff --git a/editedfile b/editedfile
1305 1 hunks, 3 lines changed
1287 1 hunks, 3 lines changed
1306 examine changes to 'editedfile'? [Ynesfdaq?] y
1288 examine changes to 'editedfile'? [Ynesfdaq?] y
1307
1289
1308 @@ -1,3 +1,3 @@
1290 @@ -1,3 +1,3 @@
1309 -This is the first line
1291 -This is the first line
1310 -This change will be committed
1292 -This change will be committed
1311 -This is the third line
1293 -This is the third line
1312 +This change will not be committed
1294 +This change will not be committed
1313 +This is the second line
1295 +This is the second line
1314 +This line has been added
1296 +This line has been added
1315 record this change to 'editedfile'? [Ynesfdaq?] e
1297 record this change to 'editedfile'? [Ynesfdaq?] e
1316
1298
1317 abort: error parsing patch: unhandled transition: file -> other
1299 abort: error parsing patch: unhandled transition: file -> other
1318 [255]
1300 [255]
1319
1301
1320 $ hg up -C
1302 $ hg up -C
1321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1303 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1322
1304
1323 With win32text
1305 With win32text
1324
1306
1325 $ echo '[extensions]' >> .hg/hgrc
1307 $ echo '[extensions]' >> .hg/hgrc
1326 $ echo 'win32text = ' >> .hg/hgrc
1308 $ echo 'win32text = ' >> .hg/hgrc
1327 $ echo '[decode]' >> .hg/hgrc
1309 $ echo '[decode]' >> .hg/hgrc
1328 $ echo '** = cleverdecode:' >> .hg/hgrc
1310 $ echo '** = cleverdecode:' >> .hg/hgrc
1329 $ echo '[encode]' >> .hg/hgrc
1311 $ echo '[encode]' >> .hg/hgrc
1330 $ echo '** = cleverencode:' >> .hg/hgrc
1312 $ echo '** = cleverencode:' >> .hg/hgrc
1331 $ echo '[patch]' >> .hg/hgrc
1313 $ echo '[patch]' >> .hg/hgrc
1332 $ echo 'eol = crlf' >> .hg/hgrc
1314 $ echo 'eol = crlf' >> .hg/hgrc
1333
1315
1334 Ignore win32text deprecation warning for now:
1316 Ignore win32text deprecation warning for now:
1335
1317
1336 $ echo '[win32text]' >> .hg/hgrc
1318 $ echo '[win32text]' >> .hg/hgrc
1337 $ echo 'warn = no' >> .hg/hgrc
1319 $ echo 'warn = no' >> .hg/hgrc
1338
1320
1339 $ echo d >> subdir/f1
1321 $ echo d >> subdir/f1
1340 $ hg commit -i -d '24 0' -mw1 <<EOF
1322 $ hg commit -i -d '24 0' -mw1 <<EOF
1341 > y
1323 > y
1342 > y
1324 > y
1343 > EOF
1325 > EOF
1344 diff --git a/subdir/f1 b/subdir/f1
1326 diff --git a/subdir/f1 b/subdir/f1
1345 1 hunks, 1 lines changed
1327 1 hunks, 1 lines changed
1346 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1328 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1347
1329
1348 @@ -3,3 +3,4 @@
1330 @@ -3,3 +3,4 @@
1349 a
1331 a
1350 b
1332 b
1351 c
1333 c
1352 +d
1334 +d
1353 record this change to 'subdir/f1'? [Ynesfdaq?] y
1335 record this change to 'subdir/f1'? [Ynesfdaq?] y
1354
1336
1355
1337
1356 $ hg tip -p
1338 $ hg tip -p
1357 changeset: 28:* (glob)
1339 changeset: 28:* (glob)
1358 tag: tip
1340 tag: tip
1359 user: test
1341 user: test
1360 date: Thu Jan 01 00:00:24 1970 +0000
1342 date: Thu Jan 01 00:00:24 1970 +0000
1361 summary: w1
1343 summary: w1
1362
1344
1363 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1345 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1364 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1346 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1365 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1347 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1366 @@ -3,3 +3,4 @@
1348 @@ -3,3 +3,4 @@
1367 a
1349 a
1368 b
1350 b
1369 c
1351 c
1370 +d
1352 +d
1371
1353
1372
1354
1373
1355
1374 Test --user when ui.username not set
1356 Test --user when ui.username not set
1375 $ unset HGUSER
1357 $ unset HGUSER
1376 $ echo e >> subdir/f1
1358 $ echo e >> subdir/f1
1377 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1359 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1378 > y
1360 > y
1379 > y
1361 > y
1380 > EOF
1362 > EOF
1381 diff --git a/subdir/f1 b/subdir/f1
1363 diff --git a/subdir/f1 b/subdir/f1
1382 1 hunks, 1 lines changed
1364 1 hunks, 1 lines changed
1383 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1365 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1384
1366
1385 @@ -4,3 +4,4 @@
1367 @@ -4,3 +4,4 @@
1386 b
1368 b
1387 c
1369 c
1388 d
1370 d
1389 +e
1371 +e
1390 record this change to 'subdir/f1'? [Ynesfdaq?] y
1372 record this change to 'subdir/f1'? [Ynesfdaq?] y
1391
1373
1392 $ hg log --template '{author}\n' -l 1
1374 $ hg log --template '{author}\n' -l 1
1393 xyz
1375 xyz
1394 $ HGUSER="test"
1376 $ HGUSER="test"
1395 $ export HGUSER
1377 $ export HGUSER
1396
1378
1397
1379
1398 Editing patch of newly added file
1380 Moving files
1399
1381
1400 $ cat > editor.sh << '__EOF__'
1382 $ hg update -C .
1401 > cat "$1" | sed "s/first/very/g" > tt
1383 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1402 > mv tt "$1"
1384 $ hg mv plain plain3
1403 > __EOF__
1385 $ echo somechange >> plain3
1404 $ cat > newfile << '__EOF__'
1386 $ hg commit -i -d '23 0' -mmoving_files << EOF
1405 > This is the first line
1406 > This is the second line
1407 > This is the third line
1408 > __EOF__
1409 $ hg add newfile
1410 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1411 > y
1387 > y
1412 > e
1388 > y
1413 > EOF
1389 > EOF
1414 diff --git a/newfile b/newfile
1390 diff --git a/plain b/plain3
1415 new file mode 100644
1391 rename from plain
1416 examine changes to 'newfile'? [Ynesfdaq?] y
1392 rename to plain3
1417
1393 1 hunks, 1 lines changed
1418 @@ -0,0 +1,3 @@
1394 examine changes to 'plain' and 'plain3'? [Ynesfdaq?] y
1419 +This is the first line
1420 +This is the second line
1421 +This is the third line
1422 record this change to 'newfile'? [Ynesfdaq?] e
1423
1395
1424 $ hg cat -r tip newfile
1396 @@ -11,3 +11,4 @@
1425 This is the very line
1397 9
1426 This is the second line
1398 10
1427 This is the third line
1399 11
1400 +somechange
1401 record this change to 'plain3'? [Ynesfdaq?] y
1428
1402
1429 $ cat newfile
1403 $ hg tip
1430 This is the first line
1404 changeset: 30:542e1f362a22
1431 This is the second line
1405 tag: tip
1432 This is the third line
1406 user: test
1407 date: Thu Jan 01 00:00:23 1970 +0000
1408 summary: moving_files
1433
1409
1434 $ cd ..
1410 $ cd ..
@@ -1,1339 +1,1327 b''
1 $ cat <<EOF >> $HGRCPATH
1 $ cat <<EOF >> $HGRCPATH
2 > [extensions]
2 > [extensions]
3 > keyword =
3 > keyword =
4 > mq =
4 > mq =
5 > notify =
5 > notify =
6 > record =
6 > record =
7 > transplant =
7 > transplant =
8 > [ui]
8 > [ui]
9 > interactive = true
9 > interactive = true
10 > EOF
10 > EOF
11
11
12 hide outer repo
12 hide outer repo
13 $ hg init
13 $ hg init
14
14
15 Run kwdemo before [keyword] files are set up
15 Run kwdemo before [keyword] files are set up
16 as it would succeed without uisetup otherwise
16 as it would succeed without uisetup otherwise
17
17
18 $ hg --quiet kwdemo
18 $ hg --quiet kwdemo
19 [extensions]
19 [extensions]
20 keyword =
20 keyword =
21 [keyword]
21 [keyword]
22 demo.txt =
22 demo.txt =
23 [keywordset]
23 [keywordset]
24 svn = False
24 svn = False
25 [keywordmaps]
25 [keywordmaps]
26 Author = {author|user}
26 Author = {author|user}
27 Date = {date|utcdate}
27 Date = {date|utcdate}
28 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
28 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
29 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
29 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
30 RCSFile = {file|basename},v
30 RCSFile = {file|basename},v
31 RCSfile = {file|basename},v
31 RCSfile = {file|basename},v
32 Revision = {node|short}
32 Revision = {node|short}
33 Source = {root}/{file},v
33 Source = {root}/{file},v
34 $Author: test $
34 $Author: test $
35 $Date: ????/??/?? ??:??:?? $ (glob)
35 $Date: ????/??/?? ??:??:?? $ (glob)
36 $Header: */demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
36 $Header: */demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
37 $Id: demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
37 $Id: demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
38 $RCSFile: demo.txt,v $
38 $RCSFile: demo.txt,v $
39 $RCSfile: demo.txt,v $
39 $RCSfile: demo.txt,v $
40 $Revision: ???????????? $ (glob)
40 $Revision: ???????????? $ (glob)
41 $Source: */demo.txt,v $ (glob)
41 $Source: */demo.txt,v $ (glob)
42
42
43 $ hg --quiet kwdemo "Branch = {branches}"
43 $ hg --quiet kwdemo "Branch = {branches}"
44 [extensions]
44 [extensions]
45 keyword =
45 keyword =
46 [keyword]
46 [keyword]
47 demo.txt =
47 demo.txt =
48 [keywordset]
48 [keywordset]
49 svn = False
49 svn = False
50 [keywordmaps]
50 [keywordmaps]
51 Branch = {branches}
51 Branch = {branches}
52 $Branch: demobranch $
52 $Branch: demobranch $
53
53
54 $ cat <<EOF >> $HGRCPATH
54 $ cat <<EOF >> $HGRCPATH
55 > [keyword]
55 > [keyword]
56 > ** =
56 > ** =
57 > b = ignore
57 > b = ignore
58 > i = ignore
58 > i = ignore
59 > [hooks]
59 > [hooks]
60 > EOF
60 > EOF
61 $ cp $HGRCPATH $HGRCPATH.nohooks
61 $ cp $HGRCPATH $HGRCPATH.nohooks
62 > cat <<EOF >> $HGRCPATH
62 > cat <<EOF >> $HGRCPATH
63 > commit=
63 > commit=
64 > commit.test=cp a hooktest
64 > commit.test=cp a hooktest
65 > EOF
65 > EOF
66
66
67 $ hg init Test-bndl
67 $ hg init Test-bndl
68 $ cd Test-bndl
68 $ cd Test-bndl
69
69
70 kwshrink should exit silently in empty/invalid repo
70 kwshrink should exit silently in empty/invalid repo
71
71
72 $ hg kwshrink
72 $ hg kwshrink
73
73
74 Symlinks cannot be created on Windows.
74 Symlinks cannot be created on Windows.
75 A bundle to test this was made with:
75 A bundle to test this was made with:
76 hg init t
76 hg init t
77 cd t
77 cd t
78 echo a > a
78 echo a > a
79 ln -s a sym
79 ln -s a sym
80 hg add sym
80 hg add sym
81 hg ci -m addsym -u mercurial
81 hg ci -m addsym -u mercurial
82 hg bundle --base null ../test-keyword.hg
82 hg bundle --base null ../test-keyword.hg
83
83
84 $ hg pull -u "$TESTDIR"/bundles/test-keyword.hg
84 $ hg pull -u "$TESTDIR"/bundles/test-keyword.hg
85 pulling from *test-keyword.hg (glob)
85 pulling from *test-keyword.hg (glob)
86 requesting all changes
86 requesting all changes
87 adding changesets
87 adding changesets
88 adding manifests
88 adding manifests
89 adding file changes
89 adding file changes
90 added 1 changesets with 1 changes to 1 files
90 added 1 changesets with 1 changes to 1 files
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92
92
93 $ echo 'expand $Id$' > a
93 $ echo 'expand $Id$' > a
94 $ echo 'do not process $Id:' >> a
94 $ echo 'do not process $Id:' >> a
95 $ echo 'xxx $' >> a
95 $ echo 'xxx $' >> a
96 $ echo 'ignore $Id$' > b
96 $ echo 'ignore $Id$' > b
97
97
98 Output files as they were created
98 Output files as they were created
99
99
100 $ cat a b
100 $ cat a b
101 expand $Id$
101 expand $Id$
102 do not process $Id:
102 do not process $Id:
103 xxx $
103 xxx $
104 ignore $Id$
104 ignore $Id$
105
105
106 no kwfiles
106 no kwfiles
107
107
108 $ hg kwfiles
108 $ hg kwfiles
109
109
110 untracked candidates
110 untracked candidates
111
111
112 $ hg -v kwfiles --unknown
112 $ hg -v kwfiles --unknown
113 k a
113 k a
114
114
115 Add files and check status
115 Add files and check status
116
116
117 $ hg addremove
117 $ hg addremove
118 adding a
118 adding a
119 adding b
119 adding b
120 $ hg status
120 $ hg status
121 A a
121 A a
122 A b
122 A b
123
123
124
124
125 Default keyword expansion including commit hook
125 Default keyword expansion including commit hook
126 Interrupted commit should not change state or run commit hook
126 Interrupted commit should not change state or run commit hook
127
127
128 $ hg --debug commit
128 $ hg --debug commit
129 abort: empty commit message
129 abort: empty commit message
130 [255]
130 [255]
131 $ hg status
131 $ hg status
132 A a
132 A a
133 A b
133 A b
134
134
135 Commit with several checks
135 Commit with several checks
136
136
137 $ hg --debug commit -mabsym -u 'User Name <user@example.com>'
137 $ hg --debug commit -mabsym -u 'User Name <user@example.com>'
138 committing files:
138 committing files:
139 a
139 a
140 b
140 b
141 committing manifest
141 committing manifest
142 committing changelog
142 committing changelog
143 overwriting a expanding keywords
143 overwriting a expanding keywords
144 running hook commit.test: cp a hooktest
144 running hook commit.test: cp a hooktest
145 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
145 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
146 $ hg status
146 $ hg status
147 ? hooktest
147 ? hooktest
148 $ hg debugrebuildstate
148 $ hg debugrebuildstate
149 $ hg --quiet identify
149 $ hg --quiet identify
150 ef63ca68695b
150 ef63ca68695b
151
151
152 cat files in working directory with keywords expanded
152 cat files in working directory with keywords expanded
153
153
154 $ cat a b
154 $ cat a b
155 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
155 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
156 do not process $Id:
156 do not process $Id:
157 xxx $
157 xxx $
158 ignore $Id$
158 ignore $Id$
159
159
160 hg cat files and symlink, no expansion
160 hg cat files and symlink, no expansion
161
161
162 $ hg cat sym a b && echo
162 $ hg cat sym a b && echo
163 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
163 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
164 do not process $Id:
164 do not process $Id:
165 xxx $
165 xxx $
166 ignore $Id$
166 ignore $Id$
167 a
167 a
168
168
169 $ diff a hooktest
169 $ diff a hooktest
170
170
171 $ cp $HGRCPATH.nohooks $HGRCPATH
171 $ cp $HGRCPATH.nohooks $HGRCPATH
172 $ rm hooktest
172 $ rm hooktest
173
173
174 hg status of kw-ignored binary file starting with '\1\n'
174 hg status of kw-ignored binary file starting with '\1\n'
175
175
176 >>> open("i", "wb").write("\1\nfoo")
176 >>> open("i", "wb").write("\1\nfoo")
177 $ hg -q commit -Am metasep i
177 $ hg -q commit -Am metasep i
178 $ hg status
178 $ hg status
179 >>> open("i", "wb").write("\1\nbar")
179 >>> open("i", "wb").write("\1\nbar")
180 $ hg status
180 $ hg status
181 M i
181 M i
182 $ hg -q commit -m "modify metasep" i
182 $ hg -q commit -m "modify metasep" i
183 $ hg status --rev 2:3
183 $ hg status --rev 2:3
184 M i
184 M i
185 $ touch empty
185 $ touch empty
186 $ hg -q commit -A -m "another file"
186 $ hg -q commit -A -m "another file"
187 $ hg status -A --rev 3:4 i
187 $ hg status -A --rev 3:4 i
188 C i
188 C i
189
189
190 $ hg -q strip --no-backup 2
190 $ hg -q strip --no-backup 2
191
191
192 Test hook execution
192 Test hook execution
193
193
194 bundle
194 bundle
195
195
196 $ hg bundle --base null ../kw.hg
196 $ hg bundle --base null ../kw.hg
197 2 changesets found
197 2 changesets found
198 $ cd ..
198 $ cd ..
199 $ hg init Test
199 $ hg init Test
200 $ cd Test
200 $ cd Test
201
201
202 Notify on pull to check whether keywords stay as is in email
202 Notify on pull to check whether keywords stay as is in email
203 ie. if patch.diff wrapper acts as it should
203 ie. if patch.diff wrapper acts as it should
204
204
205 $ cat <<EOF >> $HGRCPATH
205 $ cat <<EOF >> $HGRCPATH
206 > [hooks]
206 > [hooks]
207 > incoming.notify = python:hgext.notify.hook
207 > incoming.notify = python:hgext.notify.hook
208 > [notify]
208 > [notify]
209 > sources = pull
209 > sources = pull
210 > diffstat = False
210 > diffstat = False
211 > maxsubject = 15
211 > maxsubject = 15
212 > [reposubs]
212 > [reposubs]
213 > * = Test
213 > * = Test
214 > EOF
214 > EOF
215
215
216 Pull from bundle and trigger notify
216 Pull from bundle and trigger notify
217
217
218 $ hg pull -u ../kw.hg
218 $ hg pull -u ../kw.hg
219 pulling from ../kw.hg
219 pulling from ../kw.hg
220 requesting all changes
220 requesting all changes
221 adding changesets
221 adding changesets
222 adding manifests
222 adding manifests
223 adding file changes
223 adding file changes
224 added 2 changesets with 3 changes to 3 files
224 added 2 changesets with 3 changes to 3 files
225 Content-Type: text/plain; charset="us-ascii"
225 Content-Type: text/plain; charset="us-ascii"
226 MIME-Version: 1.0
226 MIME-Version: 1.0
227 Content-Transfer-Encoding: 7bit
227 Content-Transfer-Encoding: 7bit
228 Date: * (glob)
228 Date: * (glob)
229 Subject: changeset in...
229 Subject: changeset in...
230 From: mercurial
230 From: mercurial
231 X-Hg-Notification: changeset a2392c293916
231 X-Hg-Notification: changeset a2392c293916
232 Message-Id: <hg.a2392c293916*> (glob)
232 Message-Id: <hg.a2392c293916*> (glob)
233 To: Test
233 To: Test
234
234
235 changeset a2392c293916 in $TESTTMP/Test (glob)
235 changeset a2392c293916 in $TESTTMP/Test (glob)
236 details: $TESTTMP/Test?cmd=changeset;node=a2392c293916
236 details: $TESTTMP/Test?cmd=changeset;node=a2392c293916
237 description:
237 description:
238 addsym
238 addsym
239
239
240 diffs (6 lines):
240 diffs (6 lines):
241
241
242 diff -r 000000000000 -r a2392c293916 sym
242 diff -r 000000000000 -r a2392c293916 sym
243 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
243 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
244 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
244 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
245 @@ -0,0 +1,1 @@
245 @@ -0,0 +1,1 @@
246 +a
246 +a
247 \ No newline at end of file
247 \ No newline at end of file
248 Content-Type: text/plain; charset="us-ascii"
248 Content-Type: text/plain; charset="us-ascii"
249 MIME-Version: 1.0
249 MIME-Version: 1.0
250 Content-Transfer-Encoding: 7bit
250 Content-Transfer-Encoding: 7bit
251 Date:* (glob)
251 Date:* (glob)
252 Subject: changeset in...
252 Subject: changeset in...
253 From: User Name <user@example.com>
253 From: User Name <user@example.com>
254 X-Hg-Notification: changeset ef63ca68695b
254 X-Hg-Notification: changeset ef63ca68695b
255 Message-Id: <hg.ef63ca68695b*> (glob)
255 Message-Id: <hg.ef63ca68695b*> (glob)
256 To: Test
256 To: Test
257
257
258 changeset ef63ca68695b in $TESTTMP/Test (glob)
258 changeset ef63ca68695b in $TESTTMP/Test (glob)
259 details: $TESTTMP/Test?cmd=changeset;node=ef63ca68695b
259 details: $TESTTMP/Test?cmd=changeset;node=ef63ca68695b
260 description:
260 description:
261 absym
261 absym
262
262
263 diffs (12 lines):
263 diffs (12 lines):
264
264
265 diff -r a2392c293916 -r ef63ca68695b a
265 diff -r a2392c293916 -r ef63ca68695b a
266 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
266 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
267 +++ b/a Thu Jan 01 00:00:00 1970 +0000
267 +++ b/a Thu Jan 01 00:00:00 1970 +0000
268 @@ -0,0 +1,3 @@
268 @@ -0,0 +1,3 @@
269 +expand $Id$
269 +expand $Id$
270 +do not process $Id:
270 +do not process $Id:
271 +xxx $
271 +xxx $
272 diff -r a2392c293916 -r ef63ca68695b b
272 diff -r a2392c293916 -r ef63ca68695b b
273 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
273 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
274 +++ b/b Thu Jan 01 00:00:00 1970 +0000
274 +++ b/b Thu Jan 01 00:00:00 1970 +0000
275 @@ -0,0 +1,1 @@
275 @@ -0,0 +1,1 @@
276 +ignore $Id$
276 +ignore $Id$
277 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
277 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
278
278
279 $ cp $HGRCPATH.nohooks $HGRCPATH
279 $ cp $HGRCPATH.nohooks $HGRCPATH
280
280
281 Touch files and check with status
281 Touch files and check with status
282
282
283 $ touch a b
283 $ touch a b
284 $ hg status
284 $ hg status
285
285
286 Update and expand
286 Update and expand
287
287
288 $ rm sym a b
288 $ rm sym a b
289 $ hg update -C
289 $ hg update -C
290 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
290 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 $ cat a b
291 $ cat a b
292 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
292 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
293 do not process $Id:
293 do not process $Id:
294 xxx $
294 xxx $
295 ignore $Id$
295 ignore $Id$
296
296
297 Check whether expansion is filewise and file mode is preserved
297 Check whether expansion is filewise and file mode is preserved
298
298
299 $ echo '$Id$' > c
299 $ echo '$Id$' > c
300 $ echo 'tests for different changenodes' >> c
300 $ echo 'tests for different changenodes' >> c
301 #if unix-permissions
301 #if unix-permissions
302 $ chmod 600 c
302 $ chmod 600 c
303 $ ls -l c | cut -b 1-10
303 $ ls -l c | cut -b 1-10
304 -rw-------
304 -rw-------
305 #endif
305 #endif
306
306
307 commit file c
307 commit file c
308
308
309 $ hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
309 $ hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
310 adding c
310 adding c
311 #if unix-permissions
311 #if unix-permissions
312 $ ls -l c | cut -b 1-10
312 $ ls -l c | cut -b 1-10
313 -rw-------
313 -rw-------
314 #endif
314 #endif
315
315
316 force expansion
316 force expansion
317
317
318 $ hg -v kwexpand
318 $ hg -v kwexpand
319 overwriting a expanding keywords
319 overwriting a expanding keywords
320 overwriting c expanding keywords
320 overwriting c expanding keywords
321
321
322 compare changenodes in a and c
322 compare changenodes in a and c
323
323
324 $ cat a c
324 $ cat a c
325 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
325 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
326 do not process $Id:
326 do not process $Id:
327 xxx $
327 xxx $
328 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
328 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
329 tests for different changenodes
329 tests for different changenodes
330
330
331 record
331 record
332
332
333 $ echo '$Id$' > r
333 $ echo '$Id$' > r
334 $ hg add r
334 $ hg add r
335
335
336 record chunk
336 record chunk
337
337
338 >>> lines = open('a', 'rb').readlines()
338 >>> lines = open('a', 'rb').readlines()
339 >>> lines.insert(1, 'foo\n')
339 >>> lines.insert(1, 'foo\n')
340 >>> lines.append('bar\n')
340 >>> lines.append('bar\n')
341 >>> open('a', 'wb').writelines(lines)
341 >>> open('a', 'wb').writelines(lines)
342 $ hg record -d '10 1' -m rectest a<<EOF
342 $ hg record -d '10 1' -m rectest a<<EOF
343 > y
343 > y
344 > y
344 > y
345 > n
345 > n
346 > EOF
346 > EOF
347 diff --git a/a b/a
347 diff --git a/a b/a
348 2 hunks, 2 lines changed
348 2 hunks, 2 lines changed
349 examine changes to 'a'? [Ynesfdaq?] y
349 examine changes to 'a'? [Ynesfdaq?] y
350
350
351 @@ -1,3 +1,4 @@
351 @@ -1,3 +1,4 @@
352 expand $Id$
352 expand $Id$
353 +foo
353 +foo
354 do not process $Id:
354 do not process $Id:
355 xxx $
355 xxx $
356 record change 1/2 to 'a'? [Ynesfdaq?] y
356 record change 1/2 to 'a'? [Ynesfdaq?] y
357
357
358 @@ -2,2 +3,3 @@
358 @@ -2,2 +3,3 @@
359 do not process $Id:
359 do not process $Id:
360 xxx $
360 xxx $
361 +bar
361 +bar
362 record change 2/2 to 'a'? [Ynesfdaq?] n
362 record change 2/2 to 'a'? [Ynesfdaq?] n
363
363
364
364
365 $ hg identify
365 $ hg identify
366 5f5eb23505c3+ tip
366 5f5eb23505c3+ tip
367 $ hg status
367 $ hg status
368 M a
368 M a
369 A r
369 A r
370
370
371 Cat modified file a
371 Cat modified file a
372
372
373 $ cat a
373 $ cat a
374 expand $Id: a,v 5f5eb23505c3 1970/01/01 00:00:10 test $
374 expand $Id: a,v 5f5eb23505c3 1970/01/01 00:00:10 test $
375 foo
375 foo
376 do not process $Id:
376 do not process $Id:
377 xxx $
377 xxx $
378 bar
378 bar
379
379
380 Diff remaining chunk
380 Diff remaining chunk
381
381
382 $ hg diff a
382 $ hg diff a
383 diff -r 5f5eb23505c3 a
383 diff -r 5f5eb23505c3 a
384 --- a/a Thu Jan 01 00:00:09 1970 -0000
384 --- a/a Thu Jan 01 00:00:09 1970 -0000
385 +++ b/a * (glob)
385 +++ b/a * (glob)
386 @@ -2,3 +2,4 @@
386 @@ -2,3 +2,4 @@
387 foo
387 foo
388 do not process $Id:
388 do not process $Id:
389 xxx $
389 xxx $
390 +bar
390 +bar
391
391
392 $ hg rollback
392 $ hg rollback
393 repository tip rolled back to revision 2 (undo commit)
393 repository tip rolled back to revision 2 (undo commit)
394 working directory now based on revision 2
394 working directory now based on revision 2
395
395
396 Record all chunks in file a
396 Record all chunks in file a
397
397
398 $ echo foo > msg
398 $ echo foo > msg
399
399
400 - do not use "hg record -m" here!
400 - do not use "hg record -m" here!
401
401
402 $ hg record -l msg -d '11 1' a<<EOF
402 $ hg record -l msg -d '11 1' a<<EOF
403 > y
403 > y
404 > y
404 > y
405 > y
405 > y
406 > EOF
406 > EOF
407 diff --git a/a b/a
407 diff --git a/a b/a
408 2 hunks, 2 lines changed
408 2 hunks, 2 lines changed
409 examine changes to 'a'? [Ynesfdaq?] y
409 examine changes to 'a'? [Ynesfdaq?] y
410
410
411 @@ -1,3 +1,4 @@
411 @@ -1,3 +1,4 @@
412 expand $Id$
412 expand $Id$
413 +foo
413 +foo
414 do not process $Id:
414 do not process $Id:
415 xxx $
415 xxx $
416 record change 1/2 to 'a'? [Ynesfdaq?] y
416 record change 1/2 to 'a'? [Ynesfdaq?] y
417
417
418 @@ -2,2 +3,3 @@
418 @@ -2,2 +3,3 @@
419 do not process $Id:
419 do not process $Id:
420 xxx $
420 xxx $
421 +bar
421 +bar
422 record change 2/2 to 'a'? [Ynesfdaq?] y
422 record change 2/2 to 'a'? [Ynesfdaq?] y
423
423
424
424
425 File a should be clean
425 File a should be clean
426
426
427 $ hg status -A a
427 $ hg status -A a
428 C a
428 C a
429
429
430 rollback and revert expansion
430 rollback and revert expansion
431
431
432 $ cat a
432 $ cat a
433 expand $Id: a,v 78e0a02d76aa 1970/01/01 00:00:11 test $
433 expand $Id: a,v 78e0a02d76aa 1970/01/01 00:00:11 test $
434 foo
434 foo
435 do not process $Id:
435 do not process $Id:
436 xxx $
436 xxx $
437 bar
437 bar
438 $ hg --verbose rollback
438 $ hg --verbose rollback
439 repository tip rolled back to revision 2 (undo commit)
439 repository tip rolled back to revision 2 (undo commit)
440 working directory now based on revision 2
440 working directory now based on revision 2
441 overwriting a expanding keywords
441 overwriting a expanding keywords
442 $ hg status a
442 $ hg status a
443 M a
443 M a
444 $ cat a
444 $ cat a
445 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
445 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
446 foo
446 foo
447 do not process $Id:
447 do not process $Id:
448 xxx $
448 xxx $
449 bar
449 bar
450 $ echo '$Id$' > y
450 $ echo '$Id$' > y
451 $ echo '$Id$' > z
451 $ echo '$Id$' > z
452 $ hg add y
452 $ hg add y
453 $ hg commit -Am "rollback only" z
453 $ hg commit -Am "rollback only" z
454 $ cat z
454 $ cat z
455 $Id: z,v 45a5d3adce53 1970/01/01 00:00:00 test $
455 $Id: z,v 45a5d3adce53 1970/01/01 00:00:00 test $
456 $ hg --verbose rollback
456 $ hg --verbose rollback
457 repository tip rolled back to revision 2 (undo commit)
457 repository tip rolled back to revision 2 (undo commit)
458 working directory now based on revision 2
458 working directory now based on revision 2
459 overwriting z shrinking keywords
459 overwriting z shrinking keywords
460
460
461 Only z should be overwritten
461 Only z should be overwritten
462
462
463 $ hg status a y z
463 $ hg status a y z
464 M a
464 M a
465 A y
465 A y
466 A z
466 A z
467 $ cat z
467 $ cat z
468 $Id$
468 $Id$
469 $ hg forget y z
469 $ hg forget y z
470 $ rm y z
470 $ rm y z
471
471
472 record added file alone
472 record added file alone
473
473
474 $ hg -v record -l msg -d '12 2' r<<EOF
474 $ hg -v record -l msg -d '12 2' r<<EOF
475 > y
475 > y
476 > y
476 > y
477 > EOF
477 > EOF
478 diff --git a/r b/r
478 diff --git a/r b/r
479 new file mode 100644
479 new file mode 100644
480 examine changes to 'r'? [Ynesfdaq?] y
480 examine changes to 'r'? [Ynesfdaq?] y
481
481
482 @@ -0,0 +1,1 @@
483 +$Id$
484 record this change to 'r'? [Ynesfdaq?] y
485
486 resolving manifests
487 patching file r
488 committing files:
482 committing files:
489 r
483 r
490 committing manifest
484 committing manifest
491 committing changelog
485 committing changelog
492 committed changeset 3:82a2f715724d
486 committed changeset 3:82a2f715724d
493 overwriting r expanding keywords
487 overwriting r expanding keywords
494 $ hg status r
488 $ hg status r
495 $ hg --verbose rollback
489 $ hg --verbose rollback
496 repository tip rolled back to revision 2 (undo commit)
490 repository tip rolled back to revision 2 (undo commit)
497 working directory now based on revision 2
491 working directory now based on revision 2
498 overwriting r shrinking keywords
492 overwriting r shrinking keywords
499 $ hg forget r
493 $ hg forget r
500 $ rm msg r
494 $ rm msg r
501 $ hg update -C
495 $ hg update -C
502 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
496 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
503
497
504 record added keyword ignored file
498 record added keyword ignored file
505
499
506 $ echo '$Id$' > i
500 $ echo '$Id$' > i
507 $ hg add i
501 $ hg add i
508 $ hg --verbose record -d '13 1' -m recignored<<EOF
502 $ hg --verbose record -d '13 1' -m recignored<<EOF
509 > y
503 > y
510 > y
504 > y
511 > EOF
505 > EOF
512 diff --git a/i b/i
506 diff --git a/i b/i
513 new file mode 100644
507 new file mode 100644
514 examine changes to 'i'? [Ynesfdaq?] y
508 examine changes to 'i'? [Ynesfdaq?] y
515
509
516 @@ -0,0 +1,1 @@
517 +$Id$
518 record this change to 'i'? [Ynesfdaq?] y
519
520 resolving manifests
521 patching file i
522 committing files:
510 committing files:
523 i
511 i
524 committing manifest
512 committing manifest
525 committing changelog
513 committing changelog
526 committed changeset 3:9f40ceb5a072
514 committed changeset 3:9f40ceb5a072
527 $ cat i
515 $ cat i
528 $Id$
516 $Id$
529 $ hg -q rollback
517 $ hg -q rollback
530 $ hg forget i
518 $ hg forget i
531 $ rm i
519 $ rm i
532
520
533 amend
521 amend
534
522
535 $ echo amend >> a
523 $ echo amend >> a
536 $ echo amend >> b
524 $ echo amend >> b
537 $ hg -q commit -d '14 1' -m 'prepare amend'
525 $ hg -q commit -d '14 1' -m 'prepare amend'
538
526
539 $ hg --debug commit --amend -d '15 1' -m 'amend without changes' | grep keywords
527 $ hg --debug commit --amend -d '15 1' -m 'amend without changes' | grep keywords
540 overwriting a expanding keywords
528 overwriting a expanding keywords
541 $ hg -q id
529 $ hg -q id
542 67d8c481a6be
530 67d8c481a6be
543 $ head -1 a
531 $ head -1 a
544 expand $Id: a,v 67d8c481a6be 1970/01/01 00:00:15 test $
532 expand $Id: a,v 67d8c481a6be 1970/01/01 00:00:15 test $
545
533
546 $ hg -q strip --no-backup tip
534 $ hg -q strip --no-backup tip
547
535
548 Test patch queue repo
536 Test patch queue repo
549
537
550 $ hg init --mq
538 $ hg init --mq
551 $ hg qimport -r tip -n mqtest.diff
539 $ hg qimport -r tip -n mqtest.diff
552 $ hg commit --mq -m mqtest
540 $ hg commit --mq -m mqtest
553
541
554 Keywords should not be expanded in patch
542 Keywords should not be expanded in patch
555
543
556 $ cat .hg/patches/mqtest.diff
544 $ cat .hg/patches/mqtest.diff
557 # HG changeset patch
545 # HG changeset patch
558 # User User Name <user@example.com>
546 # User User Name <user@example.com>
559 # Date 1 0
547 # Date 1 0
560 # Thu Jan 01 00:00:01 1970 +0000
548 # Thu Jan 01 00:00:01 1970 +0000
561 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
549 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
562 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
550 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
563 cndiff
551 cndiff
564
552
565 diff -r ef63ca68695b -r 40a904bbbe4c c
553 diff -r ef63ca68695b -r 40a904bbbe4c c
566 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
554 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
567 +++ b/c Thu Jan 01 00:00:01 1970 +0000
555 +++ b/c Thu Jan 01 00:00:01 1970 +0000
568 @@ -0,0 +1,2 @@
556 @@ -0,0 +1,2 @@
569 +$Id$
557 +$Id$
570 +tests for different changenodes
558 +tests for different changenodes
571
559
572 $ hg qpop
560 $ hg qpop
573 popping mqtest.diff
561 popping mqtest.diff
574 patch queue now empty
562 patch queue now empty
575
563
576 qgoto, implying qpush, should expand
564 qgoto, implying qpush, should expand
577
565
578 $ hg qgoto mqtest.diff
566 $ hg qgoto mqtest.diff
579 applying mqtest.diff
567 applying mqtest.diff
580 now at: mqtest.diff
568 now at: mqtest.diff
581 $ cat c
569 $ cat c
582 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
570 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
583 tests for different changenodes
571 tests for different changenodes
584 $ hg cat c
572 $ hg cat c
585 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
573 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
586 tests for different changenodes
574 tests for different changenodes
587
575
588 Keywords should not be expanded in filelog
576 Keywords should not be expanded in filelog
589
577
590 $ hg --config 'extensions.keyword=!' cat c
578 $ hg --config 'extensions.keyword=!' cat c
591 $Id$
579 $Id$
592 tests for different changenodes
580 tests for different changenodes
593
581
594 qpop and move on
582 qpop and move on
595
583
596 $ hg qpop
584 $ hg qpop
597 popping mqtest.diff
585 popping mqtest.diff
598 patch queue now empty
586 patch queue now empty
599
587
600 Copy and show added kwfiles
588 Copy and show added kwfiles
601
589
602 $ hg cp a c
590 $ hg cp a c
603 $ hg kwfiles
591 $ hg kwfiles
604 a
592 a
605 c
593 c
606
594
607 Commit and show expansion in original and copy
595 Commit and show expansion in original and copy
608
596
609 $ hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
597 $ hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
610 committing files:
598 committing files:
611 c
599 c
612 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
600 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
613 committing manifest
601 committing manifest
614 committing changelog
602 committing changelog
615 overwriting c expanding keywords
603 overwriting c expanding keywords
616 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
604 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
617 $ cat a c
605 $ cat a c
618 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
606 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
619 do not process $Id:
607 do not process $Id:
620 xxx $
608 xxx $
621 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
609 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
622 do not process $Id:
610 do not process $Id:
623 xxx $
611 xxx $
624
612
625 Touch copied c and check its status
613 Touch copied c and check its status
626
614
627 $ touch c
615 $ touch c
628 $ hg status
616 $ hg status
629
617
630 Copy kwfile to keyword ignored file unexpanding keywords
618 Copy kwfile to keyword ignored file unexpanding keywords
631
619
632 $ hg --verbose copy a i
620 $ hg --verbose copy a i
633 copying a to i
621 copying a to i
634 overwriting i shrinking keywords
622 overwriting i shrinking keywords
635 $ head -n 1 i
623 $ head -n 1 i
636 expand $Id$
624 expand $Id$
637 $ hg forget i
625 $ hg forget i
638 $ rm i
626 $ rm i
639
627
640 Copy ignored file to ignored file: no overwriting
628 Copy ignored file to ignored file: no overwriting
641
629
642 $ hg --verbose copy b i
630 $ hg --verbose copy b i
643 copying b to i
631 copying b to i
644 $ hg forget i
632 $ hg forget i
645 $ rm i
633 $ rm i
646
634
647 cp symlink file; hg cp -A symlink file (part1)
635 cp symlink file; hg cp -A symlink file (part1)
648 - copied symlink points to kwfile: overwrite
636 - copied symlink points to kwfile: overwrite
649
637
650 #if symlink
638 #if symlink
651 $ cp sym i
639 $ cp sym i
652 $ ls -l i
640 $ ls -l i
653 -rw-r--r--* (glob)
641 -rw-r--r--* (glob)
654 $ head -1 i
642 $ head -1 i
655 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
643 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
656 $ hg copy --after --verbose sym i
644 $ hg copy --after --verbose sym i
657 copying sym to i
645 copying sym to i
658 overwriting i shrinking keywords
646 overwriting i shrinking keywords
659 $ head -1 i
647 $ head -1 i
660 expand $Id$
648 expand $Id$
661 $ hg forget i
649 $ hg forget i
662 $ rm i
650 $ rm i
663 #endif
651 #endif
664
652
665 Test different options of hg kwfiles
653 Test different options of hg kwfiles
666
654
667 $ hg kwfiles
655 $ hg kwfiles
668 a
656 a
669 c
657 c
670 $ hg -v kwfiles --ignore
658 $ hg -v kwfiles --ignore
671 I b
659 I b
672 I sym
660 I sym
673 $ hg kwfiles --all
661 $ hg kwfiles --all
674 K a
662 K a
675 K c
663 K c
676 I b
664 I b
677 I sym
665 I sym
678
666
679 Diff specific revision
667 Diff specific revision
680
668
681 $ hg diff --rev 1
669 $ hg diff --rev 1
682 diff -r ef63ca68695b c
670 diff -r ef63ca68695b c
683 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
671 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
684 +++ b/c * (glob)
672 +++ b/c * (glob)
685 @@ -0,0 +1,3 @@
673 @@ -0,0 +1,3 @@
686 +expand $Id$
674 +expand $Id$
687 +do not process $Id:
675 +do not process $Id:
688 +xxx $
676 +xxx $
689
677
690 Status after rollback:
678 Status after rollback:
691
679
692 $ hg rollback
680 $ hg rollback
693 repository tip rolled back to revision 1 (undo commit)
681 repository tip rolled back to revision 1 (undo commit)
694 working directory now based on revision 1
682 working directory now based on revision 1
695 $ hg status
683 $ hg status
696 A c
684 A c
697 $ hg update --clean
685 $ hg update --clean
698 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
686 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
699
687
700 #if symlink
688 #if symlink
701
689
702 cp symlink file; hg cp -A symlink file (part2)
690 cp symlink file; hg cp -A symlink file (part2)
703 - copied symlink points to kw ignored file: do not overwrite
691 - copied symlink points to kw ignored file: do not overwrite
704
692
705 $ cat a > i
693 $ cat a > i
706 $ ln -s i symignored
694 $ ln -s i symignored
707 $ hg commit -Am 'fake expansion in ignored and symlink' i symignored
695 $ hg commit -Am 'fake expansion in ignored and symlink' i symignored
708 $ cp symignored x
696 $ cp symignored x
709 $ hg copy --after --verbose symignored x
697 $ hg copy --after --verbose symignored x
710 copying symignored to x
698 copying symignored to x
711 $ head -n 1 x
699 $ head -n 1 x
712 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
700 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
713 $ hg forget x
701 $ hg forget x
714 $ rm x
702 $ rm x
715
703
716 $ hg rollback
704 $ hg rollback
717 repository tip rolled back to revision 1 (undo commit)
705 repository tip rolled back to revision 1 (undo commit)
718 working directory now based on revision 1
706 working directory now based on revision 1
719 $ hg update --clean
707 $ hg update --clean
720 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
708 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
721 $ rm i symignored
709 $ rm i symignored
722
710
723 #endif
711 #endif
724
712
725 Custom keywordmaps as argument to kwdemo
713 Custom keywordmaps as argument to kwdemo
726
714
727 $ hg --quiet kwdemo "Xinfo = {author}: {desc}"
715 $ hg --quiet kwdemo "Xinfo = {author}: {desc}"
728 [extensions]
716 [extensions]
729 keyword =
717 keyword =
730 [keyword]
718 [keyword]
731 ** =
719 ** =
732 b = ignore
720 b = ignore
733 demo.txt =
721 demo.txt =
734 i = ignore
722 i = ignore
735 [keywordset]
723 [keywordset]
736 svn = False
724 svn = False
737 [keywordmaps]
725 [keywordmaps]
738 Xinfo = {author}: {desc}
726 Xinfo = {author}: {desc}
739 $Xinfo: test: hg keyword configuration and expansion example $
727 $Xinfo: test: hg keyword configuration and expansion example $
740
728
741 Configure custom keywordmaps
729 Configure custom keywordmaps
742
730
743 $ cat <<EOF >>$HGRCPATH
731 $ cat <<EOF >>$HGRCPATH
744 > [keywordmaps]
732 > [keywordmaps]
745 > Id = {file} {node|short} {date|rfc822date} {author|user}
733 > Id = {file} {node|short} {date|rfc822date} {author|user}
746 > Xinfo = {author}: {desc}
734 > Xinfo = {author}: {desc}
747 > EOF
735 > EOF
748
736
749 Cat and hg cat files before custom expansion
737 Cat and hg cat files before custom expansion
750
738
751 $ cat a b
739 $ cat a b
752 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
740 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
753 do not process $Id:
741 do not process $Id:
754 xxx $
742 xxx $
755 ignore $Id$
743 ignore $Id$
756 $ hg cat sym a b && echo
744 $ hg cat sym a b && echo
757 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
745 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
758 do not process $Id:
746 do not process $Id:
759 xxx $
747 xxx $
760 ignore $Id$
748 ignore $Id$
761 a
749 a
762
750
763 Write custom keyword and prepare multi-line commit message
751 Write custom keyword and prepare multi-line commit message
764
752
765 $ echo '$Xinfo$' >> a
753 $ echo '$Xinfo$' >> a
766 $ cat <<EOF >> log
754 $ cat <<EOF >> log
767 > firstline
755 > firstline
768 > secondline
756 > secondline
769 > EOF
757 > EOF
770
758
771 Interrupted commit should not change state
759 Interrupted commit should not change state
772
760
773 $ hg commit
761 $ hg commit
774 abort: empty commit message
762 abort: empty commit message
775 [255]
763 [255]
776 $ hg status
764 $ hg status
777 M a
765 M a
778 ? c
766 ? c
779 ? log
767 ? log
780
768
781 Commit with multi-line message and custom expansion
769 Commit with multi-line message and custom expansion
782
770
783 $ hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
771 $ hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
784 committing files:
772 committing files:
785 a
773 a
786 committing manifest
774 committing manifest
787 committing changelog
775 committing changelog
788 overwriting a expanding keywords
776 overwriting a expanding keywords
789 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
777 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
790 $ rm log
778 $ rm log
791
779
792 Stat, verify and show custom expansion (firstline)
780 Stat, verify and show custom expansion (firstline)
793
781
794 $ hg status
782 $ hg status
795 ? c
783 ? c
796 $ hg verify
784 $ hg verify
797 checking changesets
785 checking changesets
798 checking manifests
786 checking manifests
799 crosschecking files in changesets and manifests
787 crosschecking files in changesets and manifests
800 checking files
788 checking files
801 3 files, 3 changesets, 4 total revisions
789 3 files, 3 changesets, 4 total revisions
802 $ cat a b
790 $ cat a b
803 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
791 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
804 do not process $Id:
792 do not process $Id:
805 xxx $
793 xxx $
806 $Xinfo: User Name <user@example.com>: firstline $
794 $Xinfo: User Name <user@example.com>: firstline $
807 ignore $Id$
795 ignore $Id$
808 $ hg cat sym a b && echo
796 $ hg cat sym a b && echo
809 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
797 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
810 do not process $Id:
798 do not process $Id:
811 xxx $
799 xxx $
812 $Xinfo: User Name <user@example.com>: firstline $
800 $Xinfo: User Name <user@example.com>: firstline $
813 ignore $Id$
801 ignore $Id$
814 a
802 a
815
803
816 annotate
804 annotate
817
805
818 $ hg annotate a
806 $ hg annotate a
819 1: expand $Id$
807 1: expand $Id$
820 1: do not process $Id:
808 1: do not process $Id:
821 1: xxx $
809 1: xxx $
822 2: $Xinfo$
810 2: $Xinfo$
823
811
824 remove with status checks
812 remove with status checks
825
813
826 $ hg debugrebuildstate
814 $ hg debugrebuildstate
827 $ hg remove a
815 $ hg remove a
828 $ hg --debug commit -m rma
816 $ hg --debug commit -m rma
829 committing files:
817 committing files:
830 committing manifest
818 committing manifest
831 committing changelog
819 committing changelog
832 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
820 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
833 $ hg status
821 $ hg status
834 ? c
822 ? c
835
823
836 Rollback, revert, and check expansion
824 Rollback, revert, and check expansion
837
825
838 $ hg rollback
826 $ hg rollback
839 repository tip rolled back to revision 2 (undo commit)
827 repository tip rolled back to revision 2 (undo commit)
840 working directory now based on revision 2
828 working directory now based on revision 2
841 $ hg status
829 $ hg status
842 R a
830 R a
843 ? c
831 ? c
844 $ hg revert --no-backup --rev tip a
832 $ hg revert --no-backup --rev tip a
845 $ cat a
833 $ cat a
846 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
834 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
847 do not process $Id:
835 do not process $Id:
848 xxx $
836 xxx $
849 $Xinfo: User Name <user@example.com>: firstline $
837 $Xinfo: User Name <user@example.com>: firstline $
850
838
851 Clone to test global and local configurations
839 Clone to test global and local configurations
852
840
853 $ cd ..
841 $ cd ..
854
842
855 Expansion in destination with global configuration
843 Expansion in destination with global configuration
856
844
857 $ hg --quiet clone Test globalconf
845 $ hg --quiet clone Test globalconf
858 $ cat globalconf/a
846 $ cat globalconf/a
859 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
847 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
860 do not process $Id:
848 do not process $Id:
861 xxx $
849 xxx $
862 $Xinfo: User Name <user@example.com>: firstline $
850 $Xinfo: User Name <user@example.com>: firstline $
863
851
864 No expansion in destination with local configuration in origin only
852 No expansion in destination with local configuration in origin only
865
853
866 $ hg --quiet --config 'keyword.**=ignore' clone Test localconf
854 $ hg --quiet --config 'keyword.**=ignore' clone Test localconf
867 $ cat localconf/a
855 $ cat localconf/a
868 expand $Id$
856 expand $Id$
869 do not process $Id:
857 do not process $Id:
870 xxx $
858 xxx $
871 $Xinfo$
859 $Xinfo$
872
860
873 Clone to test incoming
861 Clone to test incoming
874
862
875 $ hg clone -r1 Test Test-a
863 $ hg clone -r1 Test Test-a
876 adding changesets
864 adding changesets
877 adding manifests
865 adding manifests
878 adding file changes
866 adding file changes
879 added 2 changesets with 3 changes to 3 files
867 added 2 changesets with 3 changes to 3 files
880 updating to branch default
868 updating to branch default
881 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
869 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
882 $ cd Test-a
870 $ cd Test-a
883 $ cat <<EOF >> .hg/hgrc
871 $ cat <<EOF >> .hg/hgrc
884 > [paths]
872 > [paths]
885 > default = ../Test
873 > default = ../Test
886 > EOF
874 > EOF
887 $ hg incoming
875 $ hg incoming
888 comparing with $TESTTMP/Test (glob)
876 comparing with $TESTTMP/Test (glob)
889 searching for changes
877 searching for changes
890 changeset: 2:bb948857c743
878 changeset: 2:bb948857c743
891 tag: tip
879 tag: tip
892 user: User Name <user@example.com>
880 user: User Name <user@example.com>
893 date: Thu Jan 01 00:00:02 1970 +0000
881 date: Thu Jan 01 00:00:02 1970 +0000
894 summary: firstline
882 summary: firstline
895
883
896 Imported patch should not be rejected
884 Imported patch should not be rejected
897
885
898 >>> import re
886 >>> import re
899 >>> text = re.sub(r'(Id.*)', r'\1 rejecttest', open('a').read())
887 >>> text = re.sub(r'(Id.*)', r'\1 rejecttest', open('a').read())
900 >>> open('a', 'wb').write(text)
888 >>> open('a', 'wb').write(text)
901 $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
889 $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
902 committing files:
890 committing files:
903 a
891 a
904 committing manifest
892 committing manifest
905 committing changelog
893 committing changelog
906 overwriting a expanding keywords
894 overwriting a expanding keywords
907 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
895 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
908 $ hg export -o ../rejecttest.diff tip
896 $ hg export -o ../rejecttest.diff tip
909 $ cd ../Test
897 $ cd ../Test
910 $ hg import ../rejecttest.diff
898 $ hg import ../rejecttest.diff
911 applying ../rejecttest.diff
899 applying ../rejecttest.diff
912 $ cat a b
900 $ cat a b
913 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
901 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
914 do not process $Id: rejecttest
902 do not process $Id: rejecttest
915 xxx $
903 xxx $
916 $Xinfo: User Name <user@example.com>: rejects? $
904 $Xinfo: User Name <user@example.com>: rejects? $
917 ignore $Id$
905 ignore $Id$
918
906
919 $ hg rollback
907 $ hg rollback
920 repository tip rolled back to revision 2 (undo import)
908 repository tip rolled back to revision 2 (undo import)
921 working directory now based on revision 2
909 working directory now based on revision 2
922 $ hg update --clean
910 $ hg update --clean
923 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
911 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
924
912
925 kwexpand/kwshrink on selected files
913 kwexpand/kwshrink on selected files
926
914
927 $ mkdir x
915 $ mkdir x
928 $ hg copy a x/a
916 $ hg copy a x/a
929 $ hg --verbose kwshrink a
917 $ hg --verbose kwshrink a
930 overwriting a shrinking keywords
918 overwriting a shrinking keywords
931 - sleep required for dirstate.normal() check
919 - sleep required for dirstate.normal() check
932 $ sleep 1
920 $ sleep 1
933 $ hg status a
921 $ hg status a
934 $ hg --verbose kwexpand a
922 $ hg --verbose kwexpand a
935 overwriting a expanding keywords
923 overwriting a expanding keywords
936 $ hg status a
924 $ hg status a
937
925
938 kwexpand x/a should abort
926 kwexpand x/a should abort
939
927
940 $ hg --verbose kwexpand x/a
928 $ hg --verbose kwexpand x/a
941 abort: outstanding uncommitted changes
929 abort: outstanding uncommitted changes
942 [255]
930 [255]
943 $ cd x
931 $ cd x
944 $ hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
932 $ hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
945 committing files:
933 committing files:
946 x/a
934 x/a
947 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
935 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
948 committing manifest
936 committing manifest
949 committing changelog
937 committing changelog
950 overwriting x/a expanding keywords
938 overwriting x/a expanding keywords
951 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
939 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
952 $ cat a
940 $ cat a
953 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
941 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
954 do not process $Id:
942 do not process $Id:
955 xxx $
943 xxx $
956 $Xinfo: User Name <user@example.com>: xa $
944 $Xinfo: User Name <user@example.com>: xa $
957
945
958 kwshrink a inside directory x
946 kwshrink a inside directory x
959
947
960 $ hg --verbose kwshrink a
948 $ hg --verbose kwshrink a
961 overwriting x/a shrinking keywords
949 overwriting x/a shrinking keywords
962 $ cat a
950 $ cat a
963 expand $Id$
951 expand $Id$
964 do not process $Id:
952 do not process $Id:
965 xxx $
953 xxx $
966 $Xinfo$
954 $Xinfo$
967 $ cd ..
955 $ cd ..
968
956
969 kwexpand nonexistent
957 kwexpand nonexistent
970
958
971 $ hg kwexpand nonexistent
959 $ hg kwexpand nonexistent
972 nonexistent:* (glob)
960 nonexistent:* (glob)
973
961
974
962
975 #if serve
963 #if serve
976 hg serve
964 hg serve
977 - expand with hgweb file
965 - expand with hgweb file
978 - no expansion with hgweb annotate/changeset/filediff
966 - no expansion with hgweb annotate/changeset/filediff
979 - check errors
967 - check errors
980
968
981 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
969 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
982 $ cat hg.pid >> $DAEMON_PIDS
970 $ cat hg.pid >> $DAEMON_PIDS
983 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/a/?style=raw'
971 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/a/?style=raw'
984 200 Script output follows
972 200 Script output follows
985
973
986 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
974 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
987 do not process $Id:
975 do not process $Id:
988 xxx $
976 xxx $
989 $Xinfo: User Name <user@example.com>: firstline $
977 $Xinfo: User Name <user@example.com>: firstline $
990 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/a/?style=raw'
978 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/a/?style=raw'
991 200 Script output follows
979 200 Script output follows
992
980
993
981
994 user@1: expand $Id$
982 user@1: expand $Id$
995 user@1: do not process $Id:
983 user@1: do not process $Id:
996 user@1: xxx $
984 user@1: xxx $
997 user@2: $Xinfo$
985 user@2: $Xinfo$
998
986
999
987
1000
988
1001
989
1002 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip/?style=raw'
990 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip/?style=raw'
1003 200 Script output follows
991 200 Script output follows
1004
992
1005
993
1006 # HG changeset patch
994 # HG changeset patch
1007 # User User Name <user@example.com>
995 # User User Name <user@example.com>
1008 # Date 3 0
996 # Date 3 0
1009 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
997 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
1010 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
998 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
1011 xa
999 xa
1012
1000
1013 diff -r bb948857c743 -r b4560182a3f9 x/a
1001 diff -r bb948857c743 -r b4560182a3f9 x/a
1014 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1002 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1015 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
1003 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
1016 @@ -0,0 +1,4 @@
1004 @@ -0,0 +1,4 @@
1017 +expand $Id$
1005 +expand $Id$
1018 +do not process $Id:
1006 +do not process $Id:
1019 +xxx $
1007 +xxx $
1020 +$Xinfo$
1008 +$Xinfo$
1021
1009
1022 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/bb948857c743/a?style=raw'
1010 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/bb948857c743/a?style=raw'
1023 200 Script output follows
1011 200 Script output follows
1024
1012
1025
1013
1026 diff -r ef63ca68695b -r bb948857c743 a
1014 diff -r ef63ca68695b -r bb948857c743 a
1027 --- a/a Thu Jan 01 00:00:00 1970 +0000
1015 --- a/a Thu Jan 01 00:00:00 1970 +0000
1028 +++ b/a Thu Jan 01 00:00:02 1970 +0000
1016 +++ b/a Thu Jan 01 00:00:02 1970 +0000
1029 @@ -1,3 +1,4 @@
1017 @@ -1,3 +1,4 @@
1030 expand $Id$
1018 expand $Id$
1031 do not process $Id:
1019 do not process $Id:
1032 xxx $
1020 xxx $
1033 +$Xinfo$
1021 +$Xinfo$
1034
1022
1035
1023
1036
1024
1037
1025
1038 $ cat errors.log
1026 $ cat errors.log
1039 #endif
1027 #endif
1040
1028
1041 Prepare merge and resolve tests
1029 Prepare merge and resolve tests
1042
1030
1043 $ echo '$Id$' > m
1031 $ echo '$Id$' > m
1044 $ hg add m
1032 $ hg add m
1045 $ hg commit -m 4kw
1033 $ hg commit -m 4kw
1046 $ echo foo >> m
1034 $ echo foo >> m
1047 $ hg commit -m 5foo
1035 $ hg commit -m 5foo
1048
1036
1049 simplemerge
1037 simplemerge
1050
1038
1051 $ hg update 4
1039 $ hg update 4
1052 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1040 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1053 $ echo foo >> m
1041 $ echo foo >> m
1054 $ hg commit -m 6foo
1042 $ hg commit -m 6foo
1055 created new head
1043 created new head
1056 $ hg merge
1044 $ hg merge
1057 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1045 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1058 (branch merge, don't forget to commit)
1046 (branch merge, don't forget to commit)
1059 $ hg commit -m simplemerge
1047 $ hg commit -m simplemerge
1060 $ cat m
1048 $ cat m
1061 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
1049 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
1062 foo
1050 foo
1063
1051
1064 conflict: keyword should stay outside conflict zone
1052 conflict: keyword should stay outside conflict zone
1065
1053
1066 $ hg update 4
1054 $ hg update 4
1067 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1055 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1068 $ echo bar >> m
1056 $ echo bar >> m
1069 $ hg commit -m 8bar
1057 $ hg commit -m 8bar
1070 created new head
1058 created new head
1071 $ hg merge
1059 $ hg merge
1072 merging m
1060 merging m
1073 warning: conflicts during merge.
1061 warning: conflicts during merge.
1074 merging m incomplete! (edit conflicts, then use 'hg resolve --mark')
1062 merging m incomplete! (edit conflicts, then use 'hg resolve --mark')
1075 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1063 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1076 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1064 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1077 [1]
1065 [1]
1078 $ cat m
1066 $ cat m
1079 $Id$
1067 $Id$
1080 <<<<<<< local: 88a80c8d172e - test: 8bar
1068 <<<<<<< local: 88a80c8d172e - test: 8bar
1081 bar
1069 bar
1082 =======
1070 =======
1083 foo
1071 foo
1084 >>>>>>> other: 85d2d2d732a5 - test: simplemerge
1072 >>>>>>> other: 85d2d2d732a5 - test: simplemerge
1085
1073
1086 resolve to local, m must contain hash of last change (local parent)
1074 resolve to local, m must contain hash of last change (local parent)
1087
1075
1088 $ hg resolve -t internal:local -a
1076 $ hg resolve -t internal:local -a
1089 (no more unresolved files)
1077 (no more unresolved files)
1090 $ hg commit -m localresolve
1078 $ hg commit -m localresolve
1091 $ cat m
1079 $ cat m
1092 $Id: m 88a80c8d172e Thu, 01 Jan 1970 00:00:00 +0000 test $
1080 $Id: m 88a80c8d172e Thu, 01 Jan 1970 00:00:00 +0000 test $
1093 bar
1081 bar
1094
1082
1095 Test restricted mode with transplant -b
1083 Test restricted mode with transplant -b
1096
1084
1097 $ hg update 6
1085 $ hg update 6
1098 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1086 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1099 $ hg branch foo
1087 $ hg branch foo
1100 marked working directory as branch foo
1088 marked working directory as branch foo
1101 (branches are permanent and global, did you want a bookmark?)
1089 (branches are permanent and global, did you want a bookmark?)
1102 $ mv a a.bak
1090 $ mv a a.bak
1103 $ echo foobranch > a
1091 $ echo foobranch > a
1104 $ cat a.bak >> a
1092 $ cat a.bak >> a
1105 $ rm a.bak
1093 $ rm a.bak
1106 $ hg commit -m 9foobranch
1094 $ hg commit -m 9foobranch
1107 $ hg update default
1095 $ hg update default
1108 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1096 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1109 $ hg -y transplant -b foo tip
1097 $ hg -y transplant -b foo tip
1110 applying 4aa30d025d50
1098 applying 4aa30d025d50
1111 4aa30d025d50 transplanted to e00abbf63521
1099 4aa30d025d50 transplanted to e00abbf63521
1112
1100
1113 Expansion in changeset but not in file
1101 Expansion in changeset but not in file
1114
1102
1115 $ hg tip -p
1103 $ hg tip -p
1116 changeset: 11:e00abbf63521
1104 changeset: 11:e00abbf63521
1117 tag: tip
1105 tag: tip
1118 parent: 9:800511b3a22d
1106 parent: 9:800511b3a22d
1119 user: test
1107 user: test
1120 date: Thu Jan 01 00:00:00 1970 +0000
1108 date: Thu Jan 01 00:00:00 1970 +0000
1121 summary: 9foobranch
1109 summary: 9foobranch
1122
1110
1123 diff -r 800511b3a22d -r e00abbf63521 a
1111 diff -r 800511b3a22d -r e00abbf63521 a
1124 --- a/a Thu Jan 01 00:00:00 1970 +0000
1112 --- a/a Thu Jan 01 00:00:00 1970 +0000
1125 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1113 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1126 @@ -1,3 +1,4 @@
1114 @@ -1,3 +1,4 @@
1127 +foobranch
1115 +foobranch
1128 expand $Id$
1116 expand $Id$
1129 do not process $Id:
1117 do not process $Id:
1130 xxx $
1118 xxx $
1131
1119
1132 $ head -n 2 a
1120 $ head -n 2 a
1133 foobranch
1121 foobranch
1134 expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $
1122 expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $
1135
1123
1136 Turn off expansion
1124 Turn off expansion
1137
1125
1138 $ hg -q rollback
1126 $ hg -q rollback
1139 $ hg -q update -C
1127 $ hg -q update -C
1140
1128
1141 kwshrink with unknown file u
1129 kwshrink with unknown file u
1142
1130
1143 $ cp a u
1131 $ cp a u
1144 $ hg --verbose kwshrink
1132 $ hg --verbose kwshrink
1145 overwriting a shrinking keywords
1133 overwriting a shrinking keywords
1146 overwriting m shrinking keywords
1134 overwriting m shrinking keywords
1147 overwriting x/a shrinking keywords
1135 overwriting x/a shrinking keywords
1148
1136
1149 Keywords shrunk in working directory, but not yet disabled
1137 Keywords shrunk in working directory, but not yet disabled
1150 - cat shows unexpanded keywords
1138 - cat shows unexpanded keywords
1151 - hg cat shows expanded keywords
1139 - hg cat shows expanded keywords
1152
1140
1153 $ cat a b
1141 $ cat a b
1154 expand $Id$
1142 expand $Id$
1155 do not process $Id:
1143 do not process $Id:
1156 xxx $
1144 xxx $
1157 $Xinfo$
1145 $Xinfo$
1158 ignore $Id$
1146 ignore $Id$
1159 $ hg cat sym a b && echo
1147 $ hg cat sym a b && echo
1160 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
1148 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
1161 do not process $Id:
1149 do not process $Id:
1162 xxx $
1150 xxx $
1163 $Xinfo: User Name <user@example.com>: firstline $
1151 $Xinfo: User Name <user@example.com>: firstline $
1164 ignore $Id$
1152 ignore $Id$
1165 a
1153 a
1166
1154
1167 Now disable keyword expansion
1155 Now disable keyword expansion
1168
1156
1169 $ cp $HGRCPATH $HGRCPATH.backup
1157 $ cp $HGRCPATH $HGRCPATH.backup
1170 $ rm "$HGRCPATH"
1158 $ rm "$HGRCPATH"
1171 $ cat a b
1159 $ cat a b
1172 expand $Id$
1160 expand $Id$
1173 do not process $Id:
1161 do not process $Id:
1174 xxx $
1162 xxx $
1175 $Xinfo$
1163 $Xinfo$
1176 ignore $Id$
1164 ignore $Id$
1177 $ hg cat sym a b && echo
1165 $ hg cat sym a b && echo
1178 expand $Id$
1166 expand $Id$
1179 do not process $Id:
1167 do not process $Id:
1180 xxx $
1168 xxx $
1181 $Xinfo$
1169 $Xinfo$
1182 ignore $Id$
1170 ignore $Id$
1183 a
1171 a
1184
1172
1185 enable keyword expansion again
1173 enable keyword expansion again
1186
1174
1187 $ cat $HGRCPATH.backup >> $HGRCPATH
1175 $ cat $HGRCPATH.backup >> $HGRCPATH
1188
1176
1189 Test restricted mode with unshelve
1177 Test restricted mode with unshelve
1190
1178
1191 $ cat <<EOF >> $HGRCPATH
1179 $ cat <<EOF >> $HGRCPATH
1192 > [extensions]
1180 > [extensions]
1193 > shelve =
1181 > shelve =
1194 > EOF
1182 > EOF
1195
1183
1196 $ echo xxxx >> a
1184 $ echo xxxx >> a
1197 $ hg diff
1185 $ hg diff
1198 diff -r 800511b3a22d a
1186 diff -r 800511b3a22d a
1199 --- a/a Thu Jan 01 00:00:00 1970 +0000
1187 --- a/a Thu Jan 01 00:00:00 1970 +0000
1200 +++ b/a * (glob)
1188 +++ b/a * (glob)
1201 @@ -2,3 +2,4 @@
1189 @@ -2,3 +2,4 @@
1202 do not process $Id:
1190 do not process $Id:
1203 xxx $
1191 xxx $
1204 $Xinfo$
1192 $Xinfo$
1205 +xxxx
1193 +xxxx
1206 $ hg shelve -q --name tmp
1194 $ hg shelve -q --name tmp
1207 $ hg shelve --list --patch
1195 $ hg shelve --list --patch
1208 tmp (*) changes to 'localresolve' (glob)
1196 tmp (*) changes to 'localresolve' (glob)
1209
1197
1210 diff --git a/a b/a
1198 diff --git a/a b/a
1211 --- a/a
1199 --- a/a
1212 +++ b/a
1200 +++ b/a
1213 @@ -2,3 +2,4 @@
1201 @@ -2,3 +2,4 @@
1214 do not process $Id:
1202 do not process $Id:
1215 xxx $
1203 xxx $
1216 $Xinfo$
1204 $Xinfo$
1217 +xxxx
1205 +xxxx
1218
1206
1219 $ hg update -q -C 10
1207 $ hg update -q -C 10
1220 $ hg unshelve -q tmp
1208 $ hg unshelve -q tmp
1221 $ hg diff
1209 $ hg diff
1222 diff -r 4aa30d025d50 a
1210 diff -r 4aa30d025d50 a
1223 --- a/a Thu Jan 01 00:00:00 1970 +0000
1211 --- a/a Thu Jan 01 00:00:00 1970 +0000
1224 +++ b/a * (glob)
1212 +++ b/a * (glob)
1225 @@ -3,3 +3,4 @@
1213 @@ -3,3 +3,4 @@
1226 do not process $Id:
1214 do not process $Id:
1227 xxx $
1215 xxx $
1228 $Xinfo$
1216 $Xinfo$
1229 +xxxx
1217 +xxxx
1230
1218
1231 Test restricted mode with rebase
1219 Test restricted mode with rebase
1232
1220
1233 $ cat <<EOF >> $HGRCPATH
1221 $ cat <<EOF >> $HGRCPATH
1234 > [extensions]
1222 > [extensions]
1235 > rebase =
1223 > rebase =
1236 > EOF
1224 > EOF
1237
1225
1238 $ hg update -q -C 9
1226 $ hg update -q -C 9
1239
1227
1240 $ echo xxxx >> a
1228 $ echo xxxx >> a
1241 $ hg commit -m '#11'
1229 $ hg commit -m '#11'
1242 $ hg diff -c 11
1230 $ hg diff -c 11
1243 diff -r 800511b3a22d -r b07670694489 a
1231 diff -r 800511b3a22d -r b07670694489 a
1244 --- a/a Thu Jan 01 00:00:00 1970 +0000
1232 --- a/a Thu Jan 01 00:00:00 1970 +0000
1245 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1233 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1246 @@ -2,3 +2,4 @@
1234 @@ -2,3 +2,4 @@
1247 do not process $Id:
1235 do not process $Id:
1248 xxx $
1236 xxx $
1249 $Xinfo$
1237 $Xinfo$
1250 +xxxx
1238 +xxxx
1251
1239
1252 $ hg diff -c 10
1240 $ hg diff -c 10
1253 diff -r 27d48ee14f67 -r 4aa30d025d50 a
1241 diff -r 27d48ee14f67 -r 4aa30d025d50 a
1254 --- a/a Thu Jan 01 00:00:00 1970 +0000
1242 --- a/a Thu Jan 01 00:00:00 1970 +0000
1255 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1243 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1256 @@ -1,3 +1,4 @@
1244 @@ -1,3 +1,4 @@
1257 +foobranch
1245 +foobranch
1258 expand $Id$
1246 expand $Id$
1259 do not process $Id:
1247 do not process $Id:
1260 xxx $
1248 xxx $
1261
1249
1262 $ hg rebase -q -s 10 -d 11 --keep
1250 $ hg rebase -q -s 10 -d 11 --keep
1263 $ hg diff -r 9 -r 12 a
1251 $ hg diff -r 9 -r 12 a
1264 diff -r 800511b3a22d -r 1939b927726c a
1252 diff -r 800511b3a22d -r 1939b927726c a
1265 --- a/a Thu Jan 01 00:00:00 1970 +0000
1253 --- a/a Thu Jan 01 00:00:00 1970 +0000
1266 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1254 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1267 @@ -1,4 +1,6 @@
1255 @@ -1,4 +1,6 @@
1268 +foobranch
1256 +foobranch
1269 expand $Id$
1257 expand $Id$
1270 do not process $Id:
1258 do not process $Id:
1271 xxx $
1259 xxx $
1272 $Xinfo$
1260 $Xinfo$
1273 +xxxx
1261 +xxxx
1274
1262
1275 Test restricted mode with graft
1263 Test restricted mode with graft
1276
1264
1277 $ hg graft -q 10
1265 $ hg graft -q 10
1278 $ hg diff -r 9 -r 13 a
1266 $ hg diff -r 9 -r 13 a
1279 diff -r 800511b3a22d -r 01a68de1003a a
1267 diff -r 800511b3a22d -r 01a68de1003a a
1280 --- a/a Thu Jan 01 00:00:00 1970 +0000
1268 --- a/a Thu Jan 01 00:00:00 1970 +0000
1281 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1269 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1282 @@ -1,4 +1,6 @@
1270 @@ -1,4 +1,6 @@
1283 +foobranch
1271 +foobranch
1284 expand $Id$
1272 expand $Id$
1285 do not process $Id:
1273 do not process $Id:
1286 xxx $
1274 xxx $
1287 $Xinfo$
1275 $Xinfo$
1288 +xxxx
1276 +xxxx
1289
1277
1290 Test restricted mode with backout
1278 Test restricted mode with backout
1291
1279
1292 $ hg backout -q 11
1280 $ hg backout -q 11
1293 $ hg diff a
1281 $ hg diff a
1294 diff -r 01a68de1003a a
1282 diff -r 01a68de1003a a
1295 --- a/a Thu Jan 01 00:00:00 1970 +0000
1283 --- a/a Thu Jan 01 00:00:00 1970 +0000
1296 +++ b/a * (glob)
1284 +++ b/a * (glob)
1297 @@ -3,4 +3,3 @@
1285 @@ -3,4 +3,3 @@
1298 do not process $Id:
1286 do not process $Id:
1299 xxx $
1287 xxx $
1300 $Xinfo$
1288 $Xinfo$
1301 -xxxx
1289 -xxxx
1302
1290
1303 Test restricted mode with histedit
1291 Test restricted mode with histedit
1304
1292
1305 $ cat <<EOF >> $HGRCPATH
1293 $ cat <<EOF >> $HGRCPATH
1306 > [extensions]
1294 > [extensions]
1307 > histedit =
1295 > histedit =
1308 > EOF
1296 > EOF
1309
1297
1310 $ hg commit -m 'backout #11'
1298 $ hg commit -m 'backout #11'
1311 $ hg histedit -q --command - 13 <<EOF
1299 $ hg histedit -q --command - 13 <<EOF
1312 > pick 49f5f2d940c3 14 backout #11
1300 > pick 49f5f2d940c3 14 backout #11
1313 > pick 01a68de1003a 13 9foobranch
1301 > pick 01a68de1003a 13 9foobranch
1314 > EOF
1302 > EOF
1315
1303
1316 Test restricted mode with fetch (with merge)
1304 Test restricted mode with fetch (with merge)
1317
1305
1318 $ cat <<EOF >> $HGRCPATH
1306 $ cat <<EOF >> $HGRCPATH
1319 > [extensions]
1307 > [extensions]
1320 > fetch =
1308 > fetch =
1321 > EOF
1309 > EOF
1322
1310
1323 $ hg clone -q -r 9 . ../fetch-merge
1311 $ hg clone -q -r 9 . ../fetch-merge
1324 $ cd ../fetch-merge
1312 $ cd ../fetch-merge
1325 $ hg -R ../Test export 10 | hg import -q -
1313 $ hg -R ../Test export 10 | hg import -q -
1326 $ hg fetch -q -r 11
1314 $ hg fetch -q -r 11
1327 $ hg diff -r 9 a
1315 $ hg diff -r 9 a
1328 diff -r 800511b3a22d a
1316 diff -r 800511b3a22d a
1329 --- a/a Thu Jan 01 00:00:00 1970 +0000
1317 --- a/a Thu Jan 01 00:00:00 1970 +0000
1330 +++ b/a * (glob)
1318 +++ b/a * (glob)
1331 @@ -1,4 +1,6 @@
1319 @@ -1,4 +1,6 @@
1332 +foobranch
1320 +foobranch
1333 expand $Id$
1321 expand $Id$
1334 do not process $Id:
1322 do not process $Id:
1335 xxx $
1323 xxx $
1336 $Xinfo$
1324 $Xinfo$
1337 +xxxx
1325 +xxxx
1338
1326
1339 $ cd ..
1327 $ cd ..
@@ -1,620 +1,610 b''
1 $ cat <<EOF >> $HGRCPATH
1 $ cat <<EOF >> $HGRCPATH
2 > [ui]
2 > [ui]
3 > commitsubrepos = Yes
3 > commitsubrepos = Yes
4 > [extensions]
4 > [extensions]
5 > mq =
5 > mq =
6 > record =
6 > record =
7 > [diff]
7 > [diff]
8 > nodates = 1
8 > nodates = 1
9 > EOF
9 > EOF
10
10
11 $ stdin=`pwd`/stdin.tmp
11 $ stdin=`pwd`/stdin.tmp
12
12
13 fn to create new repository w/dirty subrepo, and cd into it
13 fn to create new repository w/dirty subrepo, and cd into it
14 $ mkrepo() {
14 $ mkrepo() {
15 > hg init $1
15 > hg init $1
16 > cd $1
16 > cd $1
17 > hg qinit
17 > hg qinit
18 > }
18 > }
19
19
20 fn to create dirty subrepo
20 fn to create dirty subrepo
21 $ mksubrepo() {
21 $ mksubrepo() {
22 > hg init $1
22 > hg init $1
23 > cd $1
23 > cd $1
24 > echo a > a
24 > echo a > a
25 > hg add
25 > hg add
26 > cd ..
26 > cd ..
27 > }
27 > }
28
28
29 $ testadd() {
29 $ testadd() {
30 > cat - > "$stdin"
30 > cat - > "$stdin"
31 > mksubrepo sub
31 > mksubrepo sub
32 > echo sub = sub >> .hgsub
32 > echo sub = sub >> .hgsub
33 > hg add .hgsub
33 > hg add .hgsub
34 > echo % abort when adding .hgsub w/dirty subrepo
34 > echo % abort when adding .hgsub w/dirty subrepo
35 > hg status -S
35 > hg status -S
36 > echo '%' $*
36 > echo '%' $*
37 > cat "$stdin" | hg $*
37 > cat "$stdin" | hg $*
38 > echo [$?]
38 > echo [$?]
39 > hg -R sub ci -m0sub
39 > hg -R sub ci -m0sub
40 > echo % update substate when adding .hgsub w/clean updated subrepo
40 > echo % update substate when adding .hgsub w/clean updated subrepo
41 > hg status -S
41 > hg status -S
42 > echo '%' $*
42 > echo '%' $*
43 > cat "$stdin" | hg $*
43 > cat "$stdin" | hg $*
44 > hg debugsub
44 > hg debugsub
45 > }
45 > }
46
46
47 $ testmod() {
47 $ testmod() {
48 > cat - > "$stdin"
48 > cat - > "$stdin"
49 > mksubrepo sub2
49 > mksubrepo sub2
50 > echo sub2 = sub2 >> .hgsub
50 > echo sub2 = sub2 >> .hgsub
51 > echo % abort when modifying .hgsub w/dirty subrepo
51 > echo % abort when modifying .hgsub w/dirty subrepo
52 > hg status -S
52 > hg status -S
53 > echo '%' $*
53 > echo '%' $*
54 > cat "$stdin" | hg $*
54 > cat "$stdin" | hg $*
55 > echo [$?]
55 > echo [$?]
56 > hg -R sub2 ci -m0sub2
56 > hg -R sub2 ci -m0sub2
57 > echo % update substate when modifying .hgsub w/clean updated subrepo
57 > echo % update substate when modifying .hgsub w/clean updated subrepo
58 > hg status -S
58 > hg status -S
59 > echo '%' $*
59 > echo '%' $*
60 > cat "$stdin" | hg $*
60 > cat "$stdin" | hg $*
61 > hg debugsub
61 > hg debugsub
62 > }
62 > }
63
63
64 $ testrm1() {
64 $ testrm1() {
65 > cat - > "$stdin"
65 > cat - > "$stdin"
66 > mksubrepo sub3
66 > mksubrepo sub3
67 > echo sub3 = sub3 >> .hgsub
67 > echo sub3 = sub3 >> .hgsub
68 > hg ci -Aqmsub3
68 > hg ci -Aqmsub3
69 > $EXTRA
69 > $EXTRA
70 > echo b >> sub3/a
70 > echo b >> sub3/a
71 > hg rm .hgsub
71 > hg rm .hgsub
72 > echo % update substate when removing .hgsub w/dirty subrepo
72 > echo % update substate when removing .hgsub w/dirty subrepo
73 > hg status -S
73 > hg status -S
74 > echo '%' $*
74 > echo '%' $*
75 > cat "$stdin" | hg $*
75 > cat "$stdin" | hg $*
76 > echo % debugsub should be empty
76 > echo % debugsub should be empty
77 > hg debugsub
77 > hg debugsub
78 > }
78 > }
79
79
80 $ testrm2() {
80 $ testrm2() {
81 > cat - > "$stdin"
81 > cat - > "$stdin"
82 > mksubrepo sub4
82 > mksubrepo sub4
83 > echo sub4 = sub4 >> .hgsub
83 > echo sub4 = sub4 >> .hgsub
84 > hg ci -Aqmsub4
84 > hg ci -Aqmsub4
85 > $EXTRA
85 > $EXTRA
86 > hg rm .hgsub
86 > hg rm .hgsub
87 > echo % update substate when removing .hgsub w/clean updated subrepo
87 > echo % update substate when removing .hgsub w/clean updated subrepo
88 > hg status -S
88 > hg status -S
89 > echo '%' $*
89 > echo '%' $*
90 > cat "$stdin" | hg $*
90 > cat "$stdin" | hg $*
91 > echo % debugsub should be empty
91 > echo % debugsub should be empty
92 > hg debugsub
92 > hg debugsub
93 > }
93 > }
94
94
95
95
96 handle subrepos safely on qnew
96 handle subrepos safely on qnew
97
97
98 $ mkrepo repo-2499-qnew
98 $ mkrepo repo-2499-qnew
99 $ testadd qnew -X path:no-effect -m0 0.diff
99 $ testadd qnew -X path:no-effect -m0 0.diff
100 adding a
100 adding a
101 % abort when adding .hgsub w/dirty subrepo
101 % abort when adding .hgsub w/dirty subrepo
102 A .hgsub
102 A .hgsub
103 A sub/a
103 A sub/a
104 % qnew -X path:no-effect -m0 0.diff
104 % qnew -X path:no-effect -m0 0.diff
105 abort: uncommitted changes in subrepository 'sub'
105 abort: uncommitted changes in subrepository 'sub'
106 [255]
106 [255]
107 % update substate when adding .hgsub w/clean updated subrepo
107 % update substate when adding .hgsub w/clean updated subrepo
108 A .hgsub
108 A .hgsub
109 % qnew -X path:no-effect -m0 0.diff
109 % qnew -X path:no-effect -m0 0.diff
110 path sub
110 path sub
111 source sub
111 source sub
112 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
112 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
113
113
114 $ testmod qnew --cwd .. -R repo-2499-qnew -X path:no-effect -m1 1.diff
114 $ testmod qnew --cwd .. -R repo-2499-qnew -X path:no-effect -m1 1.diff
115 adding a
115 adding a
116 % abort when modifying .hgsub w/dirty subrepo
116 % abort when modifying .hgsub w/dirty subrepo
117 M .hgsub
117 M .hgsub
118 A sub2/a
118 A sub2/a
119 % qnew --cwd .. -R repo-2499-qnew -X path:no-effect -m1 1.diff
119 % qnew --cwd .. -R repo-2499-qnew -X path:no-effect -m1 1.diff
120 abort: uncommitted changes in subrepository 'sub2'
120 abort: uncommitted changes in subrepository 'sub2'
121 [255]
121 [255]
122 % update substate when modifying .hgsub w/clean updated subrepo
122 % update substate when modifying .hgsub w/clean updated subrepo
123 M .hgsub
123 M .hgsub
124 % qnew --cwd .. -R repo-2499-qnew -X path:no-effect -m1 1.diff
124 % qnew --cwd .. -R repo-2499-qnew -X path:no-effect -m1 1.diff
125 path sub
125 path sub
126 source sub
126 source sub
127 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
127 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
128 path sub2
128 path sub2
129 source sub2
129 source sub2
130 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
130 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
131
131
132 $ hg qpop -qa
132 $ hg qpop -qa
133 patch queue now empty
133 patch queue now empty
134 $ testrm1 qnew -m2 2.diff
134 $ testrm1 qnew -m2 2.diff
135 adding a
135 adding a
136 % update substate when removing .hgsub w/dirty subrepo
136 % update substate when removing .hgsub w/dirty subrepo
137 M sub3/a
137 M sub3/a
138 R .hgsub
138 R .hgsub
139 % qnew -m2 2.diff
139 % qnew -m2 2.diff
140 % debugsub should be empty
140 % debugsub should be empty
141
141
142 $ hg qpop -qa
142 $ hg qpop -qa
143 patch queue now empty
143 patch queue now empty
144 $ testrm2 qnew -m3 3.diff
144 $ testrm2 qnew -m3 3.diff
145 adding a
145 adding a
146 % update substate when removing .hgsub w/clean updated subrepo
146 % update substate when removing .hgsub w/clean updated subrepo
147 R .hgsub
147 R .hgsub
148 % qnew -m3 3.diff
148 % qnew -m3 3.diff
149 % debugsub should be empty
149 % debugsub should be empty
150
150
151 $ cd ..
151 $ cd ..
152
152
153
153
154 handle subrepos safely on qrefresh
154 handle subrepos safely on qrefresh
155
155
156 $ mkrepo repo-2499-qrefresh
156 $ mkrepo repo-2499-qrefresh
157 $ hg qnew -m0 0.diff
157 $ hg qnew -m0 0.diff
158 $ testadd qrefresh
158 $ testadd qrefresh
159 adding a
159 adding a
160 % abort when adding .hgsub w/dirty subrepo
160 % abort when adding .hgsub w/dirty subrepo
161 A .hgsub
161 A .hgsub
162 A sub/a
162 A sub/a
163 % qrefresh
163 % qrefresh
164 abort: uncommitted changes in subrepository 'sub'
164 abort: uncommitted changes in subrepository 'sub'
165 [255]
165 [255]
166 % update substate when adding .hgsub w/clean updated subrepo
166 % update substate when adding .hgsub w/clean updated subrepo
167 A .hgsub
167 A .hgsub
168 % qrefresh
168 % qrefresh
169 path sub
169 path sub
170 source sub
170 source sub
171 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
171 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
172
172
173 $ hg qnew -m1 1.diff
173 $ hg qnew -m1 1.diff
174 $ testmod qrefresh
174 $ testmod qrefresh
175 adding a
175 adding a
176 % abort when modifying .hgsub w/dirty subrepo
176 % abort when modifying .hgsub w/dirty subrepo
177 M .hgsub
177 M .hgsub
178 A sub2/a
178 A sub2/a
179 % qrefresh
179 % qrefresh
180 abort: uncommitted changes in subrepository 'sub2'
180 abort: uncommitted changes in subrepository 'sub2'
181 [255]
181 [255]
182 % update substate when modifying .hgsub w/clean updated subrepo
182 % update substate when modifying .hgsub w/clean updated subrepo
183 M .hgsub
183 M .hgsub
184 % qrefresh
184 % qrefresh
185 path sub
185 path sub
186 source sub
186 source sub
187 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
187 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
188 path sub2
188 path sub2
189 source sub2
189 source sub2
190 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
190 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
191
191
192 $ hg qpop -qa
192 $ hg qpop -qa
193 patch queue now empty
193 patch queue now empty
194 $ EXTRA='hg qnew -m2 2.diff'
194 $ EXTRA='hg qnew -m2 2.diff'
195 $ testrm1 qrefresh
195 $ testrm1 qrefresh
196 adding a
196 adding a
197 % update substate when removing .hgsub w/dirty subrepo
197 % update substate when removing .hgsub w/dirty subrepo
198 M sub3/a
198 M sub3/a
199 R .hgsub
199 R .hgsub
200 % qrefresh
200 % qrefresh
201 % debugsub should be empty
201 % debugsub should be empty
202
202
203 $ hg qpop -qa
203 $ hg qpop -qa
204 patch queue now empty
204 patch queue now empty
205 $ EXTRA='hg qnew -m3 3.diff'
205 $ EXTRA='hg qnew -m3 3.diff'
206 $ testrm2 qrefresh
206 $ testrm2 qrefresh
207 adding a
207 adding a
208 % update substate when removing .hgsub w/clean updated subrepo
208 % update substate when removing .hgsub w/clean updated subrepo
209 R .hgsub
209 R .hgsub
210 % qrefresh
210 % qrefresh
211 % debugsub should be empty
211 % debugsub should be empty
212 $ EXTRA=
212 $ EXTRA=
213
213
214 $ cd ..
214 $ cd ..
215
215
216
216
217 handle subrepos safely on qpush/qpop
217 handle subrepos safely on qpush/qpop
218 (and we cannot qpop / qpush with a modified subrepo)
218 (and we cannot qpop / qpush with a modified subrepo)
219
219
220 $ mkrepo repo-2499-qpush
220 $ mkrepo repo-2499-qpush
221 $ mksubrepo sub
221 $ mksubrepo sub
222 adding a
222 adding a
223 $ hg -R sub ci -m0sub
223 $ hg -R sub ci -m0sub
224 $ echo sub = sub > .hgsub
224 $ echo sub = sub > .hgsub
225 $ hg add .hgsub
225 $ hg add .hgsub
226 $ hg commit -m0
226 $ hg commit -m0
227 $ hg debugsub
227 $ hg debugsub
228 path sub
228 path sub
229 source sub
229 source sub
230 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
230 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
231 $ echo foo > ./sub/a
231 $ echo foo > ./sub/a
232 $ hg -R sub commit -m foo
232 $ hg -R sub commit -m foo
233 $ hg commit -m1
233 $ hg commit -m1
234 $ hg qimport -r "0:tip"
234 $ hg qimport -r "0:tip"
235 $ hg -R sub id --id
235 $ hg -R sub id --id
236 aa037b301eba
236 aa037b301eba
237
237
238 qpop
238 qpop
239 $ hg -R sub update 0000
239 $ hg -R sub update 0000
240 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
240 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
241 $ hg qpop
241 $ hg qpop
242 abort: local changed subrepos found, refresh first
242 abort: local changed subrepos found, refresh first
243 [255]
243 [255]
244 $ hg revert sub
244 $ hg revert sub
245 reverting subrepo sub
245 reverting subrepo sub
246 adding sub/a (glob)
246 adding sub/a (glob)
247 $ hg qpop
247 $ hg qpop
248 popping 1.diff
248 popping 1.diff
249 now at: 0.diff
249 now at: 0.diff
250 $ hg status -AS
250 $ hg status -AS
251 C .hgsub
251 C .hgsub
252 C .hgsubstate
252 C .hgsubstate
253 C sub/a
253 C sub/a
254 $ hg -R sub id --id
254 $ hg -R sub id --id
255 b2fdb12cd82b
255 b2fdb12cd82b
256
256
257 qpush
257 qpush
258 $ hg -R sub update 0000
258 $ hg -R sub update 0000
259 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
259 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
260 $ hg qpush
260 $ hg qpush
261 abort: local changed subrepos found, refresh first
261 abort: local changed subrepos found, refresh first
262 [255]
262 [255]
263 $ hg revert sub
263 $ hg revert sub
264 reverting subrepo sub
264 reverting subrepo sub
265 adding sub/a (glob)
265 adding sub/a (glob)
266 $ hg qpush
266 $ hg qpush
267 applying 1.diff
267 applying 1.diff
268 subrepository sub diverged (local revision: b2fdb12cd82b, remote revision: aa037b301eba)
268 subrepository sub diverged (local revision: b2fdb12cd82b, remote revision: aa037b301eba)
269 (M)erge, keep (l)ocal or keep (r)emote? m
269 (M)erge, keep (l)ocal or keep (r)emote? m
270 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
271 now at: 1.diff
271 now at: 1.diff
272 $ hg status -AS
272 $ hg status -AS
273 C .hgsub
273 C .hgsub
274 C .hgsubstate
274 C .hgsubstate
275 C sub/a
275 C sub/a
276 $ hg -R sub id --id
276 $ hg -R sub id --id
277 aa037b301eba
277 aa037b301eba
278
278
279 $ cd ..
279 $ cd ..
280
280
281
281
282 handle subrepos safely on qrecord
282 handle subrepos safely on qrecord
283
283
284 $ mkrepo repo-2499-qrecord
284 $ mkrepo repo-2499-qrecord
285 $ testadd qrecord --config ui.interactive=1 -m0 0.diff <<EOF
285 $ testadd qrecord --config ui.interactive=1 -m0 0.diff <<EOF
286 > y
286 > y
287 > y
287 > y
288 > EOF
288 > EOF
289 adding a
289 adding a
290 % abort when adding .hgsub w/dirty subrepo
290 % abort when adding .hgsub w/dirty subrepo
291 A .hgsub
291 A .hgsub
292 A sub/a
292 A sub/a
293 % qrecord --config ui.interactive=1 -m0 0.diff
293 % qrecord --config ui.interactive=1 -m0 0.diff
294 diff --git a/.hgsub b/.hgsub
294 diff --git a/.hgsub b/.hgsub
295 new file mode 100644
295 new file mode 100644
296 examine changes to '.hgsub'? [Ynesfdaq?] y
296 examine changes to '.hgsub'? [Ynesfdaq?] y
297
297
298 @@ -0,0 +1,1 @@
299 +sub = sub
300 record this change to '.hgsub'? [Ynesfdaq?] y
301
302 warning: subrepo spec file '.hgsub' not found
303 abort: uncommitted changes in subrepository 'sub'
298 abort: uncommitted changes in subrepository 'sub'
304 [255]
299 [255]
305 % update substate when adding .hgsub w/clean updated subrepo
300 % update substate when adding .hgsub w/clean updated subrepo
306 A .hgsub
301 A .hgsub
307 % qrecord --config ui.interactive=1 -m0 0.diff
302 % qrecord --config ui.interactive=1 -m0 0.diff
308 diff --git a/.hgsub b/.hgsub
303 diff --git a/.hgsub b/.hgsub
309 new file mode 100644
304 new file mode 100644
310 examine changes to '.hgsub'? [Ynesfdaq?] y
305 examine changes to '.hgsub'? [Ynesfdaq?] y
311
306
312 @@ -0,0 +1,1 @@
313 +sub = sub
314 record this change to '.hgsub'? [Ynesfdaq?] y
315
316 warning: subrepo spec file '.hgsub' not found
317 path sub
307 path sub
318 source sub
308 source sub
319 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
309 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
320 $ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
310 $ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
321 > y
311 > y
322 > y
312 > y
323 > EOF
313 > EOF
324 adding a
314 adding a
325 % abort when modifying .hgsub w/dirty subrepo
315 % abort when modifying .hgsub w/dirty subrepo
326 M .hgsub
316 M .hgsub
327 A sub2/a
317 A sub2/a
328 % qrecord --config ui.interactive=1 -m1 1.diff
318 % qrecord --config ui.interactive=1 -m1 1.diff
329 diff --git a/.hgsub b/.hgsub
319 diff --git a/.hgsub b/.hgsub
330 1 hunks, 1 lines changed
320 1 hunks, 1 lines changed
331 examine changes to '.hgsub'? [Ynesfdaq?] y
321 examine changes to '.hgsub'? [Ynesfdaq?] y
332
322
333 @@ -1,1 +1,2 @@
323 @@ -1,1 +1,2 @@
334 sub = sub
324 sub = sub
335 +sub2 = sub2
325 +sub2 = sub2
336 record this change to '.hgsub'? [Ynesfdaq?] y
326 record this change to '.hgsub'? [Ynesfdaq?] y
337
327
338 abort: uncommitted changes in subrepository 'sub2'
328 abort: uncommitted changes in subrepository 'sub2'
339 [255]
329 [255]
340 % update substate when modifying .hgsub w/clean updated subrepo
330 % update substate when modifying .hgsub w/clean updated subrepo
341 M .hgsub
331 M .hgsub
342 % qrecord --config ui.interactive=1 -m1 1.diff
332 % qrecord --config ui.interactive=1 -m1 1.diff
343 diff --git a/.hgsub b/.hgsub
333 diff --git a/.hgsub b/.hgsub
344 1 hunks, 1 lines changed
334 1 hunks, 1 lines changed
345 examine changes to '.hgsub'? [Ynesfdaq?] y
335 examine changes to '.hgsub'? [Ynesfdaq?] y
346
336
347 @@ -1,1 +1,2 @@
337 @@ -1,1 +1,2 @@
348 sub = sub
338 sub = sub
349 +sub2 = sub2
339 +sub2 = sub2
350 record this change to '.hgsub'? [Ynesfdaq?] y
340 record this change to '.hgsub'? [Ynesfdaq?] y
351
341
352 path sub
342 path sub
353 source sub
343 source sub
354 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
344 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
355 path sub2
345 path sub2
356 source sub2
346 source sub2
357 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
347 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
358
348
359 $ hg qpop -qa
349 $ hg qpop -qa
360 patch queue now empty
350 patch queue now empty
361 $ testrm1 qrecord --config ui.interactive=1 -m2 2.diff <<EOF
351 $ testrm1 qrecord --config ui.interactive=1 -m2 2.diff <<EOF
362 > y
352 > y
363 > y
353 > y
364 > EOF
354 > EOF
365 adding a
355 adding a
366 % update substate when removing .hgsub w/dirty subrepo
356 % update substate when removing .hgsub w/dirty subrepo
367 M sub3/a
357 M sub3/a
368 R .hgsub
358 R .hgsub
369 % qrecord --config ui.interactive=1 -m2 2.diff
359 % qrecord --config ui.interactive=1 -m2 2.diff
370 diff --git a/.hgsub b/.hgsub
360 diff --git a/.hgsub b/.hgsub
371 deleted file mode 100644
361 deleted file mode 100644
372 examine changes to '.hgsub'? [Ynesfdaq?] y
362 examine changes to '.hgsub'? [Ynesfdaq?] y
373
363
374 % debugsub should be empty
364 % debugsub should be empty
375
365
376 $ hg qpop -qa
366 $ hg qpop -qa
377 patch queue now empty
367 patch queue now empty
378 $ testrm2 qrecord --config ui.interactive=1 -m3 3.diff <<EOF
368 $ testrm2 qrecord --config ui.interactive=1 -m3 3.diff <<EOF
379 > y
369 > y
380 > y
370 > y
381 > EOF
371 > EOF
382 adding a
372 adding a
383 % update substate when removing .hgsub w/clean updated subrepo
373 % update substate when removing .hgsub w/clean updated subrepo
384 R .hgsub
374 R .hgsub
385 % qrecord --config ui.interactive=1 -m3 3.diff
375 % qrecord --config ui.interactive=1 -m3 3.diff
386 diff --git a/.hgsub b/.hgsub
376 diff --git a/.hgsub b/.hgsub
387 deleted file mode 100644
377 deleted file mode 100644
388 examine changes to '.hgsub'? [Ynesfdaq?] y
378 examine changes to '.hgsub'? [Ynesfdaq?] y
389
379
390 % debugsub should be empty
380 % debugsub should be empty
391
381
392 $ cd ..
382 $ cd ..
393
383
394
384
395 correctly handle subrepos with patch queues
385 correctly handle subrepos with patch queues
396 $ mkrepo repo-subrepo-with-queue
386 $ mkrepo repo-subrepo-with-queue
397 $ mksubrepo sub
387 $ mksubrepo sub
398 adding a
388 adding a
399 $ hg -R sub qnew sub0.diff
389 $ hg -R sub qnew sub0.diff
400 $ echo sub = sub >> .hgsub
390 $ echo sub = sub >> .hgsub
401 $ hg add .hgsub
391 $ hg add .hgsub
402 $ hg qnew 0.diff
392 $ hg qnew 0.diff
403
393
404 $ cd ..
394 $ cd ..
405
395
406 check whether MQ operations can import updated .hgsubstate correctly
396 check whether MQ operations can import updated .hgsubstate correctly
407 both into 'revision' and 'patch file under .hg/patches':
397 both into 'revision' and 'patch file under .hg/patches':
408
398
409 $ hg init importing-hgsubstate
399 $ hg init importing-hgsubstate
410 $ cd importing-hgsubstate
400 $ cd importing-hgsubstate
411
401
412 $ echo a > a
402 $ echo a > a
413 $ hg commit -u test -d '0 0' -Am '#0 in parent'
403 $ hg commit -u test -d '0 0' -Am '#0 in parent'
414 adding a
404 adding a
415 $ hg init sub
405 $ hg init sub
416 $ echo sa > sub/sa
406 $ echo sa > sub/sa
417 $ hg -R sub commit -u test -d '0 0' -Am '#0 in sub'
407 $ hg -R sub commit -u test -d '0 0' -Am '#0 in sub'
418 adding sa
408 adding sa
419 $ echo 'sub = sub' > .hgsub
409 $ echo 'sub = sub' > .hgsub
420 $ touch .hgsubstate
410 $ touch .hgsubstate
421 $ hg add .hgsub .hgsubstate
411 $ hg add .hgsub .hgsubstate
422
412
423 $ hg qnew -u test -d '0 0' import-at-qnew
413 $ hg qnew -u test -d '0 0' import-at-qnew
424 $ hg -R sub parents --template '{node} sub\n'
414 $ hg -R sub parents --template '{node} sub\n'
425 b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
415 b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
426 $ cat .hgsubstate
416 $ cat .hgsubstate
427 b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
417 b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
428 $ hg diff -c tip
418 $ hg diff -c tip
429 diff -r f499373e340c -r f69e96d86e75 .hgsub
419 diff -r f499373e340c -r f69e96d86e75 .hgsub
430 --- /dev/null
420 --- /dev/null
431 +++ b/.hgsub
421 +++ b/.hgsub
432 @@ -0,0 +1,1 @@
422 @@ -0,0 +1,1 @@
433 +sub = sub
423 +sub = sub
434 diff -r f499373e340c -r f69e96d86e75 .hgsubstate
424 diff -r f499373e340c -r f69e96d86e75 .hgsubstate
435 --- /dev/null
425 --- /dev/null
436 +++ b/.hgsubstate
426 +++ b/.hgsubstate
437 @@ -0,0 +1,1 @@
427 @@ -0,0 +1,1 @@
438 +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
428 +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
439 $ cat .hg/patches/import-at-qnew
429 $ cat .hg/patches/import-at-qnew
440 # HG changeset patch
430 # HG changeset patch
441 # User test
431 # User test
442 # Date 0 0
432 # Date 0 0
443 # Parent f499373e340cdca5d01dee904aeb42dd2a325e71
433 # Parent f499373e340cdca5d01dee904aeb42dd2a325e71
444
434
445 diff -r f499373e340c -r f69e96d86e75 .hgsub
435 diff -r f499373e340c -r f69e96d86e75 .hgsub
446 --- /dev/null
436 --- /dev/null
447 +++ b/.hgsub
437 +++ b/.hgsub
448 @@ -0,0 +1,1 @@
438 @@ -0,0 +1,1 @@
449 +sub = sub
439 +sub = sub
450 diff -r f499373e340c -r f69e96d86e75 .hgsubstate
440 diff -r f499373e340c -r f69e96d86e75 .hgsubstate
451 --- /dev/null
441 --- /dev/null
452 +++ b/.hgsubstate
442 +++ b/.hgsubstate
453 @@ -0,0 +1,1 @@
443 @@ -0,0 +1,1 @@
454 +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
444 +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
455 $ hg parents --template '{node}\n'
445 $ hg parents --template '{node}\n'
456 f69e96d86e75a6d4fd88285dc9697acb23951041
446 f69e96d86e75a6d4fd88285dc9697acb23951041
457 $ hg parents --template '{files}\n'
447 $ hg parents --template '{files}\n'
458 .hgsub .hgsubstate
448 .hgsub .hgsubstate
459
449
460 check also whether qnew not including ".hgsubstate" explicitly causes
450 check also whether qnew not including ".hgsubstate" explicitly causes
461 as same result (in node hash) as one including it.
451 as same result (in node hash) as one including it.
462
452
463 $ hg qpop -a -q
453 $ hg qpop -a -q
464 patch queue now empty
454 patch queue now empty
465 $ hg qdelete import-at-qnew
455 $ hg qdelete import-at-qnew
466 $ echo 'sub = sub' > .hgsub
456 $ echo 'sub = sub' > .hgsub
467 $ hg add .hgsub
457 $ hg add .hgsub
468 $ rm -f .hgsubstate
458 $ rm -f .hgsubstate
469 $ hg qnew -u test -d '0 0' import-at-qnew
459 $ hg qnew -u test -d '0 0' import-at-qnew
470 $ hg parents --template '{node}\n'
460 $ hg parents --template '{node}\n'
471 f69e96d86e75a6d4fd88285dc9697acb23951041
461 f69e96d86e75a6d4fd88285dc9697acb23951041
472 $ hg parents --template '{files}\n'
462 $ hg parents --template '{files}\n'
473 .hgsub .hgsubstate
463 .hgsub .hgsubstate
474
464
475 check whether qrefresh imports updated .hgsubstate correctly
465 check whether qrefresh imports updated .hgsubstate correctly
476
466
477 $ hg qpop
467 $ hg qpop
478 popping import-at-qnew
468 popping import-at-qnew
479 patch queue now empty
469 patch queue now empty
480 $ hg qpush
470 $ hg qpush
481 applying import-at-qnew
471 applying import-at-qnew
482 now at: import-at-qnew
472 now at: import-at-qnew
483 $ hg parents --template '{files}\n'
473 $ hg parents --template '{files}\n'
484 .hgsub .hgsubstate
474 .hgsub .hgsubstate
485
475
486 $ hg qnew import-at-qrefresh
476 $ hg qnew import-at-qrefresh
487 $ echo sb > sub/sb
477 $ echo sb > sub/sb
488 $ hg -R sub commit -u test -d '0 0' -Am '#1 in sub'
478 $ hg -R sub commit -u test -d '0 0' -Am '#1 in sub'
489 adding sb
479 adding sb
490 $ hg qrefresh -u test -d '0 0'
480 $ hg qrefresh -u test -d '0 0'
491 $ hg -R sub parents --template '{node} sub\n'
481 $ hg -R sub parents --template '{node} sub\n'
492 88ac1bef5ed43b689d1d200b59886b675dec474b sub
482 88ac1bef5ed43b689d1d200b59886b675dec474b sub
493 $ cat .hgsubstate
483 $ cat .hgsubstate
494 88ac1bef5ed43b689d1d200b59886b675dec474b sub
484 88ac1bef5ed43b689d1d200b59886b675dec474b sub
495 $ hg diff -c tip
485 $ hg diff -c tip
496 diff -r 05b056bb9c8c -r d987bec230f4 .hgsubstate
486 diff -r 05b056bb9c8c -r d987bec230f4 .hgsubstate
497 --- a/.hgsubstate
487 --- a/.hgsubstate
498 +++ b/.hgsubstate
488 +++ b/.hgsubstate
499 @@ -1,1 +1,1 @@
489 @@ -1,1 +1,1 @@
500 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
490 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
501 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
491 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
502 $ cat .hg/patches/import-at-qrefresh
492 $ cat .hg/patches/import-at-qrefresh
503 # HG changeset patch
493 # HG changeset patch
504 # User test
494 # User test
505 # Date 0 0
495 # Date 0 0
506 # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
496 # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
507
497
508 diff -r 05b056bb9c8c .hgsubstate
498 diff -r 05b056bb9c8c .hgsubstate
509 --- a/.hgsubstate
499 --- a/.hgsubstate
510 +++ b/.hgsubstate
500 +++ b/.hgsubstate
511 @@ -1,1 +1,1 @@
501 @@ -1,1 +1,1 @@
512 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
502 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
513 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
503 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
514 $ hg parents --template '{files}\n'
504 $ hg parents --template '{files}\n'
515 .hgsubstate
505 .hgsubstate
516
506
517 $ hg qrefresh -u test -d '0 0'
507 $ hg qrefresh -u test -d '0 0'
518 $ cat .hgsubstate
508 $ cat .hgsubstate
519 88ac1bef5ed43b689d1d200b59886b675dec474b sub
509 88ac1bef5ed43b689d1d200b59886b675dec474b sub
520 $ hg diff -c tip
510 $ hg diff -c tip
521 diff -r 05b056bb9c8c -r d987bec230f4 .hgsubstate
511 diff -r 05b056bb9c8c -r d987bec230f4 .hgsubstate
522 --- a/.hgsubstate
512 --- a/.hgsubstate
523 +++ b/.hgsubstate
513 +++ b/.hgsubstate
524 @@ -1,1 +1,1 @@
514 @@ -1,1 +1,1 @@
525 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
515 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
526 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
516 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
527 $ cat .hg/patches/import-at-qrefresh
517 $ cat .hg/patches/import-at-qrefresh
528 # HG changeset patch
518 # HG changeset patch
529 # User test
519 # User test
530 # Date 0 0
520 # Date 0 0
531 # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
521 # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
532
522
533 diff -r 05b056bb9c8c .hgsubstate
523 diff -r 05b056bb9c8c .hgsubstate
534 --- a/.hgsubstate
524 --- a/.hgsubstate
535 +++ b/.hgsubstate
525 +++ b/.hgsubstate
536 @@ -1,1 +1,1 @@
526 @@ -1,1 +1,1 @@
537 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
527 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
538 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
528 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
539 $ hg parents --template '{files}\n'
529 $ hg parents --template '{files}\n'
540 .hgsubstate
530 .hgsubstate
541
531
542 $ hg update -C tip
532 $ hg update -C tip
543 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
533 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
544 $ hg qpop -a
534 $ hg qpop -a
545 popping import-at-qrefresh
535 popping import-at-qrefresh
546 popping import-at-qnew
536 popping import-at-qnew
547 patch queue now empty
537 patch queue now empty
548
538
549 $ hg -R sub update -C 0
539 $ hg -R sub update -C 0
550 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
540 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
551 $ echo 'sub = sub' > .hgsub
541 $ echo 'sub = sub' > .hgsub
552 $ hg commit -Am '#1 in parent'
542 $ hg commit -Am '#1 in parent'
553 adding .hgsub
543 adding .hgsub
554 $ hg -R sub update -C 1
544 $ hg -R sub update -C 1
555 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
545 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
556 $ hg commit -Am '#2 in parent (but will be rolled back soon)'
546 $ hg commit -Am '#2 in parent (but will be rolled back soon)'
557 $ hg rollback
547 $ hg rollback
558 repository tip rolled back to revision 1 (undo commit)
548 repository tip rolled back to revision 1 (undo commit)
559 working directory now based on revision 1
549 working directory now based on revision 1
560 $ hg status
550 $ hg status
561 M .hgsubstate
551 M .hgsubstate
562 $ hg qnew -u test -d '0 0' checkstate-at-qnew
552 $ hg qnew -u test -d '0 0' checkstate-at-qnew
563 $ hg -R sub parents --template '{node} sub\n'
553 $ hg -R sub parents --template '{node} sub\n'
564 88ac1bef5ed43b689d1d200b59886b675dec474b sub
554 88ac1bef5ed43b689d1d200b59886b675dec474b sub
565 $ cat .hgsubstate
555 $ cat .hgsubstate
566 88ac1bef5ed43b689d1d200b59886b675dec474b sub
556 88ac1bef5ed43b689d1d200b59886b675dec474b sub
567 $ hg diff -c tip
557 $ hg diff -c tip
568 diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
558 diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
569 --- a/.hgsubstate
559 --- a/.hgsubstate
570 +++ b/.hgsubstate
560 +++ b/.hgsubstate
571 @@ -1,1 +1,1 @@
561 @@ -1,1 +1,1 @@
572 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
562 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
573 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
563 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
574 $ cat .hg/patches/checkstate-at-qnew
564 $ cat .hg/patches/checkstate-at-qnew
575 # HG changeset patch
565 # HG changeset patch
576 # User test
566 # User test
577 # Date 0 0
567 # Date 0 0
578 # Parent 4d91eb2fa1d1b22ec513347b9cd06f6b49d470fa
568 # Parent 4d91eb2fa1d1b22ec513347b9cd06f6b49d470fa
579
569
580 diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
570 diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
581 --- a/.hgsubstate
571 --- a/.hgsubstate
582 +++ b/.hgsubstate
572 +++ b/.hgsubstate
583 @@ -1,1 +1,1 @@
573 @@ -1,1 +1,1 @@
584 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
574 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
585 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
575 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
586 $ hg parents --template '{files}\n'
576 $ hg parents --template '{files}\n'
587 .hgsubstate
577 .hgsubstate
588
578
589 check whether qrefresh not including ".hgsubstate" explicitly causes
579 check whether qrefresh not including ".hgsubstate" explicitly causes
590 as same result (in node hash) as one including it.
580 as same result (in node hash) as one including it.
591
581
592 $ hg update -C -q 0
582 $ hg update -C -q 0
593 $ hg qpop -a -q
583 $ hg qpop -a -q
594 patch queue now empty
584 patch queue now empty
595 $ hg qnew -u test -d '0 0' add-hgsub-at-qrefresh
585 $ hg qnew -u test -d '0 0' add-hgsub-at-qrefresh
596 $ echo 'sub = sub' > .hgsub
586 $ echo 'sub = sub' > .hgsub
597 $ echo > .hgsubstate
587 $ echo > .hgsubstate
598 $ hg add .hgsub .hgsubstate
588 $ hg add .hgsub .hgsubstate
599 $ hg qrefresh -u test -d '0 0'
589 $ hg qrefresh -u test -d '0 0'
600 $ hg parents --template '{node}\n'
590 $ hg parents --template '{node}\n'
601 7c48c35501aae6770ed9c2517014628615821a8e
591 7c48c35501aae6770ed9c2517014628615821a8e
602 $ hg parents --template '{files}\n'
592 $ hg parents --template '{files}\n'
603 .hgsub .hgsubstate
593 .hgsub .hgsubstate
604
594
605 $ hg qpop -a -q
595 $ hg qpop -a -q
606 patch queue now empty
596 patch queue now empty
607 $ hg qdelete add-hgsub-at-qrefresh
597 $ hg qdelete add-hgsub-at-qrefresh
608 $ hg qnew -u test -d '0 0' add-hgsub-at-qrefresh
598 $ hg qnew -u test -d '0 0' add-hgsub-at-qrefresh
609 $ echo 'sub = sub' > .hgsub
599 $ echo 'sub = sub' > .hgsub
610 $ hg add .hgsub
600 $ hg add .hgsub
611 $ rm -f .hgsubstate
601 $ rm -f .hgsubstate
612 $ hg qrefresh -u test -d '0 0'
602 $ hg qrefresh -u test -d '0 0'
613 $ hg parents --template '{node}\n'
603 $ hg parents --template '{node}\n'
614 7c48c35501aae6770ed9c2517014628615821a8e
604 7c48c35501aae6770ed9c2517014628615821a8e
615 $ hg parents --template '{files}\n'
605 $ hg parents --template '{files}\n'
616 .hgsub .hgsubstate
606 .hgsub .hgsubstate
617
607
618 $ cd ..
608 $ cd ..
619
609
620 $ cd ..
610 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now