##// END OF EJS Templates
record: edit patch of newly added files (issue4304)...
Laurent Charignon -
r24845:8133494a stable
parent child Browse files
Show More
@@ -1,3247 +1,3258 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)
62
64
63 if not ui.interactive():
65 if not ui.interactive():
64 raise util.Abort(_('running non-interactively, use %s instead') %
66 raise util.Abort(_('running non-interactively, use %s instead') %
65 cmdsuggest)
67 cmdsuggest)
66
68
67 # make sure username is set before going interactive
69 # make sure username is set before going interactive
68 if not opts.get('user'):
70 if not opts.get('user'):
69 ui.username() # raise exception, username not provided
71 ui.username() # raise exception, username not provided
70
72
71 def recordfunc(ui, repo, message, match, opts):
73 def recordfunc(ui, repo, message, match, opts):
72 """This is generic record driver.
74 """This is generic record driver.
73
75
74 Its job is to interactively filter local changes, and
76 Its job is to interactively filter local changes, and
75 accordingly prepare working directory into a state in which the
77 accordingly prepare working directory into a state in which the
76 job can be delegated to a non-interactive commit command such as
78 job can be delegated to a non-interactive commit command such as
77 'commit' or 'qrefresh'.
79 'commit' or 'qrefresh'.
78
80
79 After the actual job is done by non-interactive command, the
81 After the actual job is done by non-interactive command, the
80 working directory is restored to its original state.
82 working directory is restored to its original state.
81
83
82 In the end we'll record interesting changes, and everything else
84 In the end we'll record interesting changes, and everything else
83 will be left in place, so the user can continue working.
85 will be left in place, so the user can continue working.
84 """
86 """
85
87
86 checkunfinished(repo, commit=True)
88 checkunfinished(repo, commit=True)
87 merge = len(repo[None].parents()) > 1
89 merge = len(repo[None].parents()) > 1
88 if merge:
90 if merge:
89 raise util.Abort(_('cannot partially commit a merge '
91 raise util.Abort(_('cannot partially commit a merge '
90 '(use "hg commit" instead)'))
92 '(use "hg commit" instead)'))
91
93
92 status = repo.status(match=match)
94 status = repo.status(match=match)
93 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
95 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
94 diffopts.nodates = True
96 diffopts.nodates = True
95 diffopts.git = True
97 diffopts.git = True
96 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
98 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
97 originalchunks = patch.parsepatch(originaldiff)
99 originalchunks = patch.parsepatch(originaldiff)
98
100
99 # 1. filter patch, so we have intending-to apply subset of it
101 # 1. filter patch, so we have intending-to apply subset of it
100 try:
102 try:
101 chunks = filterfn(ui, originalchunks)
103 chunks = filterfn(ui, originalchunks)
102 except patch.PatchError, err:
104 except patch.PatchError, err:
103 raise util.Abort(_('error parsing patch: %s') % err)
105 raise util.Abort(_('error parsing patch: %s') % err)
104
106
107 # We need to keep a backup of files that have been newly added and
108 # modified during the recording process because there is a previous
109 # version without the edit in the workdir
110 newlyaddedandmodifiedfiles = set()
111 for chunk in chunks:
112 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
113 originalchunks:
114 newlyaddedandmodifiedfiles.add(chunk.header.filename())
105 contenders = set()
115 contenders = set()
106 for h in chunks:
116 for h in chunks:
107 try:
117 try:
108 contenders.update(set(h.files()))
118 contenders.update(set(h.files()))
109 except AttributeError:
119 except AttributeError:
110 pass
120 pass
111
121
112 changed = status.modified + status.added + status.removed
122 changed = status.modified + status.added + status.removed
113 newfiles = [f for f in changed if f in contenders]
123 newfiles = [f for f in changed if f in contenders]
114 if not newfiles:
124 if not newfiles:
115 ui.status(_('no changes to record\n'))
125 ui.status(_('no changes to record\n'))
116 return 0
126 return 0
117
127
118 modified = set(status.modified)
128 modified = set(status.modified)
119
129
120 # 2. backup changed files, so we can restore them in the end
130 # 2. backup changed files, so we can restore them in the end
121
131
122 if backupall:
132 if backupall:
123 tobackup = changed
133 tobackup = changed
124 else:
134 else:
125 tobackup = [f for f in newfiles if f in modified]
135 tobackup = [f for f in newfiles if f in modified or f in \
126
136 newlyaddedandmodifiedfiles]
127 backups = {}
137 backups = {}
128 if tobackup:
138 if tobackup:
129 backupdir = repo.join('record-backups')
139 backupdir = repo.join('record-backups')
130 try:
140 try:
131 os.mkdir(backupdir)
141 os.mkdir(backupdir)
132 except OSError, err:
142 except OSError, err:
133 if err.errno != errno.EEXIST:
143 if err.errno != errno.EEXIST:
134 raise
144 raise
135 try:
145 try:
136 # backup continues
146 # backup continues
137 for f in tobackup:
147 for f in tobackup:
138 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
148 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
139 dir=backupdir)
149 dir=backupdir)
140 os.close(fd)
150 os.close(fd)
141 ui.debug('backup %r as %r\n' % (f, tmpname))
151 ui.debug('backup %r as %r\n' % (f, tmpname))
142 util.copyfile(repo.wjoin(f), tmpname)
152 util.copyfile(repo.wjoin(f), tmpname)
143 shutil.copystat(repo.wjoin(f), tmpname)
153 shutil.copystat(repo.wjoin(f), tmpname)
144 backups[f] = tmpname
154 backups[f] = tmpname
145
155
146 fp = cStringIO.StringIO()
156 fp = cStringIO.StringIO()
147 for c in chunks:
157 for c in chunks:
148 fname = c.filename()
158 fname = c.filename()
149 if fname in backups:
159 if fname in backups:
150 c.write(fp)
160 c.write(fp)
151 dopatch = fp.tell()
161 dopatch = fp.tell()
152 fp.seek(0)
162 fp.seek(0)
153
163
164 [os.unlink(c) for c in newlyaddedandmodifiedfiles]
154 # 3a. apply filtered patch to clean repo (clean)
165 # 3a. apply filtered patch to clean repo (clean)
155 if backups:
166 if backups:
156 # Equivalent to hg.revert
167 # Equivalent to hg.revert
157 choices = lambda key: key in backups
168 choices = lambda key: key in backups
158 mergemod.update(repo, repo.dirstate.p1(),
169 mergemod.update(repo, repo.dirstate.p1(),
159 False, True, choices)
170 False, True, choices)
160
171
161 # 3b. (apply)
172 # 3b. (apply)
162 if dopatch:
173 if dopatch:
163 try:
174 try:
164 ui.debug('applying patch\n')
175 ui.debug('applying patch\n')
165 ui.debug(fp.getvalue())
176 ui.debug(fp.getvalue())
166 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
177 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
167 except patch.PatchError, err:
178 except patch.PatchError, err:
168 raise util.Abort(str(err))
179 raise util.Abort(str(err))
169 del fp
180 del fp
170
181
171 # 4. We prepared working directory according to filtered
182 # 4. We prepared working directory according to filtered
172 # patch. Now is the time to delegate the job to
183 # patch. Now is the time to delegate the job to
173 # commit/qrefresh or the like!
184 # commit/qrefresh or the like!
174
185
175 # Make all of the pathnames absolute.
186 # Make all of the pathnames absolute.
176 newfiles = [repo.wjoin(nf) for nf in newfiles]
187 newfiles = [repo.wjoin(nf) for nf in newfiles]
177 return commitfunc(ui, repo, *newfiles, **opts)
188 return commitfunc(ui, repo, *newfiles, **opts)
178 finally:
189 finally:
179 # 5. finally restore backed-up files
190 # 5. finally restore backed-up files
180 try:
191 try:
181 for realname, tmpname in backups.iteritems():
192 for realname, tmpname in backups.iteritems():
182 ui.debug('restoring %r to %r\n' % (tmpname, realname))
193 ui.debug('restoring %r to %r\n' % (tmpname, realname))
183 util.copyfile(tmpname, repo.wjoin(realname))
194 util.copyfile(tmpname, repo.wjoin(realname))
184 # Our calls to copystat() here and above are a
195 # Our calls to copystat() here and above are a
185 # hack to trick any editors that have f open that
196 # hack to trick any editors that have f open that
186 # we haven't modified them.
197 # we haven't modified them.
187 #
198 #
188 # Also note that this racy as an editor could
199 # Also note that this racy as an editor could
189 # notice the file's mtime before we've finished
200 # notice the file's mtime before we've finished
190 # writing it.
201 # writing it.
191 shutil.copystat(tmpname, repo.wjoin(realname))
202 shutil.copystat(tmpname, repo.wjoin(realname))
192 os.unlink(tmpname)
203 os.unlink(tmpname)
193 if tobackup:
204 if tobackup:
194 os.rmdir(backupdir)
205 os.rmdir(backupdir)
195 except OSError:
206 except OSError:
196 pass
207 pass
197
208
198 return commit(ui, repo, recordfunc, pats, opts)
209 return commit(ui, repo, recordfunc, pats, opts)
199
210
200 def findpossible(cmd, table, strict=False):
211 def findpossible(cmd, table, strict=False):
201 """
212 """
202 Return cmd -> (aliases, command table entry)
213 Return cmd -> (aliases, command table entry)
203 for each matching command.
214 for each matching command.
204 Return debug commands (or their aliases) only if no normal command matches.
215 Return debug commands (or their aliases) only if no normal command matches.
205 """
216 """
206 choice = {}
217 choice = {}
207 debugchoice = {}
218 debugchoice = {}
208
219
209 if cmd in table:
220 if cmd in table:
210 # short-circuit exact matches, "log" alias beats "^log|history"
221 # short-circuit exact matches, "log" alias beats "^log|history"
211 keys = [cmd]
222 keys = [cmd]
212 else:
223 else:
213 keys = table.keys()
224 keys = table.keys()
214
225
215 allcmds = []
226 allcmds = []
216 for e in keys:
227 for e in keys:
217 aliases = parsealiases(e)
228 aliases = parsealiases(e)
218 allcmds.extend(aliases)
229 allcmds.extend(aliases)
219 found = None
230 found = None
220 if cmd in aliases:
231 if cmd in aliases:
221 found = cmd
232 found = cmd
222 elif not strict:
233 elif not strict:
223 for a in aliases:
234 for a in aliases:
224 if a.startswith(cmd):
235 if a.startswith(cmd):
225 found = a
236 found = a
226 break
237 break
227 if found is not None:
238 if found is not None:
228 if aliases[0].startswith("debug") or found.startswith("debug"):
239 if aliases[0].startswith("debug") or found.startswith("debug"):
229 debugchoice[found] = (aliases, table[e])
240 debugchoice[found] = (aliases, table[e])
230 else:
241 else:
231 choice[found] = (aliases, table[e])
242 choice[found] = (aliases, table[e])
232
243
233 if not choice and debugchoice:
244 if not choice and debugchoice:
234 choice = debugchoice
245 choice = debugchoice
235
246
236 return choice, allcmds
247 return choice, allcmds
237
248
238 def findcmd(cmd, table, strict=True):
249 def findcmd(cmd, table, strict=True):
239 """Return (aliases, command table entry) for command string."""
250 """Return (aliases, command table entry) for command string."""
240 choice, allcmds = findpossible(cmd, table, strict)
251 choice, allcmds = findpossible(cmd, table, strict)
241
252
242 if cmd in choice:
253 if cmd in choice:
243 return choice[cmd]
254 return choice[cmd]
244
255
245 if len(choice) > 1:
256 if len(choice) > 1:
246 clist = choice.keys()
257 clist = choice.keys()
247 clist.sort()
258 clist.sort()
248 raise error.AmbiguousCommand(cmd, clist)
259 raise error.AmbiguousCommand(cmd, clist)
249
260
250 if choice:
261 if choice:
251 return choice.values()[0]
262 return choice.values()[0]
252
263
253 raise error.UnknownCommand(cmd, allcmds)
264 raise error.UnknownCommand(cmd, allcmds)
254
265
255 def findrepo(p):
266 def findrepo(p):
256 while not os.path.isdir(os.path.join(p, ".hg")):
267 while not os.path.isdir(os.path.join(p, ".hg")):
257 oldp, p = p, os.path.dirname(p)
268 oldp, p = p, os.path.dirname(p)
258 if p == oldp:
269 if p == oldp:
259 return None
270 return None
260
271
261 return p
272 return p
262
273
263 def bailifchanged(repo, merge=True):
274 def bailifchanged(repo, merge=True):
264 if merge and repo.dirstate.p2() != nullid:
275 if merge and repo.dirstate.p2() != nullid:
265 raise util.Abort(_('outstanding uncommitted merge'))
276 raise util.Abort(_('outstanding uncommitted merge'))
266 modified, added, removed, deleted = repo.status()[:4]
277 modified, added, removed, deleted = repo.status()[:4]
267 if modified or added or removed or deleted:
278 if modified or added or removed or deleted:
268 raise util.Abort(_('uncommitted changes'))
279 raise util.Abort(_('uncommitted changes'))
269 ctx = repo[None]
280 ctx = repo[None]
270 for s in sorted(ctx.substate):
281 for s in sorted(ctx.substate):
271 ctx.sub(s).bailifchanged()
282 ctx.sub(s).bailifchanged()
272
283
273 def logmessage(ui, opts):
284 def logmessage(ui, opts):
274 """ get the log message according to -m and -l option """
285 """ get the log message according to -m and -l option """
275 message = opts.get('message')
286 message = opts.get('message')
276 logfile = opts.get('logfile')
287 logfile = opts.get('logfile')
277
288
278 if message and logfile:
289 if message and logfile:
279 raise util.Abort(_('options --message and --logfile are mutually '
290 raise util.Abort(_('options --message and --logfile are mutually '
280 'exclusive'))
291 'exclusive'))
281 if not message and logfile:
292 if not message and logfile:
282 try:
293 try:
283 if logfile == '-':
294 if logfile == '-':
284 message = ui.fin.read()
295 message = ui.fin.read()
285 else:
296 else:
286 message = '\n'.join(util.readfile(logfile).splitlines())
297 message = '\n'.join(util.readfile(logfile).splitlines())
287 except IOError, inst:
298 except IOError, inst:
288 raise util.Abort(_("can't read commit message '%s': %s") %
299 raise util.Abort(_("can't read commit message '%s': %s") %
289 (logfile, inst.strerror))
300 (logfile, inst.strerror))
290 return message
301 return message
291
302
292 def mergeeditform(ctxorbool, baseformname):
303 def mergeeditform(ctxorbool, baseformname):
293 """return appropriate editform name (referencing a committemplate)
304 """return appropriate editform name (referencing a committemplate)
294
305
295 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
306 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
296 merging is committed.
307 merging is committed.
297
308
298 This returns baseformname with '.merge' appended if it is a merge,
309 This returns baseformname with '.merge' appended if it is a merge,
299 otherwise '.normal' is appended.
310 otherwise '.normal' is appended.
300 """
311 """
301 if isinstance(ctxorbool, bool):
312 if isinstance(ctxorbool, bool):
302 if ctxorbool:
313 if ctxorbool:
303 return baseformname + ".merge"
314 return baseformname + ".merge"
304 elif 1 < len(ctxorbool.parents()):
315 elif 1 < len(ctxorbool.parents()):
305 return baseformname + ".merge"
316 return baseformname + ".merge"
306
317
307 return baseformname + ".normal"
318 return baseformname + ".normal"
308
319
309 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
320 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
310 editform='', **opts):
321 editform='', **opts):
311 """get appropriate commit message editor according to '--edit' option
322 """get appropriate commit message editor according to '--edit' option
312
323
313 'finishdesc' is a function to be called with edited commit message
324 'finishdesc' is a function to be called with edited commit message
314 (= 'description' of the new changeset) just after editing, but
325 (= 'description' of the new changeset) just after editing, but
315 before checking empty-ness. It should return actual text to be
326 before checking empty-ness. It should return actual text to be
316 stored into history. This allows to change description before
327 stored into history. This allows to change description before
317 storing.
328 storing.
318
329
319 'extramsg' is a extra message to be shown in the editor instead of
330 'extramsg' is a extra message to be shown in the editor instead of
320 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
331 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
321 is automatically added.
332 is automatically added.
322
333
323 'editform' is a dot-separated list of names, to distinguish
334 'editform' is a dot-separated list of names, to distinguish
324 the purpose of commit text editing.
335 the purpose of commit text editing.
325
336
326 'getcommiteditor' returns 'commitforceeditor' regardless of
337 'getcommiteditor' returns 'commitforceeditor' regardless of
327 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
338 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
328 they are specific for usage in MQ.
339 they are specific for usage in MQ.
329 """
340 """
330 if edit or finishdesc or extramsg:
341 if edit or finishdesc or extramsg:
331 return lambda r, c, s: commitforceeditor(r, c, s,
342 return lambda r, c, s: commitforceeditor(r, c, s,
332 finishdesc=finishdesc,
343 finishdesc=finishdesc,
333 extramsg=extramsg,
344 extramsg=extramsg,
334 editform=editform)
345 editform=editform)
335 elif editform:
346 elif editform:
336 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
347 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
337 else:
348 else:
338 return commiteditor
349 return commiteditor
339
350
340 def loglimit(opts):
351 def loglimit(opts):
341 """get the log limit according to option -l/--limit"""
352 """get the log limit according to option -l/--limit"""
342 limit = opts.get('limit')
353 limit = opts.get('limit')
343 if limit:
354 if limit:
344 try:
355 try:
345 limit = int(limit)
356 limit = int(limit)
346 except ValueError:
357 except ValueError:
347 raise util.Abort(_('limit must be a positive integer'))
358 raise util.Abort(_('limit must be a positive integer'))
348 if limit <= 0:
359 if limit <= 0:
349 raise util.Abort(_('limit must be positive'))
360 raise util.Abort(_('limit must be positive'))
350 else:
361 else:
351 limit = None
362 limit = None
352 return limit
363 return limit
353
364
354 def makefilename(repo, pat, node, desc=None,
365 def makefilename(repo, pat, node, desc=None,
355 total=None, seqno=None, revwidth=None, pathname=None):
366 total=None, seqno=None, revwidth=None, pathname=None):
356 node_expander = {
367 node_expander = {
357 'H': lambda: hex(node),
368 'H': lambda: hex(node),
358 'R': lambda: str(repo.changelog.rev(node)),
369 'R': lambda: str(repo.changelog.rev(node)),
359 'h': lambda: short(node),
370 'h': lambda: short(node),
360 'm': lambda: re.sub('[^\w]', '_', str(desc))
371 'm': lambda: re.sub('[^\w]', '_', str(desc))
361 }
372 }
362 expander = {
373 expander = {
363 '%': lambda: '%',
374 '%': lambda: '%',
364 'b': lambda: os.path.basename(repo.root),
375 'b': lambda: os.path.basename(repo.root),
365 }
376 }
366
377
367 try:
378 try:
368 if node:
379 if node:
369 expander.update(node_expander)
380 expander.update(node_expander)
370 if node:
381 if node:
371 expander['r'] = (lambda:
382 expander['r'] = (lambda:
372 str(repo.changelog.rev(node)).zfill(revwidth or 0))
383 str(repo.changelog.rev(node)).zfill(revwidth or 0))
373 if total is not None:
384 if total is not None:
374 expander['N'] = lambda: str(total)
385 expander['N'] = lambda: str(total)
375 if seqno is not None:
386 if seqno is not None:
376 expander['n'] = lambda: str(seqno)
387 expander['n'] = lambda: str(seqno)
377 if total is not None and seqno is not None:
388 if total is not None and seqno is not None:
378 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
389 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
379 if pathname is not None:
390 if pathname is not None:
380 expander['s'] = lambda: os.path.basename(pathname)
391 expander['s'] = lambda: os.path.basename(pathname)
381 expander['d'] = lambda: os.path.dirname(pathname) or '.'
392 expander['d'] = lambda: os.path.dirname(pathname) or '.'
382 expander['p'] = lambda: pathname
393 expander['p'] = lambda: pathname
383
394
384 newname = []
395 newname = []
385 patlen = len(pat)
396 patlen = len(pat)
386 i = 0
397 i = 0
387 while i < patlen:
398 while i < patlen:
388 c = pat[i]
399 c = pat[i]
389 if c == '%':
400 if c == '%':
390 i += 1
401 i += 1
391 c = pat[i]
402 c = pat[i]
392 c = expander[c]()
403 c = expander[c]()
393 newname.append(c)
404 newname.append(c)
394 i += 1
405 i += 1
395 return ''.join(newname)
406 return ''.join(newname)
396 except KeyError, inst:
407 except KeyError, inst:
397 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
408 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
398 inst.args[0])
409 inst.args[0])
399
410
400 def makefileobj(repo, pat, node=None, desc=None, total=None,
411 def makefileobj(repo, pat, node=None, desc=None, total=None,
401 seqno=None, revwidth=None, mode='wb', modemap=None,
412 seqno=None, revwidth=None, mode='wb', modemap=None,
402 pathname=None):
413 pathname=None):
403
414
404 writable = mode not in ('r', 'rb')
415 writable = mode not in ('r', 'rb')
405
416
406 if not pat or pat == '-':
417 if not pat or pat == '-':
407 if writable:
418 if writable:
408 fp = repo.ui.fout
419 fp = repo.ui.fout
409 else:
420 else:
410 fp = repo.ui.fin
421 fp = repo.ui.fin
411 if util.safehasattr(fp, 'fileno'):
422 if util.safehasattr(fp, 'fileno'):
412 return os.fdopen(os.dup(fp.fileno()), mode)
423 return os.fdopen(os.dup(fp.fileno()), mode)
413 else:
424 else:
414 # if this fp can't be duped properly, return
425 # if this fp can't be duped properly, return
415 # a dummy object that can be closed
426 # a dummy object that can be closed
416 class wrappedfileobj(object):
427 class wrappedfileobj(object):
417 noop = lambda x: None
428 noop = lambda x: None
418 def __init__(self, f):
429 def __init__(self, f):
419 self.f = f
430 self.f = f
420 def __getattr__(self, attr):
431 def __getattr__(self, attr):
421 if attr == 'close':
432 if attr == 'close':
422 return self.noop
433 return self.noop
423 else:
434 else:
424 return getattr(self.f, attr)
435 return getattr(self.f, attr)
425
436
426 return wrappedfileobj(fp)
437 return wrappedfileobj(fp)
427 if util.safehasattr(pat, 'write') and writable:
438 if util.safehasattr(pat, 'write') and writable:
428 return pat
439 return pat
429 if util.safehasattr(pat, 'read') and 'r' in mode:
440 if util.safehasattr(pat, 'read') and 'r' in mode:
430 return pat
441 return pat
431 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
442 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
432 if modemap is not None:
443 if modemap is not None:
433 mode = modemap.get(fn, mode)
444 mode = modemap.get(fn, mode)
434 if mode == 'wb':
445 if mode == 'wb':
435 modemap[fn] = 'ab'
446 modemap[fn] = 'ab'
436 return open(fn, mode)
447 return open(fn, mode)
437
448
438 def openrevlog(repo, cmd, file_, opts):
449 def openrevlog(repo, cmd, file_, opts):
439 """opens the changelog, manifest, a filelog or a given revlog"""
450 """opens the changelog, manifest, a filelog or a given revlog"""
440 cl = opts['changelog']
451 cl = opts['changelog']
441 mf = opts['manifest']
452 mf = opts['manifest']
442 msg = None
453 msg = None
443 if cl and mf:
454 if cl and mf:
444 msg = _('cannot specify --changelog and --manifest at the same time')
455 msg = _('cannot specify --changelog and --manifest at the same time')
445 elif cl or mf:
456 elif cl or mf:
446 if file_:
457 if file_:
447 msg = _('cannot specify filename with --changelog or --manifest')
458 msg = _('cannot specify filename with --changelog or --manifest')
448 elif not repo:
459 elif not repo:
449 msg = _('cannot specify --changelog or --manifest '
460 msg = _('cannot specify --changelog or --manifest '
450 'without a repository')
461 'without a repository')
451 if msg:
462 if msg:
452 raise util.Abort(msg)
463 raise util.Abort(msg)
453
464
454 r = None
465 r = None
455 if repo:
466 if repo:
456 if cl:
467 if cl:
457 r = repo.unfiltered().changelog
468 r = repo.unfiltered().changelog
458 elif mf:
469 elif mf:
459 r = repo.manifest
470 r = repo.manifest
460 elif file_:
471 elif file_:
461 filelog = repo.file(file_)
472 filelog = repo.file(file_)
462 if len(filelog):
473 if len(filelog):
463 r = filelog
474 r = filelog
464 if not r:
475 if not r:
465 if not file_:
476 if not file_:
466 raise error.CommandError(cmd, _('invalid arguments'))
477 raise error.CommandError(cmd, _('invalid arguments'))
467 if not os.path.isfile(file_):
478 if not os.path.isfile(file_):
468 raise util.Abort(_("revlog '%s' not found") % file_)
479 raise util.Abort(_("revlog '%s' not found") % file_)
469 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
480 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
470 file_[:-2] + ".i")
481 file_[:-2] + ".i")
471 return r
482 return r
472
483
473 def copy(ui, repo, pats, opts, rename=False):
484 def copy(ui, repo, pats, opts, rename=False):
474 # called with the repo lock held
485 # called with the repo lock held
475 #
486 #
476 # hgsep => pathname that uses "/" to separate directories
487 # hgsep => pathname that uses "/" to separate directories
477 # ossep => pathname that uses os.sep to separate directories
488 # ossep => pathname that uses os.sep to separate directories
478 cwd = repo.getcwd()
489 cwd = repo.getcwd()
479 targets = {}
490 targets = {}
480 after = opts.get("after")
491 after = opts.get("after")
481 dryrun = opts.get("dry_run")
492 dryrun = opts.get("dry_run")
482 wctx = repo[None]
493 wctx = repo[None]
483
494
484 def walkpat(pat):
495 def walkpat(pat):
485 srcs = []
496 srcs = []
486 if after:
497 if after:
487 badstates = '?'
498 badstates = '?'
488 else:
499 else:
489 badstates = '?r'
500 badstates = '?r'
490 m = scmutil.match(repo[None], [pat], opts, globbed=True)
501 m = scmutil.match(repo[None], [pat], opts, globbed=True)
491 for abs in repo.walk(m):
502 for abs in repo.walk(m):
492 state = repo.dirstate[abs]
503 state = repo.dirstate[abs]
493 rel = m.rel(abs)
504 rel = m.rel(abs)
494 exact = m.exact(abs)
505 exact = m.exact(abs)
495 if state in badstates:
506 if state in badstates:
496 if exact and state == '?':
507 if exact and state == '?':
497 ui.warn(_('%s: not copying - file is not managed\n') % rel)
508 ui.warn(_('%s: not copying - file is not managed\n') % rel)
498 if exact and state == 'r':
509 if exact and state == 'r':
499 ui.warn(_('%s: not copying - file has been marked for'
510 ui.warn(_('%s: not copying - file has been marked for'
500 ' remove\n') % rel)
511 ' remove\n') % rel)
501 continue
512 continue
502 # abs: hgsep
513 # abs: hgsep
503 # rel: ossep
514 # rel: ossep
504 srcs.append((abs, rel, exact))
515 srcs.append((abs, rel, exact))
505 return srcs
516 return srcs
506
517
507 # abssrc: hgsep
518 # abssrc: hgsep
508 # relsrc: ossep
519 # relsrc: ossep
509 # otarget: ossep
520 # otarget: ossep
510 def copyfile(abssrc, relsrc, otarget, exact):
521 def copyfile(abssrc, relsrc, otarget, exact):
511 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
522 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
512 if '/' in abstarget:
523 if '/' in abstarget:
513 # We cannot normalize abstarget itself, this would prevent
524 # We cannot normalize abstarget itself, this would prevent
514 # case only renames, like a => A.
525 # case only renames, like a => A.
515 abspath, absname = abstarget.rsplit('/', 1)
526 abspath, absname = abstarget.rsplit('/', 1)
516 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
527 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
517 reltarget = repo.pathto(abstarget, cwd)
528 reltarget = repo.pathto(abstarget, cwd)
518 target = repo.wjoin(abstarget)
529 target = repo.wjoin(abstarget)
519 src = repo.wjoin(abssrc)
530 src = repo.wjoin(abssrc)
520 state = repo.dirstate[abstarget]
531 state = repo.dirstate[abstarget]
521
532
522 scmutil.checkportable(ui, abstarget)
533 scmutil.checkportable(ui, abstarget)
523
534
524 # check for collisions
535 # check for collisions
525 prevsrc = targets.get(abstarget)
536 prevsrc = targets.get(abstarget)
526 if prevsrc is not None:
537 if prevsrc is not None:
527 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
538 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
528 (reltarget, repo.pathto(abssrc, cwd),
539 (reltarget, repo.pathto(abssrc, cwd),
529 repo.pathto(prevsrc, cwd)))
540 repo.pathto(prevsrc, cwd)))
530 return
541 return
531
542
532 # check for overwrites
543 # check for overwrites
533 exists = os.path.lexists(target)
544 exists = os.path.lexists(target)
534 samefile = False
545 samefile = False
535 if exists and abssrc != abstarget:
546 if exists and abssrc != abstarget:
536 if (repo.dirstate.normalize(abssrc) ==
547 if (repo.dirstate.normalize(abssrc) ==
537 repo.dirstate.normalize(abstarget)):
548 repo.dirstate.normalize(abstarget)):
538 if not rename:
549 if not rename:
539 ui.warn(_("%s: can't copy - same file\n") % reltarget)
550 ui.warn(_("%s: can't copy - same file\n") % reltarget)
540 return
551 return
541 exists = False
552 exists = False
542 samefile = True
553 samefile = True
543
554
544 if not after and exists or after and state in 'mn':
555 if not after and exists or after and state in 'mn':
545 if not opts['force']:
556 if not opts['force']:
546 ui.warn(_('%s: not overwriting - file exists\n') %
557 ui.warn(_('%s: not overwriting - file exists\n') %
547 reltarget)
558 reltarget)
548 return
559 return
549
560
550 if after:
561 if after:
551 if not exists:
562 if not exists:
552 if rename:
563 if rename:
553 ui.warn(_('%s: not recording move - %s does not exist\n') %
564 ui.warn(_('%s: not recording move - %s does not exist\n') %
554 (relsrc, reltarget))
565 (relsrc, reltarget))
555 else:
566 else:
556 ui.warn(_('%s: not recording copy - %s does not exist\n') %
567 ui.warn(_('%s: not recording copy - %s does not exist\n') %
557 (relsrc, reltarget))
568 (relsrc, reltarget))
558 return
569 return
559 elif not dryrun:
570 elif not dryrun:
560 try:
571 try:
561 if exists:
572 if exists:
562 os.unlink(target)
573 os.unlink(target)
563 targetdir = os.path.dirname(target) or '.'
574 targetdir = os.path.dirname(target) or '.'
564 if not os.path.isdir(targetdir):
575 if not os.path.isdir(targetdir):
565 os.makedirs(targetdir)
576 os.makedirs(targetdir)
566 if samefile:
577 if samefile:
567 tmp = target + "~hgrename"
578 tmp = target + "~hgrename"
568 os.rename(src, tmp)
579 os.rename(src, tmp)
569 os.rename(tmp, target)
580 os.rename(tmp, target)
570 else:
581 else:
571 util.copyfile(src, target)
582 util.copyfile(src, target)
572 srcexists = True
583 srcexists = True
573 except IOError, inst:
584 except IOError, inst:
574 if inst.errno == errno.ENOENT:
585 if inst.errno == errno.ENOENT:
575 ui.warn(_('%s: deleted in working directory\n') % relsrc)
586 ui.warn(_('%s: deleted in working directory\n') % relsrc)
576 srcexists = False
587 srcexists = False
577 else:
588 else:
578 ui.warn(_('%s: cannot copy - %s\n') %
589 ui.warn(_('%s: cannot copy - %s\n') %
579 (relsrc, inst.strerror))
590 (relsrc, inst.strerror))
580 return True # report a failure
591 return True # report a failure
581
592
582 if ui.verbose or not exact:
593 if ui.verbose or not exact:
583 if rename:
594 if rename:
584 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
595 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
585 else:
596 else:
586 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
597 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
587
598
588 targets[abstarget] = abssrc
599 targets[abstarget] = abssrc
589
600
590 # fix up dirstate
601 # fix up dirstate
591 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
602 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
592 dryrun=dryrun, cwd=cwd)
603 dryrun=dryrun, cwd=cwd)
593 if rename and not dryrun:
604 if rename and not dryrun:
594 if not after and srcexists and not samefile:
605 if not after and srcexists and not samefile:
595 util.unlinkpath(repo.wjoin(abssrc))
606 util.unlinkpath(repo.wjoin(abssrc))
596 wctx.forget([abssrc])
607 wctx.forget([abssrc])
597
608
598 # pat: ossep
609 # pat: ossep
599 # dest ossep
610 # dest ossep
600 # srcs: list of (hgsep, hgsep, ossep, bool)
611 # srcs: list of (hgsep, hgsep, ossep, bool)
601 # return: function that takes hgsep and returns ossep
612 # return: function that takes hgsep and returns ossep
602 def targetpathfn(pat, dest, srcs):
613 def targetpathfn(pat, dest, srcs):
603 if os.path.isdir(pat):
614 if os.path.isdir(pat):
604 abspfx = pathutil.canonpath(repo.root, cwd, pat)
615 abspfx = pathutil.canonpath(repo.root, cwd, pat)
605 abspfx = util.localpath(abspfx)
616 abspfx = util.localpath(abspfx)
606 if destdirexists:
617 if destdirexists:
607 striplen = len(os.path.split(abspfx)[0])
618 striplen = len(os.path.split(abspfx)[0])
608 else:
619 else:
609 striplen = len(abspfx)
620 striplen = len(abspfx)
610 if striplen:
621 if striplen:
611 striplen += len(os.sep)
622 striplen += len(os.sep)
612 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
623 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
613 elif destdirexists:
624 elif destdirexists:
614 res = lambda p: os.path.join(dest,
625 res = lambda p: os.path.join(dest,
615 os.path.basename(util.localpath(p)))
626 os.path.basename(util.localpath(p)))
616 else:
627 else:
617 res = lambda p: dest
628 res = lambda p: dest
618 return res
629 return res
619
630
620 # pat: ossep
631 # pat: ossep
621 # dest ossep
632 # dest ossep
622 # srcs: list of (hgsep, hgsep, ossep, bool)
633 # srcs: list of (hgsep, hgsep, ossep, bool)
623 # return: function that takes hgsep and returns ossep
634 # return: function that takes hgsep and returns ossep
624 def targetpathafterfn(pat, dest, srcs):
635 def targetpathafterfn(pat, dest, srcs):
625 if matchmod.patkind(pat):
636 if matchmod.patkind(pat):
626 # a mercurial pattern
637 # a mercurial pattern
627 res = lambda p: os.path.join(dest,
638 res = lambda p: os.path.join(dest,
628 os.path.basename(util.localpath(p)))
639 os.path.basename(util.localpath(p)))
629 else:
640 else:
630 abspfx = pathutil.canonpath(repo.root, cwd, pat)
641 abspfx = pathutil.canonpath(repo.root, cwd, pat)
631 if len(abspfx) < len(srcs[0][0]):
642 if len(abspfx) < len(srcs[0][0]):
632 # A directory. Either the target path contains the last
643 # A directory. Either the target path contains the last
633 # component of the source path or it does not.
644 # component of the source path or it does not.
634 def evalpath(striplen):
645 def evalpath(striplen):
635 score = 0
646 score = 0
636 for s in srcs:
647 for s in srcs:
637 t = os.path.join(dest, util.localpath(s[0])[striplen:])
648 t = os.path.join(dest, util.localpath(s[0])[striplen:])
638 if os.path.lexists(t):
649 if os.path.lexists(t):
639 score += 1
650 score += 1
640 return score
651 return score
641
652
642 abspfx = util.localpath(abspfx)
653 abspfx = util.localpath(abspfx)
643 striplen = len(abspfx)
654 striplen = len(abspfx)
644 if striplen:
655 if striplen:
645 striplen += len(os.sep)
656 striplen += len(os.sep)
646 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
657 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
647 score = evalpath(striplen)
658 score = evalpath(striplen)
648 striplen1 = len(os.path.split(abspfx)[0])
659 striplen1 = len(os.path.split(abspfx)[0])
649 if striplen1:
660 if striplen1:
650 striplen1 += len(os.sep)
661 striplen1 += len(os.sep)
651 if evalpath(striplen1) > score:
662 if evalpath(striplen1) > score:
652 striplen = striplen1
663 striplen = striplen1
653 res = lambda p: os.path.join(dest,
664 res = lambda p: os.path.join(dest,
654 util.localpath(p)[striplen:])
665 util.localpath(p)[striplen:])
655 else:
666 else:
656 # a file
667 # a file
657 if destdirexists:
668 if destdirexists:
658 res = lambda p: os.path.join(dest,
669 res = lambda p: os.path.join(dest,
659 os.path.basename(util.localpath(p)))
670 os.path.basename(util.localpath(p)))
660 else:
671 else:
661 res = lambda p: dest
672 res = lambda p: dest
662 return res
673 return res
663
674
664 pats = scmutil.expandpats(pats)
675 pats = scmutil.expandpats(pats)
665 if not pats:
676 if not pats:
666 raise util.Abort(_('no source or destination specified'))
677 raise util.Abort(_('no source or destination specified'))
667 if len(pats) == 1:
678 if len(pats) == 1:
668 raise util.Abort(_('no destination specified'))
679 raise util.Abort(_('no destination specified'))
669 dest = pats.pop()
680 dest = pats.pop()
670 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
681 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
671 if not destdirexists:
682 if not destdirexists:
672 if len(pats) > 1 or matchmod.patkind(pats[0]):
683 if len(pats) > 1 or matchmod.patkind(pats[0]):
673 raise util.Abort(_('with multiple sources, destination must be an '
684 raise util.Abort(_('with multiple sources, destination must be an '
674 'existing directory'))
685 'existing directory'))
675 if util.endswithsep(dest):
686 if util.endswithsep(dest):
676 raise util.Abort(_('destination %s is not a directory') % dest)
687 raise util.Abort(_('destination %s is not a directory') % dest)
677
688
678 tfn = targetpathfn
689 tfn = targetpathfn
679 if after:
690 if after:
680 tfn = targetpathafterfn
691 tfn = targetpathafterfn
681 copylist = []
692 copylist = []
682 for pat in pats:
693 for pat in pats:
683 srcs = walkpat(pat)
694 srcs = walkpat(pat)
684 if not srcs:
695 if not srcs:
685 continue
696 continue
686 copylist.append((tfn(pat, dest, srcs), srcs))
697 copylist.append((tfn(pat, dest, srcs), srcs))
687 if not copylist:
698 if not copylist:
688 raise util.Abort(_('no files to copy'))
699 raise util.Abort(_('no files to copy'))
689
700
690 errors = 0
701 errors = 0
691 for targetpath, srcs in copylist:
702 for targetpath, srcs in copylist:
692 for abssrc, relsrc, exact in srcs:
703 for abssrc, relsrc, exact in srcs:
693 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
704 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
694 errors += 1
705 errors += 1
695
706
696 if errors:
707 if errors:
697 ui.warn(_('(consider using --after)\n'))
708 ui.warn(_('(consider using --after)\n'))
698
709
699 return errors != 0
710 return errors != 0
700
711
701 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
712 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
702 runargs=None, appendpid=False):
713 runargs=None, appendpid=False):
703 '''Run a command as a service.'''
714 '''Run a command as a service.'''
704
715
705 def writepid(pid):
716 def writepid(pid):
706 if opts['pid_file']:
717 if opts['pid_file']:
707 if appendpid:
718 if appendpid:
708 mode = 'a'
719 mode = 'a'
709 else:
720 else:
710 mode = 'w'
721 mode = 'w'
711 fp = open(opts['pid_file'], mode)
722 fp = open(opts['pid_file'], mode)
712 fp.write(str(pid) + '\n')
723 fp.write(str(pid) + '\n')
713 fp.close()
724 fp.close()
714
725
715 if opts['daemon'] and not opts['daemon_pipefds']:
726 if opts['daemon'] and not opts['daemon_pipefds']:
716 # Signal child process startup with file removal
727 # Signal child process startup with file removal
717 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
728 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
718 os.close(lockfd)
729 os.close(lockfd)
719 try:
730 try:
720 if not runargs:
731 if not runargs:
721 runargs = util.hgcmd() + sys.argv[1:]
732 runargs = util.hgcmd() + sys.argv[1:]
722 runargs.append('--daemon-pipefds=%s' % lockpath)
733 runargs.append('--daemon-pipefds=%s' % lockpath)
723 # Don't pass --cwd to the child process, because we've already
734 # Don't pass --cwd to the child process, because we've already
724 # changed directory.
735 # changed directory.
725 for i in xrange(1, len(runargs)):
736 for i in xrange(1, len(runargs)):
726 if runargs[i].startswith('--cwd='):
737 if runargs[i].startswith('--cwd='):
727 del runargs[i]
738 del runargs[i]
728 break
739 break
729 elif runargs[i].startswith('--cwd'):
740 elif runargs[i].startswith('--cwd'):
730 del runargs[i:i + 2]
741 del runargs[i:i + 2]
731 break
742 break
732 def condfn():
743 def condfn():
733 return not os.path.exists(lockpath)
744 return not os.path.exists(lockpath)
734 pid = util.rundetached(runargs, condfn)
745 pid = util.rundetached(runargs, condfn)
735 if pid < 0:
746 if pid < 0:
736 raise util.Abort(_('child process failed to start'))
747 raise util.Abort(_('child process failed to start'))
737 writepid(pid)
748 writepid(pid)
738 finally:
749 finally:
739 try:
750 try:
740 os.unlink(lockpath)
751 os.unlink(lockpath)
741 except OSError, e:
752 except OSError, e:
742 if e.errno != errno.ENOENT:
753 if e.errno != errno.ENOENT:
743 raise
754 raise
744 if parentfn:
755 if parentfn:
745 return parentfn(pid)
756 return parentfn(pid)
746 else:
757 else:
747 return
758 return
748
759
749 if initfn:
760 if initfn:
750 initfn()
761 initfn()
751
762
752 if not opts['daemon']:
763 if not opts['daemon']:
753 writepid(os.getpid())
764 writepid(os.getpid())
754
765
755 if opts['daemon_pipefds']:
766 if opts['daemon_pipefds']:
756 lockpath = opts['daemon_pipefds']
767 lockpath = opts['daemon_pipefds']
757 try:
768 try:
758 os.setsid()
769 os.setsid()
759 except AttributeError:
770 except AttributeError:
760 pass
771 pass
761 os.unlink(lockpath)
772 os.unlink(lockpath)
762 util.hidewindow()
773 util.hidewindow()
763 sys.stdout.flush()
774 sys.stdout.flush()
764 sys.stderr.flush()
775 sys.stderr.flush()
765
776
766 nullfd = os.open(os.devnull, os.O_RDWR)
777 nullfd = os.open(os.devnull, os.O_RDWR)
767 logfilefd = nullfd
778 logfilefd = nullfd
768 if logfile:
779 if logfile:
769 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
780 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
770 os.dup2(nullfd, 0)
781 os.dup2(nullfd, 0)
771 os.dup2(logfilefd, 1)
782 os.dup2(logfilefd, 1)
772 os.dup2(logfilefd, 2)
783 os.dup2(logfilefd, 2)
773 if nullfd not in (0, 1, 2):
784 if nullfd not in (0, 1, 2):
774 os.close(nullfd)
785 os.close(nullfd)
775 if logfile and logfilefd not in (0, 1, 2):
786 if logfile and logfilefd not in (0, 1, 2):
776 os.close(logfilefd)
787 os.close(logfilefd)
777
788
778 if runfn:
789 if runfn:
779 return runfn()
790 return runfn()
780
791
781 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
792 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
782 """Utility function used by commands.import to import a single patch
793 """Utility function used by commands.import to import a single patch
783
794
784 This function is explicitly defined here to help the evolve extension to
795 This function is explicitly defined here to help the evolve extension to
785 wrap this part of the import logic.
796 wrap this part of the import logic.
786
797
787 The API is currently a bit ugly because it a simple code translation from
798 The API is currently a bit ugly because it a simple code translation from
788 the import command. Feel free to make it better.
799 the import command. Feel free to make it better.
789
800
790 :hunk: a patch (as a binary string)
801 :hunk: a patch (as a binary string)
791 :parents: nodes that will be parent of the created commit
802 :parents: nodes that will be parent of the created commit
792 :opts: the full dict of option passed to the import command
803 :opts: the full dict of option passed to the import command
793 :msgs: list to save commit message to.
804 :msgs: list to save commit message to.
794 (used in case we need to save it when failing)
805 (used in case we need to save it when failing)
795 :updatefunc: a function that update a repo to a given node
806 :updatefunc: a function that update a repo to a given node
796 updatefunc(<repo>, <node>)
807 updatefunc(<repo>, <node>)
797 """
808 """
798 tmpname, message, user, date, branch, nodeid, p1, p2 = \
809 tmpname, message, user, date, branch, nodeid, p1, p2 = \
799 patch.extract(ui, hunk)
810 patch.extract(ui, hunk)
800
811
801 update = not opts.get('bypass')
812 update = not opts.get('bypass')
802 strip = opts["strip"]
813 strip = opts["strip"]
803 prefix = opts["prefix"]
814 prefix = opts["prefix"]
804 sim = float(opts.get('similarity') or 0)
815 sim = float(opts.get('similarity') or 0)
805 if not tmpname:
816 if not tmpname:
806 return (None, None, False)
817 return (None, None, False)
807 msg = _('applied to working directory')
818 msg = _('applied to working directory')
808
819
809 rejects = False
820 rejects = False
810
821
811 try:
822 try:
812 cmdline_message = logmessage(ui, opts)
823 cmdline_message = logmessage(ui, opts)
813 if cmdline_message:
824 if cmdline_message:
814 # pickup the cmdline msg
825 # pickup the cmdline msg
815 message = cmdline_message
826 message = cmdline_message
816 elif message:
827 elif message:
817 # pickup the patch msg
828 # pickup the patch msg
818 message = message.strip()
829 message = message.strip()
819 else:
830 else:
820 # launch the editor
831 # launch the editor
821 message = None
832 message = None
822 ui.debug('message:\n%s\n' % message)
833 ui.debug('message:\n%s\n' % message)
823
834
824 if len(parents) == 1:
835 if len(parents) == 1:
825 parents.append(repo[nullid])
836 parents.append(repo[nullid])
826 if opts.get('exact'):
837 if opts.get('exact'):
827 if not nodeid or not p1:
838 if not nodeid or not p1:
828 raise util.Abort(_('not a Mercurial patch'))
839 raise util.Abort(_('not a Mercurial patch'))
829 p1 = repo[p1]
840 p1 = repo[p1]
830 p2 = repo[p2 or nullid]
841 p2 = repo[p2 or nullid]
831 elif p2:
842 elif p2:
832 try:
843 try:
833 p1 = repo[p1]
844 p1 = repo[p1]
834 p2 = repo[p2]
845 p2 = repo[p2]
835 # Without any options, consider p2 only if the
846 # Without any options, consider p2 only if the
836 # patch is being applied on top of the recorded
847 # patch is being applied on top of the recorded
837 # first parent.
848 # first parent.
838 if p1 != parents[0]:
849 if p1 != parents[0]:
839 p1 = parents[0]
850 p1 = parents[0]
840 p2 = repo[nullid]
851 p2 = repo[nullid]
841 except error.RepoError:
852 except error.RepoError:
842 p1, p2 = parents
853 p1, p2 = parents
843 if p2.node() == nullid:
854 if p2.node() == nullid:
844 ui.warn(_("warning: import the patch as a normal revision\n"
855 ui.warn(_("warning: import the patch as a normal revision\n"
845 "(use --exact to import the patch as a merge)\n"))
856 "(use --exact to import the patch as a merge)\n"))
846 else:
857 else:
847 p1, p2 = parents
858 p1, p2 = parents
848
859
849 n = None
860 n = None
850 if update:
861 if update:
851 repo.dirstate.beginparentchange()
862 repo.dirstate.beginparentchange()
852 if p1 != parents[0]:
863 if p1 != parents[0]:
853 updatefunc(repo, p1.node())
864 updatefunc(repo, p1.node())
854 if p2 != parents[1]:
865 if p2 != parents[1]:
855 repo.setparents(p1.node(), p2.node())
866 repo.setparents(p1.node(), p2.node())
856
867
857 if opts.get('exact') or opts.get('import_branch'):
868 if opts.get('exact') or opts.get('import_branch'):
858 repo.dirstate.setbranch(branch or 'default')
869 repo.dirstate.setbranch(branch or 'default')
859
870
860 partial = opts.get('partial', False)
871 partial = opts.get('partial', False)
861 files = set()
872 files = set()
862 try:
873 try:
863 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
874 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
864 files=files, eolmode=None, similarity=sim / 100.0)
875 files=files, eolmode=None, similarity=sim / 100.0)
865 except patch.PatchError, e:
876 except patch.PatchError, e:
866 if not partial:
877 if not partial:
867 raise util.Abort(str(e))
878 raise util.Abort(str(e))
868 if partial:
879 if partial:
869 rejects = True
880 rejects = True
870
881
871 files = list(files)
882 files = list(files)
872 if opts.get('no_commit'):
883 if opts.get('no_commit'):
873 if message:
884 if message:
874 msgs.append(message)
885 msgs.append(message)
875 else:
886 else:
876 if opts.get('exact') or p2:
887 if opts.get('exact') or p2:
877 # If you got here, you either use --force and know what
888 # If you got here, you either use --force and know what
878 # you are doing or used --exact or a merge patch while
889 # you are doing or used --exact or a merge patch while
879 # being updated to its first parent.
890 # being updated to its first parent.
880 m = None
891 m = None
881 else:
892 else:
882 m = scmutil.matchfiles(repo, files or [])
893 m = scmutil.matchfiles(repo, files or [])
883 editform = mergeeditform(repo[None], 'import.normal')
894 editform = mergeeditform(repo[None], 'import.normal')
884 if opts.get('exact'):
895 if opts.get('exact'):
885 editor = None
896 editor = None
886 else:
897 else:
887 editor = getcommiteditor(editform=editform, **opts)
898 editor = getcommiteditor(editform=editform, **opts)
888 n = repo.commit(message, opts.get('user') or user,
899 n = repo.commit(message, opts.get('user') or user,
889 opts.get('date') or date, match=m,
900 opts.get('date') or date, match=m,
890 editor=editor, force=partial)
901 editor=editor, force=partial)
891 repo.dirstate.endparentchange()
902 repo.dirstate.endparentchange()
892 else:
903 else:
893 if opts.get('exact') or opts.get('import_branch'):
904 if opts.get('exact') or opts.get('import_branch'):
894 branch = branch or 'default'
905 branch = branch or 'default'
895 else:
906 else:
896 branch = p1.branch()
907 branch = p1.branch()
897 store = patch.filestore()
908 store = patch.filestore()
898 try:
909 try:
899 files = set()
910 files = set()
900 try:
911 try:
901 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
912 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
902 files, eolmode=None)
913 files, eolmode=None)
903 except patch.PatchError, e:
914 except patch.PatchError, e:
904 raise util.Abort(str(e))
915 raise util.Abort(str(e))
905 if opts.get('exact'):
916 if opts.get('exact'):
906 editor = None
917 editor = None
907 else:
918 else:
908 editor = getcommiteditor(editform='import.bypass')
919 editor = getcommiteditor(editform='import.bypass')
909 memctx = context.makememctx(repo, (p1.node(), p2.node()),
920 memctx = context.makememctx(repo, (p1.node(), p2.node()),
910 message,
921 message,
911 opts.get('user') or user,
922 opts.get('user') or user,
912 opts.get('date') or date,
923 opts.get('date') or date,
913 branch, files, store,
924 branch, files, store,
914 editor=editor)
925 editor=editor)
915 n = memctx.commit()
926 n = memctx.commit()
916 finally:
927 finally:
917 store.close()
928 store.close()
918 if opts.get('exact') and opts.get('no_commit'):
929 if opts.get('exact') and opts.get('no_commit'):
919 # --exact with --no-commit is still useful in that it does merge
930 # --exact with --no-commit is still useful in that it does merge
920 # and branch bits
931 # and branch bits
921 ui.warn(_("warning: can't check exact import with --no-commit\n"))
932 ui.warn(_("warning: can't check exact import with --no-commit\n"))
922 elif opts.get('exact') and hex(n) != nodeid:
933 elif opts.get('exact') and hex(n) != nodeid:
923 raise util.Abort(_('patch is damaged or loses information'))
934 raise util.Abort(_('patch is damaged or loses information'))
924 if n:
935 if n:
925 # i18n: refers to a short changeset id
936 # i18n: refers to a short changeset id
926 msg = _('created %s') % short(n)
937 msg = _('created %s') % short(n)
927 return (msg, n, rejects)
938 return (msg, n, rejects)
928 finally:
939 finally:
929 os.unlink(tmpname)
940 os.unlink(tmpname)
930
941
931 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
942 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
932 opts=None):
943 opts=None):
933 '''export changesets as hg patches.'''
944 '''export changesets as hg patches.'''
934
945
935 total = len(revs)
946 total = len(revs)
936 revwidth = max([len(str(rev)) for rev in revs])
947 revwidth = max([len(str(rev)) for rev in revs])
937 filemode = {}
948 filemode = {}
938
949
939 def single(rev, seqno, fp):
950 def single(rev, seqno, fp):
940 ctx = repo[rev]
951 ctx = repo[rev]
941 node = ctx.node()
952 node = ctx.node()
942 parents = [p.node() for p in ctx.parents() if p]
953 parents = [p.node() for p in ctx.parents() if p]
943 branch = ctx.branch()
954 branch = ctx.branch()
944 if switch_parent:
955 if switch_parent:
945 parents.reverse()
956 parents.reverse()
946
957
947 if parents:
958 if parents:
948 prev = parents[0]
959 prev = parents[0]
949 else:
960 else:
950 prev = nullid
961 prev = nullid
951
962
952 shouldclose = False
963 shouldclose = False
953 if not fp and len(template) > 0:
964 if not fp and len(template) > 0:
954 desc_lines = ctx.description().rstrip().split('\n')
965 desc_lines = ctx.description().rstrip().split('\n')
955 desc = desc_lines[0] #Commit always has a first line.
966 desc = desc_lines[0] #Commit always has a first line.
956 fp = makefileobj(repo, template, node, desc=desc, total=total,
967 fp = makefileobj(repo, template, node, desc=desc, total=total,
957 seqno=seqno, revwidth=revwidth, mode='wb',
968 seqno=seqno, revwidth=revwidth, mode='wb',
958 modemap=filemode)
969 modemap=filemode)
959 if fp != template:
970 if fp != template:
960 shouldclose = True
971 shouldclose = True
961 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
972 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
962 repo.ui.note("%s\n" % fp.name)
973 repo.ui.note("%s\n" % fp.name)
963
974
964 if not fp:
975 if not fp:
965 write = repo.ui.write
976 write = repo.ui.write
966 else:
977 else:
967 def write(s, **kw):
978 def write(s, **kw):
968 fp.write(s)
979 fp.write(s)
969
980
970 write("# HG changeset patch\n")
981 write("# HG changeset patch\n")
971 write("# User %s\n" % ctx.user())
982 write("# User %s\n" % ctx.user())
972 write("# Date %d %d\n" % ctx.date())
983 write("# Date %d %d\n" % ctx.date())
973 write("# %s\n" % util.datestr(ctx.date()))
984 write("# %s\n" % util.datestr(ctx.date()))
974 if branch and branch != 'default':
985 if branch and branch != 'default':
975 write("# Branch %s\n" % branch)
986 write("# Branch %s\n" % branch)
976 write("# Node ID %s\n" % hex(node))
987 write("# Node ID %s\n" % hex(node))
977 write("# Parent %s\n" % hex(prev))
988 write("# Parent %s\n" % hex(prev))
978 if len(parents) > 1:
989 if len(parents) > 1:
979 write("# Parent %s\n" % hex(parents[1]))
990 write("# Parent %s\n" % hex(parents[1]))
980 write(ctx.description().rstrip())
991 write(ctx.description().rstrip())
981 write("\n\n")
992 write("\n\n")
982
993
983 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
994 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
984 write(chunk, label=label)
995 write(chunk, label=label)
985
996
986 if shouldclose:
997 if shouldclose:
987 fp.close()
998 fp.close()
988
999
989 for seqno, rev in enumerate(revs):
1000 for seqno, rev in enumerate(revs):
990 single(rev, seqno + 1, fp)
1001 single(rev, seqno + 1, fp)
991
1002
992 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1003 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
993 changes=None, stat=False, fp=None, prefix='',
1004 changes=None, stat=False, fp=None, prefix='',
994 root='', listsubrepos=False):
1005 root='', listsubrepos=False):
995 '''show diff or diffstat.'''
1006 '''show diff or diffstat.'''
996 if fp is None:
1007 if fp is None:
997 write = ui.write
1008 write = ui.write
998 else:
1009 else:
999 def write(s, **kw):
1010 def write(s, **kw):
1000 fp.write(s)
1011 fp.write(s)
1001
1012
1002 if root:
1013 if root:
1003 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1014 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1004 else:
1015 else:
1005 relroot = ''
1016 relroot = ''
1006 if relroot != '':
1017 if relroot != '':
1007 # XXX relative roots currently don't work if the root is within a
1018 # XXX relative roots currently don't work if the root is within a
1008 # subrepo
1019 # subrepo
1009 uirelroot = match.uipath(relroot)
1020 uirelroot = match.uipath(relroot)
1010 relroot += '/'
1021 relroot += '/'
1011 for matchroot in match.files():
1022 for matchroot in match.files():
1012 if not matchroot.startswith(relroot):
1023 if not matchroot.startswith(relroot):
1013 ui.warn(_('warning: %s not inside relative root %s\n') % (
1024 ui.warn(_('warning: %s not inside relative root %s\n') % (
1014 match.uipath(matchroot), uirelroot))
1025 match.uipath(matchroot), uirelroot))
1015
1026
1016 if stat:
1027 if stat:
1017 diffopts = diffopts.copy(context=0)
1028 diffopts = diffopts.copy(context=0)
1018 width = 80
1029 width = 80
1019 if not ui.plain():
1030 if not ui.plain():
1020 width = ui.termwidth()
1031 width = ui.termwidth()
1021 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1032 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1022 prefix=prefix, relroot=relroot)
1033 prefix=prefix, relroot=relroot)
1023 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1034 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1024 width=width,
1035 width=width,
1025 git=diffopts.git):
1036 git=diffopts.git):
1026 write(chunk, label=label)
1037 write(chunk, label=label)
1027 else:
1038 else:
1028 for chunk, label in patch.diffui(repo, node1, node2, match,
1039 for chunk, label in patch.diffui(repo, node1, node2, match,
1029 changes, diffopts, prefix=prefix,
1040 changes, diffopts, prefix=prefix,
1030 relroot=relroot):
1041 relroot=relroot):
1031 write(chunk, label=label)
1042 write(chunk, label=label)
1032
1043
1033 if listsubrepos:
1044 if listsubrepos:
1034 ctx1 = repo[node1]
1045 ctx1 = repo[node1]
1035 ctx2 = repo[node2]
1046 ctx2 = repo[node2]
1036 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1047 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1037 tempnode2 = node2
1048 tempnode2 = node2
1038 try:
1049 try:
1039 if node2 is not None:
1050 if node2 is not None:
1040 tempnode2 = ctx2.substate[subpath][1]
1051 tempnode2 = ctx2.substate[subpath][1]
1041 except KeyError:
1052 except KeyError:
1042 # A subrepo that existed in node1 was deleted between node1 and
1053 # A subrepo that existed in node1 was deleted between node1 and
1043 # node2 (inclusive). Thus, ctx2's substate won't contain that
1054 # node2 (inclusive). Thus, ctx2's substate won't contain that
1044 # subpath. The best we can do is to ignore it.
1055 # subpath. The best we can do is to ignore it.
1045 tempnode2 = None
1056 tempnode2 = None
1046 submatch = matchmod.narrowmatcher(subpath, match)
1057 submatch = matchmod.narrowmatcher(subpath, match)
1047 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1058 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1048 stat=stat, fp=fp, prefix=prefix)
1059 stat=stat, fp=fp, prefix=prefix)
1049
1060
1050 class changeset_printer(object):
1061 class changeset_printer(object):
1051 '''show changeset information when templating not requested.'''
1062 '''show changeset information when templating not requested.'''
1052
1063
1053 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1064 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1054 self.ui = ui
1065 self.ui = ui
1055 self.repo = repo
1066 self.repo = repo
1056 self.buffered = buffered
1067 self.buffered = buffered
1057 self.matchfn = matchfn
1068 self.matchfn = matchfn
1058 self.diffopts = diffopts
1069 self.diffopts = diffopts
1059 self.header = {}
1070 self.header = {}
1060 self.hunk = {}
1071 self.hunk = {}
1061 self.lastheader = None
1072 self.lastheader = None
1062 self.footer = None
1073 self.footer = None
1063
1074
1064 def flush(self, rev):
1075 def flush(self, rev):
1065 if rev in self.header:
1076 if rev in self.header:
1066 h = self.header[rev]
1077 h = self.header[rev]
1067 if h != self.lastheader:
1078 if h != self.lastheader:
1068 self.lastheader = h
1079 self.lastheader = h
1069 self.ui.write(h)
1080 self.ui.write(h)
1070 del self.header[rev]
1081 del self.header[rev]
1071 if rev in self.hunk:
1082 if rev in self.hunk:
1072 self.ui.write(self.hunk[rev])
1083 self.ui.write(self.hunk[rev])
1073 del self.hunk[rev]
1084 del self.hunk[rev]
1074 return 1
1085 return 1
1075 return 0
1086 return 0
1076
1087
1077 def close(self):
1088 def close(self):
1078 if self.footer:
1089 if self.footer:
1079 self.ui.write(self.footer)
1090 self.ui.write(self.footer)
1080
1091
1081 def show(self, ctx, copies=None, matchfn=None, **props):
1092 def show(self, ctx, copies=None, matchfn=None, **props):
1082 if self.buffered:
1093 if self.buffered:
1083 self.ui.pushbuffer()
1094 self.ui.pushbuffer()
1084 self._show(ctx, copies, matchfn, props)
1095 self._show(ctx, copies, matchfn, props)
1085 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1096 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1086 else:
1097 else:
1087 self._show(ctx, copies, matchfn, props)
1098 self._show(ctx, copies, matchfn, props)
1088
1099
1089 def _show(self, ctx, copies, matchfn, props):
1100 def _show(self, ctx, copies, matchfn, props):
1090 '''show a single changeset or file revision'''
1101 '''show a single changeset or file revision'''
1091 changenode = ctx.node()
1102 changenode = ctx.node()
1092 rev = ctx.rev()
1103 rev = ctx.rev()
1093 if self.ui.debugflag:
1104 if self.ui.debugflag:
1094 hexfunc = hex
1105 hexfunc = hex
1095 else:
1106 else:
1096 hexfunc = short
1107 hexfunc = short
1097 if rev is None:
1108 if rev is None:
1098 pctx = ctx.p1()
1109 pctx = ctx.p1()
1099 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1110 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1100 else:
1111 else:
1101 revnode = (rev, hexfunc(changenode))
1112 revnode = (rev, hexfunc(changenode))
1102
1113
1103 if self.ui.quiet:
1114 if self.ui.quiet:
1104 self.ui.write("%d:%s\n" % revnode, label='log.node')
1115 self.ui.write("%d:%s\n" % revnode, label='log.node')
1105 return
1116 return
1106
1117
1107 date = util.datestr(ctx.date())
1118 date = util.datestr(ctx.date())
1108
1119
1109 # i18n: column positioning for "hg log"
1120 # i18n: column positioning for "hg log"
1110 self.ui.write(_("changeset: %d:%s\n") % revnode,
1121 self.ui.write(_("changeset: %d:%s\n") % revnode,
1111 label='log.changeset changeset.%s' % ctx.phasestr())
1122 label='log.changeset changeset.%s' % ctx.phasestr())
1112
1123
1113 # branches are shown first before any other names due to backwards
1124 # branches are shown first before any other names due to backwards
1114 # compatibility
1125 # compatibility
1115 branch = ctx.branch()
1126 branch = ctx.branch()
1116 # don't show the default branch name
1127 # don't show the default branch name
1117 if branch != 'default':
1128 if branch != 'default':
1118 # i18n: column positioning for "hg log"
1129 # i18n: column positioning for "hg log"
1119 self.ui.write(_("branch: %s\n") % branch,
1130 self.ui.write(_("branch: %s\n") % branch,
1120 label='log.branch')
1131 label='log.branch')
1121
1132
1122 for name, ns in self.repo.names.iteritems():
1133 for name, ns in self.repo.names.iteritems():
1123 # branches has special logic already handled above, so here we just
1134 # branches has special logic already handled above, so here we just
1124 # skip it
1135 # skip it
1125 if name == 'branches':
1136 if name == 'branches':
1126 continue
1137 continue
1127 # we will use the templatename as the color name since those two
1138 # we will use the templatename as the color name since those two
1128 # should be the same
1139 # should be the same
1129 for name in ns.names(self.repo, changenode):
1140 for name in ns.names(self.repo, changenode):
1130 self.ui.write(ns.logfmt % name,
1141 self.ui.write(ns.logfmt % name,
1131 label='log.%s' % ns.colorname)
1142 label='log.%s' % ns.colorname)
1132 if self.ui.debugflag:
1143 if self.ui.debugflag:
1133 # i18n: column positioning for "hg log"
1144 # i18n: column positioning for "hg log"
1134 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
1145 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
1135 label='log.phase')
1146 label='log.phase')
1136 for pctx in self._meaningful_parentrevs(ctx):
1147 for pctx in self._meaningful_parentrevs(ctx):
1137 label = 'log.parent changeset.%s' % pctx.phasestr()
1148 label = 'log.parent changeset.%s' % pctx.phasestr()
1138 # i18n: column positioning for "hg log"
1149 # i18n: column positioning for "hg log"
1139 self.ui.write(_("parent: %d:%s\n")
1150 self.ui.write(_("parent: %d:%s\n")
1140 % (pctx.rev(), hexfunc(pctx.node())),
1151 % (pctx.rev(), hexfunc(pctx.node())),
1141 label=label)
1152 label=label)
1142
1153
1143 if self.ui.debugflag and rev is not None:
1154 if self.ui.debugflag and rev is not None:
1144 mnode = ctx.manifestnode()
1155 mnode = ctx.manifestnode()
1145 # i18n: column positioning for "hg log"
1156 # i18n: column positioning for "hg log"
1146 self.ui.write(_("manifest: %d:%s\n") %
1157 self.ui.write(_("manifest: %d:%s\n") %
1147 (self.repo.manifest.rev(mnode), hex(mnode)),
1158 (self.repo.manifest.rev(mnode), hex(mnode)),
1148 label='ui.debug log.manifest')
1159 label='ui.debug log.manifest')
1149 # i18n: column positioning for "hg log"
1160 # i18n: column positioning for "hg log"
1150 self.ui.write(_("user: %s\n") % ctx.user(),
1161 self.ui.write(_("user: %s\n") % ctx.user(),
1151 label='log.user')
1162 label='log.user')
1152 # i18n: column positioning for "hg log"
1163 # i18n: column positioning for "hg log"
1153 self.ui.write(_("date: %s\n") % date,
1164 self.ui.write(_("date: %s\n") % date,
1154 label='log.date')
1165 label='log.date')
1155
1166
1156 if self.ui.debugflag:
1167 if self.ui.debugflag:
1157 files = ctx.p1().status(ctx)[:3]
1168 files = ctx.p1().status(ctx)[:3]
1158 for key, value in zip([# i18n: column positioning for "hg log"
1169 for key, value in zip([# i18n: column positioning for "hg log"
1159 _("files:"),
1170 _("files:"),
1160 # i18n: column positioning for "hg log"
1171 # i18n: column positioning for "hg log"
1161 _("files+:"),
1172 _("files+:"),
1162 # i18n: column positioning for "hg log"
1173 # i18n: column positioning for "hg log"
1163 _("files-:")], files):
1174 _("files-:")], files):
1164 if value:
1175 if value:
1165 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1176 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1166 label='ui.debug log.files')
1177 label='ui.debug log.files')
1167 elif ctx.files() and self.ui.verbose:
1178 elif ctx.files() and self.ui.verbose:
1168 # i18n: column positioning for "hg log"
1179 # i18n: column positioning for "hg log"
1169 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1180 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1170 label='ui.note log.files')
1181 label='ui.note log.files')
1171 if copies and self.ui.verbose:
1182 if copies and self.ui.verbose:
1172 copies = ['%s (%s)' % c for c in copies]
1183 copies = ['%s (%s)' % c for c in copies]
1173 # i18n: column positioning for "hg log"
1184 # i18n: column positioning for "hg log"
1174 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1185 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1175 label='ui.note log.copies')
1186 label='ui.note log.copies')
1176
1187
1177 extra = ctx.extra()
1188 extra = ctx.extra()
1178 if extra and self.ui.debugflag:
1189 if extra and self.ui.debugflag:
1179 for key, value in sorted(extra.items()):
1190 for key, value in sorted(extra.items()):
1180 # i18n: column positioning for "hg log"
1191 # i18n: column positioning for "hg log"
1181 self.ui.write(_("extra: %s=%s\n")
1192 self.ui.write(_("extra: %s=%s\n")
1182 % (key, value.encode('string_escape')),
1193 % (key, value.encode('string_escape')),
1183 label='ui.debug log.extra')
1194 label='ui.debug log.extra')
1184
1195
1185 description = ctx.description().strip()
1196 description = ctx.description().strip()
1186 if description:
1197 if description:
1187 if self.ui.verbose:
1198 if self.ui.verbose:
1188 self.ui.write(_("description:\n"),
1199 self.ui.write(_("description:\n"),
1189 label='ui.note log.description')
1200 label='ui.note log.description')
1190 self.ui.write(description,
1201 self.ui.write(description,
1191 label='ui.note log.description')
1202 label='ui.note log.description')
1192 self.ui.write("\n\n")
1203 self.ui.write("\n\n")
1193 else:
1204 else:
1194 # i18n: column positioning for "hg log"
1205 # i18n: column positioning for "hg log"
1195 self.ui.write(_("summary: %s\n") %
1206 self.ui.write(_("summary: %s\n") %
1196 description.splitlines()[0],
1207 description.splitlines()[0],
1197 label='log.summary')
1208 label='log.summary')
1198 self.ui.write("\n")
1209 self.ui.write("\n")
1199
1210
1200 self.showpatch(changenode, matchfn)
1211 self.showpatch(changenode, matchfn)
1201
1212
1202 def showpatch(self, node, matchfn):
1213 def showpatch(self, node, matchfn):
1203 if not matchfn:
1214 if not matchfn:
1204 matchfn = self.matchfn
1215 matchfn = self.matchfn
1205 if matchfn:
1216 if matchfn:
1206 stat = self.diffopts.get('stat')
1217 stat = self.diffopts.get('stat')
1207 diff = self.diffopts.get('patch')
1218 diff = self.diffopts.get('patch')
1208 diffopts = patch.diffallopts(self.ui, self.diffopts)
1219 diffopts = patch.diffallopts(self.ui, self.diffopts)
1209 prev = self.repo.changelog.parents(node)[0]
1220 prev = self.repo.changelog.parents(node)[0]
1210 if stat:
1221 if stat:
1211 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1222 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1212 match=matchfn, stat=True)
1223 match=matchfn, stat=True)
1213 if diff:
1224 if diff:
1214 if stat:
1225 if stat:
1215 self.ui.write("\n")
1226 self.ui.write("\n")
1216 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1227 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1217 match=matchfn, stat=False)
1228 match=matchfn, stat=False)
1218 self.ui.write("\n")
1229 self.ui.write("\n")
1219
1230
1220 def _meaningful_parentrevs(self, ctx):
1231 def _meaningful_parentrevs(self, ctx):
1221 """Return list of meaningful (or all if debug) parentrevs for rev.
1232 """Return list of meaningful (or all if debug) parentrevs for rev.
1222
1233
1223 For merges (two non-nullrev revisions) both parents are meaningful.
1234 For merges (two non-nullrev revisions) both parents are meaningful.
1224 Otherwise the first parent revision is considered meaningful if it
1235 Otherwise the first parent revision is considered meaningful if it
1225 is not the preceding revision.
1236 is not the preceding revision.
1226 """
1237 """
1227 parents = ctx.parents()
1238 parents = ctx.parents()
1228 if len(parents) > 1:
1239 if len(parents) > 1:
1229 return parents
1240 return parents
1230 if self.ui.debugflag:
1241 if self.ui.debugflag:
1231 return [parents[0], self.repo['null']]
1242 return [parents[0], self.repo['null']]
1232 if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1:
1243 if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1:
1233 return []
1244 return []
1234 return parents
1245 return parents
1235
1246
1236 class jsonchangeset(changeset_printer):
1247 class jsonchangeset(changeset_printer):
1237 '''format changeset information.'''
1248 '''format changeset information.'''
1238
1249
1239 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1250 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1240 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1251 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1241 self.cache = {}
1252 self.cache = {}
1242 self._first = True
1253 self._first = True
1243
1254
1244 def close(self):
1255 def close(self):
1245 if not self._first:
1256 if not self._first:
1246 self.ui.write("\n]\n")
1257 self.ui.write("\n]\n")
1247 else:
1258 else:
1248 self.ui.write("[]\n")
1259 self.ui.write("[]\n")
1249
1260
1250 def _show(self, ctx, copies, matchfn, props):
1261 def _show(self, ctx, copies, matchfn, props):
1251 '''show a single changeset or file revision'''
1262 '''show a single changeset or file revision'''
1252 rev = ctx.rev()
1263 rev = ctx.rev()
1253 if rev is None:
1264 if rev is None:
1254 jrev = jnode = 'null'
1265 jrev = jnode = 'null'
1255 else:
1266 else:
1256 jrev = str(rev)
1267 jrev = str(rev)
1257 jnode = '"%s"' % hex(ctx.node())
1268 jnode = '"%s"' % hex(ctx.node())
1258 j = encoding.jsonescape
1269 j = encoding.jsonescape
1259
1270
1260 if self._first:
1271 if self._first:
1261 self.ui.write("[\n {")
1272 self.ui.write("[\n {")
1262 self._first = False
1273 self._first = False
1263 else:
1274 else:
1264 self.ui.write(",\n {")
1275 self.ui.write(",\n {")
1265
1276
1266 if self.ui.quiet:
1277 if self.ui.quiet:
1267 self.ui.write('\n "rev": %s' % jrev)
1278 self.ui.write('\n "rev": %s' % jrev)
1268 self.ui.write(',\n "node": %s' % jnode)
1279 self.ui.write(',\n "node": %s' % jnode)
1269 self.ui.write('\n }')
1280 self.ui.write('\n }')
1270 return
1281 return
1271
1282
1272 self.ui.write('\n "rev": %s' % jrev)
1283 self.ui.write('\n "rev": %s' % jrev)
1273 self.ui.write(',\n "node": %s' % jnode)
1284 self.ui.write(',\n "node": %s' % jnode)
1274 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1285 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1275 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1286 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1276 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1287 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1277 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1288 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1278 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1289 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1279
1290
1280 self.ui.write(',\n "bookmarks": [%s]' %
1291 self.ui.write(',\n "bookmarks": [%s]' %
1281 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1292 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1282 self.ui.write(',\n "tags": [%s]' %
1293 self.ui.write(',\n "tags": [%s]' %
1283 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1294 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1284 self.ui.write(',\n "parents": [%s]' %
1295 self.ui.write(',\n "parents": [%s]' %
1285 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1296 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1286
1297
1287 if self.ui.debugflag:
1298 if self.ui.debugflag:
1288 if rev is None:
1299 if rev is None:
1289 jmanifestnode = 'null'
1300 jmanifestnode = 'null'
1290 else:
1301 else:
1291 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1302 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1292 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1303 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1293
1304
1294 self.ui.write(',\n "extra": {%s}' %
1305 self.ui.write(',\n "extra": {%s}' %
1295 ", ".join('"%s": "%s"' % (j(k), j(v))
1306 ", ".join('"%s": "%s"' % (j(k), j(v))
1296 for k, v in ctx.extra().items()))
1307 for k, v in ctx.extra().items()))
1297
1308
1298 files = ctx.p1().status(ctx)
1309 files = ctx.p1().status(ctx)
1299 self.ui.write(',\n "modified": [%s]' %
1310 self.ui.write(',\n "modified": [%s]' %
1300 ", ".join('"%s"' % j(f) for f in files[0]))
1311 ", ".join('"%s"' % j(f) for f in files[0]))
1301 self.ui.write(',\n "added": [%s]' %
1312 self.ui.write(',\n "added": [%s]' %
1302 ", ".join('"%s"' % j(f) for f in files[1]))
1313 ", ".join('"%s"' % j(f) for f in files[1]))
1303 self.ui.write(',\n "removed": [%s]' %
1314 self.ui.write(',\n "removed": [%s]' %
1304 ", ".join('"%s"' % j(f) for f in files[2]))
1315 ", ".join('"%s"' % j(f) for f in files[2]))
1305
1316
1306 elif self.ui.verbose:
1317 elif self.ui.verbose:
1307 self.ui.write(',\n "files": [%s]' %
1318 self.ui.write(',\n "files": [%s]' %
1308 ", ".join('"%s"' % j(f) for f in ctx.files()))
1319 ", ".join('"%s"' % j(f) for f in ctx.files()))
1309
1320
1310 if copies:
1321 if copies:
1311 self.ui.write(',\n "copies": {%s}' %
1322 self.ui.write(',\n "copies": {%s}' %
1312 ", ".join('"%s": "%s"' % (j(k), j(v))
1323 ", ".join('"%s": "%s"' % (j(k), j(v))
1313 for k, v in copies))
1324 for k, v in copies))
1314
1325
1315 matchfn = self.matchfn
1326 matchfn = self.matchfn
1316 if matchfn:
1327 if matchfn:
1317 stat = self.diffopts.get('stat')
1328 stat = self.diffopts.get('stat')
1318 diff = self.diffopts.get('patch')
1329 diff = self.diffopts.get('patch')
1319 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1330 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1320 node, prev = ctx.node(), ctx.p1().node()
1331 node, prev = ctx.node(), ctx.p1().node()
1321 if stat:
1332 if stat:
1322 self.ui.pushbuffer()
1333 self.ui.pushbuffer()
1323 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1334 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1324 match=matchfn, stat=True)
1335 match=matchfn, stat=True)
1325 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1336 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1326 if diff:
1337 if diff:
1327 self.ui.pushbuffer()
1338 self.ui.pushbuffer()
1328 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1339 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1329 match=matchfn, stat=False)
1340 match=matchfn, stat=False)
1330 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1341 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1331
1342
1332 self.ui.write("\n }")
1343 self.ui.write("\n }")
1333
1344
1334 class changeset_templater(changeset_printer):
1345 class changeset_templater(changeset_printer):
1335 '''format changeset information.'''
1346 '''format changeset information.'''
1336
1347
1337 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1348 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1338 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1349 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1339 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1350 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1340 defaulttempl = {
1351 defaulttempl = {
1341 'parent': '{rev}:{node|formatnode} ',
1352 'parent': '{rev}:{node|formatnode} ',
1342 'manifest': '{rev}:{node|formatnode}',
1353 'manifest': '{rev}:{node|formatnode}',
1343 'file_copy': '{name} ({source})',
1354 'file_copy': '{name} ({source})',
1344 'extra': '{key}={value|stringescape}'
1355 'extra': '{key}={value|stringescape}'
1345 }
1356 }
1346 # filecopy is preserved for compatibility reasons
1357 # filecopy is preserved for compatibility reasons
1347 defaulttempl['filecopy'] = defaulttempl['file_copy']
1358 defaulttempl['filecopy'] = defaulttempl['file_copy']
1348 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1359 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1349 cache=defaulttempl)
1360 cache=defaulttempl)
1350 if tmpl:
1361 if tmpl:
1351 self.t.cache['changeset'] = tmpl
1362 self.t.cache['changeset'] = tmpl
1352
1363
1353 self.cache = {}
1364 self.cache = {}
1354
1365
1355 def _show(self, ctx, copies, matchfn, props):
1366 def _show(self, ctx, copies, matchfn, props):
1356 '''show a single changeset or file revision'''
1367 '''show a single changeset or file revision'''
1357
1368
1358 showlist = templatekw.showlist
1369 showlist = templatekw.showlist
1359
1370
1360 # showparents() behaviour depends on ui trace level which
1371 # showparents() behaviour depends on ui trace level which
1361 # causes unexpected behaviours at templating level and makes
1372 # causes unexpected behaviours at templating level and makes
1362 # it harder to extract it in a standalone function. Its
1373 # it harder to extract it in a standalone function. Its
1363 # behaviour cannot be changed so leave it here for now.
1374 # behaviour cannot be changed so leave it here for now.
1364 def showparents(**args):
1375 def showparents(**args):
1365 ctx = args['ctx']
1376 ctx = args['ctx']
1366 parents = [[('rev', p.rev()),
1377 parents = [[('rev', p.rev()),
1367 ('node', p.hex()),
1378 ('node', p.hex()),
1368 ('phase', p.phasestr())]
1379 ('phase', p.phasestr())]
1369 for p in self._meaningful_parentrevs(ctx)]
1380 for p in self._meaningful_parentrevs(ctx)]
1370 return showlist('parent', parents, **args)
1381 return showlist('parent', parents, **args)
1371
1382
1372 props = props.copy()
1383 props = props.copy()
1373 props.update(templatekw.keywords)
1384 props.update(templatekw.keywords)
1374 props['parents'] = showparents
1385 props['parents'] = showparents
1375 props['templ'] = self.t
1386 props['templ'] = self.t
1376 props['ctx'] = ctx
1387 props['ctx'] = ctx
1377 props['repo'] = self.repo
1388 props['repo'] = self.repo
1378 props['revcache'] = {'copies': copies}
1389 props['revcache'] = {'copies': copies}
1379 props['cache'] = self.cache
1390 props['cache'] = self.cache
1380
1391
1381 # find correct templates for current mode
1392 # find correct templates for current mode
1382
1393
1383 tmplmodes = [
1394 tmplmodes = [
1384 (True, None),
1395 (True, None),
1385 (self.ui.verbose, 'verbose'),
1396 (self.ui.verbose, 'verbose'),
1386 (self.ui.quiet, 'quiet'),
1397 (self.ui.quiet, 'quiet'),
1387 (self.ui.debugflag, 'debug'),
1398 (self.ui.debugflag, 'debug'),
1388 ]
1399 ]
1389
1400
1390 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1401 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1391 for mode, postfix in tmplmodes:
1402 for mode, postfix in tmplmodes:
1392 for type in types:
1403 for type in types:
1393 cur = postfix and ('%s_%s' % (type, postfix)) or type
1404 cur = postfix and ('%s_%s' % (type, postfix)) or type
1394 if mode and cur in self.t:
1405 if mode and cur in self.t:
1395 types[type] = cur
1406 types[type] = cur
1396
1407
1397 try:
1408 try:
1398
1409
1399 # write header
1410 # write header
1400 if types['header']:
1411 if types['header']:
1401 h = templater.stringify(self.t(types['header'], **props))
1412 h = templater.stringify(self.t(types['header'], **props))
1402 if self.buffered:
1413 if self.buffered:
1403 self.header[ctx.rev()] = h
1414 self.header[ctx.rev()] = h
1404 else:
1415 else:
1405 if self.lastheader != h:
1416 if self.lastheader != h:
1406 self.lastheader = h
1417 self.lastheader = h
1407 self.ui.write(h)
1418 self.ui.write(h)
1408
1419
1409 # write changeset metadata, then patch if requested
1420 # write changeset metadata, then patch if requested
1410 key = types['changeset']
1421 key = types['changeset']
1411 self.ui.write(templater.stringify(self.t(key, **props)))
1422 self.ui.write(templater.stringify(self.t(key, **props)))
1412 self.showpatch(ctx.node(), matchfn)
1423 self.showpatch(ctx.node(), matchfn)
1413
1424
1414 if types['footer']:
1425 if types['footer']:
1415 if not self.footer:
1426 if not self.footer:
1416 self.footer = templater.stringify(self.t(types['footer'],
1427 self.footer = templater.stringify(self.t(types['footer'],
1417 **props))
1428 **props))
1418
1429
1419 except KeyError, inst:
1430 except KeyError, inst:
1420 msg = _("%s: no key named '%s'")
1431 msg = _("%s: no key named '%s'")
1421 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1432 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1422 except SyntaxError, inst:
1433 except SyntaxError, inst:
1423 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1434 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1424
1435
1425 def gettemplate(ui, tmpl, style):
1436 def gettemplate(ui, tmpl, style):
1426 """
1437 """
1427 Find the template matching the given template spec or style.
1438 Find the template matching the given template spec or style.
1428 """
1439 """
1429
1440
1430 # ui settings
1441 # ui settings
1431 if not tmpl and not style: # template are stronger than style
1442 if not tmpl and not style: # template are stronger than style
1432 tmpl = ui.config('ui', 'logtemplate')
1443 tmpl = ui.config('ui', 'logtemplate')
1433 if tmpl:
1444 if tmpl:
1434 try:
1445 try:
1435 tmpl = templater.parsestring(tmpl)
1446 tmpl = templater.parsestring(tmpl)
1436 except SyntaxError:
1447 except SyntaxError:
1437 tmpl = templater.parsestring(tmpl, quoted=False)
1448 tmpl = templater.parsestring(tmpl, quoted=False)
1438 return tmpl, None
1449 return tmpl, None
1439 else:
1450 else:
1440 style = util.expandpath(ui.config('ui', 'style', ''))
1451 style = util.expandpath(ui.config('ui', 'style', ''))
1441
1452
1442 if not tmpl and style:
1453 if not tmpl and style:
1443 mapfile = style
1454 mapfile = style
1444 if not os.path.split(mapfile)[0]:
1455 if not os.path.split(mapfile)[0]:
1445 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1456 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1446 or templater.templatepath(mapfile))
1457 or templater.templatepath(mapfile))
1447 if mapname:
1458 if mapname:
1448 mapfile = mapname
1459 mapfile = mapname
1449 return None, mapfile
1460 return None, mapfile
1450
1461
1451 if not tmpl:
1462 if not tmpl:
1452 return None, None
1463 return None, None
1453
1464
1454 # looks like a literal template?
1465 # looks like a literal template?
1455 if '{' in tmpl:
1466 if '{' in tmpl:
1456 return tmpl, None
1467 return tmpl, None
1457
1468
1458 # perhaps a stock style?
1469 # perhaps a stock style?
1459 if not os.path.split(tmpl)[0]:
1470 if not os.path.split(tmpl)[0]:
1460 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1471 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1461 or templater.templatepath(tmpl))
1472 or templater.templatepath(tmpl))
1462 if mapname and os.path.isfile(mapname):
1473 if mapname and os.path.isfile(mapname):
1463 return None, mapname
1474 return None, mapname
1464
1475
1465 # perhaps it's a reference to [templates]
1476 # perhaps it's a reference to [templates]
1466 t = ui.config('templates', tmpl)
1477 t = ui.config('templates', tmpl)
1467 if t:
1478 if t:
1468 try:
1479 try:
1469 tmpl = templater.parsestring(t)
1480 tmpl = templater.parsestring(t)
1470 except SyntaxError:
1481 except SyntaxError:
1471 tmpl = templater.parsestring(t, quoted=False)
1482 tmpl = templater.parsestring(t, quoted=False)
1472 return tmpl, None
1483 return tmpl, None
1473
1484
1474 if tmpl == 'list':
1485 if tmpl == 'list':
1475 ui.write(_("available styles: %s\n") % templater.stylelist())
1486 ui.write(_("available styles: %s\n") % templater.stylelist())
1476 raise util.Abort(_("specify a template"))
1487 raise util.Abort(_("specify a template"))
1477
1488
1478 # perhaps it's a path to a map or a template
1489 # perhaps it's a path to a map or a template
1479 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1490 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1480 # is it a mapfile for a style?
1491 # is it a mapfile for a style?
1481 if os.path.basename(tmpl).startswith("map-"):
1492 if os.path.basename(tmpl).startswith("map-"):
1482 return None, os.path.realpath(tmpl)
1493 return None, os.path.realpath(tmpl)
1483 tmpl = open(tmpl).read()
1494 tmpl = open(tmpl).read()
1484 return tmpl, None
1495 return tmpl, None
1485
1496
1486 # constant string?
1497 # constant string?
1487 return tmpl, None
1498 return tmpl, None
1488
1499
1489 def show_changeset(ui, repo, opts, buffered=False):
1500 def show_changeset(ui, repo, opts, buffered=False):
1490 """show one changeset using template or regular display.
1501 """show one changeset using template or regular display.
1491
1502
1492 Display format will be the first non-empty hit of:
1503 Display format will be the first non-empty hit of:
1493 1. option 'template'
1504 1. option 'template'
1494 2. option 'style'
1505 2. option 'style'
1495 3. [ui] setting 'logtemplate'
1506 3. [ui] setting 'logtemplate'
1496 4. [ui] setting 'style'
1507 4. [ui] setting 'style'
1497 If all of these values are either the unset or the empty string,
1508 If all of these values are either the unset or the empty string,
1498 regular display via changeset_printer() is done.
1509 regular display via changeset_printer() is done.
1499 """
1510 """
1500 # options
1511 # options
1501 matchfn = None
1512 matchfn = None
1502 if opts.get('patch') or opts.get('stat'):
1513 if opts.get('patch') or opts.get('stat'):
1503 matchfn = scmutil.matchall(repo)
1514 matchfn = scmutil.matchall(repo)
1504
1515
1505 if opts.get('template') == 'json':
1516 if opts.get('template') == 'json':
1506 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1517 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1507
1518
1508 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1519 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1509
1520
1510 if not tmpl and not mapfile:
1521 if not tmpl and not mapfile:
1511 return changeset_printer(ui, repo, matchfn, opts, buffered)
1522 return changeset_printer(ui, repo, matchfn, opts, buffered)
1512
1523
1513 try:
1524 try:
1514 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1525 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1515 buffered)
1526 buffered)
1516 except SyntaxError, inst:
1527 except SyntaxError, inst:
1517 raise util.Abort(inst.args[0])
1528 raise util.Abort(inst.args[0])
1518 return t
1529 return t
1519
1530
1520 def showmarker(ui, marker):
1531 def showmarker(ui, marker):
1521 """utility function to display obsolescence marker in a readable way
1532 """utility function to display obsolescence marker in a readable way
1522
1533
1523 To be used by debug function."""
1534 To be used by debug function."""
1524 ui.write(hex(marker.precnode()))
1535 ui.write(hex(marker.precnode()))
1525 for repl in marker.succnodes():
1536 for repl in marker.succnodes():
1526 ui.write(' ')
1537 ui.write(' ')
1527 ui.write(hex(repl))
1538 ui.write(hex(repl))
1528 ui.write(' %X ' % marker.flags())
1539 ui.write(' %X ' % marker.flags())
1529 parents = marker.parentnodes()
1540 parents = marker.parentnodes()
1530 if parents is not None:
1541 if parents is not None:
1531 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1542 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1532 ui.write('(%s) ' % util.datestr(marker.date()))
1543 ui.write('(%s) ' % util.datestr(marker.date()))
1533 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1544 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1534 sorted(marker.metadata().items())
1545 sorted(marker.metadata().items())
1535 if t[0] != 'date')))
1546 if t[0] != 'date')))
1536 ui.write('\n')
1547 ui.write('\n')
1537
1548
1538 def finddate(ui, repo, date):
1549 def finddate(ui, repo, date):
1539 """Find the tipmost changeset that matches the given date spec"""
1550 """Find the tipmost changeset that matches the given date spec"""
1540
1551
1541 df = util.matchdate(date)
1552 df = util.matchdate(date)
1542 m = scmutil.matchall(repo)
1553 m = scmutil.matchall(repo)
1543 results = {}
1554 results = {}
1544
1555
1545 def prep(ctx, fns):
1556 def prep(ctx, fns):
1546 d = ctx.date()
1557 d = ctx.date()
1547 if df(d[0]):
1558 if df(d[0]):
1548 results[ctx.rev()] = d
1559 results[ctx.rev()] = d
1549
1560
1550 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1561 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1551 rev = ctx.rev()
1562 rev = ctx.rev()
1552 if rev in results:
1563 if rev in results:
1553 ui.status(_("found revision %s from %s\n") %
1564 ui.status(_("found revision %s from %s\n") %
1554 (rev, util.datestr(results[rev])))
1565 (rev, util.datestr(results[rev])))
1555 return str(rev)
1566 return str(rev)
1556
1567
1557 raise util.Abort(_("revision matching date not found"))
1568 raise util.Abort(_("revision matching date not found"))
1558
1569
1559 def increasingwindows(windowsize=8, sizelimit=512):
1570 def increasingwindows(windowsize=8, sizelimit=512):
1560 while True:
1571 while True:
1561 yield windowsize
1572 yield windowsize
1562 if windowsize < sizelimit:
1573 if windowsize < sizelimit:
1563 windowsize *= 2
1574 windowsize *= 2
1564
1575
1565 class FileWalkError(Exception):
1576 class FileWalkError(Exception):
1566 pass
1577 pass
1567
1578
1568 def walkfilerevs(repo, match, follow, revs, fncache):
1579 def walkfilerevs(repo, match, follow, revs, fncache):
1569 '''Walks the file history for the matched files.
1580 '''Walks the file history for the matched files.
1570
1581
1571 Returns the changeset revs that are involved in the file history.
1582 Returns the changeset revs that are involved in the file history.
1572
1583
1573 Throws FileWalkError if the file history can't be walked using
1584 Throws FileWalkError if the file history can't be walked using
1574 filelogs alone.
1585 filelogs alone.
1575 '''
1586 '''
1576 wanted = set()
1587 wanted = set()
1577 copies = []
1588 copies = []
1578 minrev, maxrev = min(revs), max(revs)
1589 minrev, maxrev = min(revs), max(revs)
1579 def filerevgen(filelog, last):
1590 def filerevgen(filelog, last):
1580 """
1591 """
1581 Only files, no patterns. Check the history of each file.
1592 Only files, no patterns. Check the history of each file.
1582
1593
1583 Examines filelog entries within minrev, maxrev linkrev range
1594 Examines filelog entries within minrev, maxrev linkrev range
1584 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1595 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1585 tuples in backwards order
1596 tuples in backwards order
1586 """
1597 """
1587 cl_count = len(repo)
1598 cl_count = len(repo)
1588 revs = []
1599 revs = []
1589 for j in xrange(0, last + 1):
1600 for j in xrange(0, last + 1):
1590 linkrev = filelog.linkrev(j)
1601 linkrev = filelog.linkrev(j)
1591 if linkrev < minrev:
1602 if linkrev < minrev:
1592 continue
1603 continue
1593 # only yield rev for which we have the changelog, it can
1604 # only yield rev for which we have the changelog, it can
1594 # happen while doing "hg log" during a pull or commit
1605 # happen while doing "hg log" during a pull or commit
1595 if linkrev >= cl_count:
1606 if linkrev >= cl_count:
1596 break
1607 break
1597
1608
1598 parentlinkrevs = []
1609 parentlinkrevs = []
1599 for p in filelog.parentrevs(j):
1610 for p in filelog.parentrevs(j):
1600 if p != nullrev:
1611 if p != nullrev:
1601 parentlinkrevs.append(filelog.linkrev(p))
1612 parentlinkrevs.append(filelog.linkrev(p))
1602 n = filelog.node(j)
1613 n = filelog.node(j)
1603 revs.append((linkrev, parentlinkrevs,
1614 revs.append((linkrev, parentlinkrevs,
1604 follow and filelog.renamed(n)))
1615 follow and filelog.renamed(n)))
1605
1616
1606 return reversed(revs)
1617 return reversed(revs)
1607 def iterfiles():
1618 def iterfiles():
1608 pctx = repo['.']
1619 pctx = repo['.']
1609 for filename in match.files():
1620 for filename in match.files():
1610 if follow:
1621 if follow:
1611 if filename not in pctx:
1622 if filename not in pctx:
1612 raise util.Abort(_('cannot follow file not in parent '
1623 raise util.Abort(_('cannot follow file not in parent '
1613 'revision: "%s"') % filename)
1624 'revision: "%s"') % filename)
1614 yield filename, pctx[filename].filenode()
1625 yield filename, pctx[filename].filenode()
1615 else:
1626 else:
1616 yield filename, None
1627 yield filename, None
1617 for filename_node in copies:
1628 for filename_node in copies:
1618 yield filename_node
1629 yield filename_node
1619
1630
1620 for file_, node in iterfiles():
1631 for file_, node in iterfiles():
1621 filelog = repo.file(file_)
1632 filelog = repo.file(file_)
1622 if not len(filelog):
1633 if not len(filelog):
1623 if node is None:
1634 if node is None:
1624 # A zero count may be a directory or deleted file, so
1635 # A zero count may be a directory or deleted file, so
1625 # try to find matching entries on the slow path.
1636 # try to find matching entries on the slow path.
1626 if follow:
1637 if follow:
1627 raise util.Abort(
1638 raise util.Abort(
1628 _('cannot follow nonexistent file: "%s"') % file_)
1639 _('cannot follow nonexistent file: "%s"') % file_)
1629 raise FileWalkError("Cannot walk via filelog")
1640 raise FileWalkError("Cannot walk via filelog")
1630 else:
1641 else:
1631 continue
1642 continue
1632
1643
1633 if node is None:
1644 if node is None:
1634 last = len(filelog) - 1
1645 last = len(filelog) - 1
1635 else:
1646 else:
1636 last = filelog.rev(node)
1647 last = filelog.rev(node)
1637
1648
1638 # keep track of all ancestors of the file
1649 # keep track of all ancestors of the file
1639 ancestors = set([filelog.linkrev(last)])
1650 ancestors = set([filelog.linkrev(last)])
1640
1651
1641 # iterate from latest to oldest revision
1652 # iterate from latest to oldest revision
1642 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1653 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1643 if not follow:
1654 if not follow:
1644 if rev > maxrev:
1655 if rev > maxrev:
1645 continue
1656 continue
1646 else:
1657 else:
1647 # Note that last might not be the first interesting
1658 # Note that last might not be the first interesting
1648 # rev to us:
1659 # rev to us:
1649 # if the file has been changed after maxrev, we'll
1660 # if the file has been changed after maxrev, we'll
1650 # have linkrev(last) > maxrev, and we still need
1661 # have linkrev(last) > maxrev, and we still need
1651 # to explore the file graph
1662 # to explore the file graph
1652 if rev not in ancestors:
1663 if rev not in ancestors:
1653 continue
1664 continue
1654 # XXX insert 1327 fix here
1665 # XXX insert 1327 fix here
1655 if flparentlinkrevs:
1666 if flparentlinkrevs:
1656 ancestors.update(flparentlinkrevs)
1667 ancestors.update(flparentlinkrevs)
1657
1668
1658 fncache.setdefault(rev, []).append(file_)
1669 fncache.setdefault(rev, []).append(file_)
1659 wanted.add(rev)
1670 wanted.add(rev)
1660 if copied:
1671 if copied:
1661 copies.append(copied)
1672 copies.append(copied)
1662
1673
1663 return wanted
1674 return wanted
1664
1675
1665 class _followfilter(object):
1676 class _followfilter(object):
1666 def __init__(self, repo, onlyfirst=False):
1677 def __init__(self, repo, onlyfirst=False):
1667 self.repo = repo
1678 self.repo = repo
1668 self.startrev = nullrev
1679 self.startrev = nullrev
1669 self.roots = set()
1680 self.roots = set()
1670 self.onlyfirst = onlyfirst
1681 self.onlyfirst = onlyfirst
1671
1682
1672 def match(self, rev):
1683 def match(self, rev):
1673 def realparents(rev):
1684 def realparents(rev):
1674 if self.onlyfirst:
1685 if self.onlyfirst:
1675 return self.repo.changelog.parentrevs(rev)[0:1]
1686 return self.repo.changelog.parentrevs(rev)[0:1]
1676 else:
1687 else:
1677 return filter(lambda x: x != nullrev,
1688 return filter(lambda x: x != nullrev,
1678 self.repo.changelog.parentrevs(rev))
1689 self.repo.changelog.parentrevs(rev))
1679
1690
1680 if self.startrev == nullrev:
1691 if self.startrev == nullrev:
1681 self.startrev = rev
1692 self.startrev = rev
1682 return True
1693 return True
1683
1694
1684 if rev > self.startrev:
1695 if rev > self.startrev:
1685 # forward: all descendants
1696 # forward: all descendants
1686 if not self.roots:
1697 if not self.roots:
1687 self.roots.add(self.startrev)
1698 self.roots.add(self.startrev)
1688 for parent in realparents(rev):
1699 for parent in realparents(rev):
1689 if parent in self.roots:
1700 if parent in self.roots:
1690 self.roots.add(rev)
1701 self.roots.add(rev)
1691 return True
1702 return True
1692 else:
1703 else:
1693 # backwards: all parents
1704 # backwards: all parents
1694 if not self.roots:
1705 if not self.roots:
1695 self.roots.update(realparents(self.startrev))
1706 self.roots.update(realparents(self.startrev))
1696 if rev in self.roots:
1707 if rev in self.roots:
1697 self.roots.remove(rev)
1708 self.roots.remove(rev)
1698 self.roots.update(realparents(rev))
1709 self.roots.update(realparents(rev))
1699 return True
1710 return True
1700
1711
1701 return False
1712 return False
1702
1713
1703 def walkchangerevs(repo, match, opts, prepare):
1714 def walkchangerevs(repo, match, opts, prepare):
1704 '''Iterate over files and the revs in which they changed.
1715 '''Iterate over files and the revs in which they changed.
1705
1716
1706 Callers most commonly need to iterate backwards over the history
1717 Callers most commonly need to iterate backwards over the history
1707 in which they are interested. Doing so has awful (quadratic-looking)
1718 in which they are interested. Doing so has awful (quadratic-looking)
1708 performance, so we use iterators in a "windowed" way.
1719 performance, so we use iterators in a "windowed" way.
1709
1720
1710 We walk a window of revisions in the desired order. Within the
1721 We walk a window of revisions in the desired order. Within the
1711 window, we first walk forwards to gather data, then in the desired
1722 window, we first walk forwards to gather data, then in the desired
1712 order (usually backwards) to display it.
1723 order (usually backwards) to display it.
1713
1724
1714 This function returns an iterator yielding contexts. Before
1725 This function returns an iterator yielding contexts. Before
1715 yielding each context, the iterator will first call the prepare
1726 yielding each context, the iterator will first call the prepare
1716 function on each context in the window in forward order.'''
1727 function on each context in the window in forward order.'''
1717
1728
1718 follow = opts.get('follow') or opts.get('follow_first')
1729 follow = opts.get('follow') or opts.get('follow_first')
1719 revs = _logrevs(repo, opts)
1730 revs = _logrevs(repo, opts)
1720 if not revs:
1731 if not revs:
1721 return []
1732 return []
1722 wanted = set()
1733 wanted = set()
1723 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1734 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1724 fncache = {}
1735 fncache = {}
1725 change = repo.changectx
1736 change = repo.changectx
1726
1737
1727 # First step is to fill wanted, the set of revisions that we want to yield.
1738 # First step is to fill wanted, the set of revisions that we want to yield.
1728 # When it does not induce extra cost, we also fill fncache for revisions in
1739 # When it does not induce extra cost, we also fill fncache for revisions in
1729 # wanted: a cache of filenames that were changed (ctx.files()) and that
1740 # wanted: a cache of filenames that were changed (ctx.files()) and that
1730 # match the file filtering conditions.
1741 # match the file filtering conditions.
1731
1742
1732 if match.always():
1743 if match.always():
1733 # No files, no patterns. Display all revs.
1744 # No files, no patterns. Display all revs.
1734 wanted = revs
1745 wanted = revs
1735
1746
1736 if not slowpath and match.files():
1747 if not slowpath and match.files():
1737 # We only have to read through the filelog to find wanted revisions
1748 # We only have to read through the filelog to find wanted revisions
1738
1749
1739 try:
1750 try:
1740 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1751 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1741 except FileWalkError:
1752 except FileWalkError:
1742 slowpath = True
1753 slowpath = True
1743
1754
1744 # We decided to fall back to the slowpath because at least one
1755 # We decided to fall back to the slowpath because at least one
1745 # of the paths was not a file. Check to see if at least one of them
1756 # of the paths was not a file. Check to see if at least one of them
1746 # existed in history, otherwise simply return
1757 # existed in history, otherwise simply return
1747 for path in match.files():
1758 for path in match.files():
1748 if path == '.' or path in repo.store:
1759 if path == '.' or path in repo.store:
1749 break
1760 break
1750 else:
1761 else:
1751 return []
1762 return []
1752
1763
1753 if slowpath:
1764 if slowpath:
1754 # We have to read the changelog to match filenames against
1765 # We have to read the changelog to match filenames against
1755 # changed files
1766 # changed files
1756
1767
1757 if follow:
1768 if follow:
1758 raise util.Abort(_('can only follow copies/renames for explicit '
1769 raise util.Abort(_('can only follow copies/renames for explicit '
1759 'filenames'))
1770 'filenames'))
1760
1771
1761 # The slow path checks files modified in every changeset.
1772 # The slow path checks files modified in every changeset.
1762 # This is really slow on large repos, so compute the set lazily.
1773 # This is really slow on large repos, so compute the set lazily.
1763 class lazywantedset(object):
1774 class lazywantedset(object):
1764 def __init__(self):
1775 def __init__(self):
1765 self.set = set()
1776 self.set = set()
1766 self.revs = set(revs)
1777 self.revs = set(revs)
1767
1778
1768 # No need to worry about locality here because it will be accessed
1779 # No need to worry about locality here because it will be accessed
1769 # in the same order as the increasing window below.
1780 # in the same order as the increasing window below.
1770 def __contains__(self, value):
1781 def __contains__(self, value):
1771 if value in self.set:
1782 if value in self.set:
1772 return True
1783 return True
1773 elif not value in self.revs:
1784 elif not value in self.revs:
1774 return False
1785 return False
1775 else:
1786 else:
1776 self.revs.discard(value)
1787 self.revs.discard(value)
1777 ctx = change(value)
1788 ctx = change(value)
1778 matches = filter(match, ctx.files())
1789 matches = filter(match, ctx.files())
1779 if matches:
1790 if matches:
1780 fncache[value] = matches
1791 fncache[value] = matches
1781 self.set.add(value)
1792 self.set.add(value)
1782 return True
1793 return True
1783 return False
1794 return False
1784
1795
1785 def discard(self, value):
1796 def discard(self, value):
1786 self.revs.discard(value)
1797 self.revs.discard(value)
1787 self.set.discard(value)
1798 self.set.discard(value)
1788
1799
1789 wanted = lazywantedset()
1800 wanted = lazywantedset()
1790
1801
1791 # it might be worthwhile to do this in the iterator if the rev range
1802 # it might be worthwhile to do this in the iterator if the rev range
1792 # is descending and the prune args are all within that range
1803 # is descending and the prune args are all within that range
1793 for rev in opts.get('prune', ()):
1804 for rev in opts.get('prune', ()):
1794 rev = repo[rev].rev()
1805 rev = repo[rev].rev()
1795 ff = _followfilter(repo)
1806 ff = _followfilter(repo)
1796 stop = min(revs[0], revs[-1])
1807 stop = min(revs[0], revs[-1])
1797 for x in xrange(rev, stop - 1, -1):
1808 for x in xrange(rev, stop - 1, -1):
1798 if ff.match(x):
1809 if ff.match(x):
1799 wanted = wanted - [x]
1810 wanted = wanted - [x]
1800
1811
1801 # Now that wanted is correctly initialized, we can iterate over the
1812 # Now that wanted is correctly initialized, we can iterate over the
1802 # revision range, yielding only revisions in wanted.
1813 # revision range, yielding only revisions in wanted.
1803 def iterate():
1814 def iterate():
1804 if follow and not match.files():
1815 if follow and not match.files():
1805 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1816 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1806 def want(rev):
1817 def want(rev):
1807 return ff.match(rev) and rev in wanted
1818 return ff.match(rev) and rev in wanted
1808 else:
1819 else:
1809 def want(rev):
1820 def want(rev):
1810 return rev in wanted
1821 return rev in wanted
1811
1822
1812 it = iter(revs)
1823 it = iter(revs)
1813 stopiteration = False
1824 stopiteration = False
1814 for windowsize in increasingwindows():
1825 for windowsize in increasingwindows():
1815 nrevs = []
1826 nrevs = []
1816 for i in xrange(windowsize):
1827 for i in xrange(windowsize):
1817 try:
1828 try:
1818 rev = it.next()
1829 rev = it.next()
1819 if want(rev):
1830 if want(rev):
1820 nrevs.append(rev)
1831 nrevs.append(rev)
1821 except (StopIteration):
1832 except (StopIteration):
1822 stopiteration = True
1833 stopiteration = True
1823 break
1834 break
1824 for rev in sorted(nrevs):
1835 for rev in sorted(nrevs):
1825 fns = fncache.get(rev)
1836 fns = fncache.get(rev)
1826 ctx = change(rev)
1837 ctx = change(rev)
1827 if not fns:
1838 if not fns:
1828 def fns_generator():
1839 def fns_generator():
1829 for f in ctx.files():
1840 for f in ctx.files():
1830 if match(f):
1841 if match(f):
1831 yield f
1842 yield f
1832 fns = fns_generator()
1843 fns = fns_generator()
1833 prepare(ctx, fns)
1844 prepare(ctx, fns)
1834 for rev in nrevs:
1845 for rev in nrevs:
1835 yield change(rev)
1846 yield change(rev)
1836
1847
1837 if stopiteration:
1848 if stopiteration:
1838 break
1849 break
1839
1850
1840 return iterate()
1851 return iterate()
1841
1852
1842 def _makefollowlogfilematcher(repo, files, followfirst):
1853 def _makefollowlogfilematcher(repo, files, followfirst):
1843 # When displaying a revision with --patch --follow FILE, we have
1854 # When displaying a revision with --patch --follow FILE, we have
1844 # to know which file of the revision must be diffed. With
1855 # to know which file of the revision must be diffed. With
1845 # --follow, we want the names of the ancestors of FILE in the
1856 # --follow, we want the names of the ancestors of FILE in the
1846 # revision, stored in "fcache". "fcache" is populated by
1857 # revision, stored in "fcache". "fcache" is populated by
1847 # reproducing the graph traversal already done by --follow revset
1858 # reproducing the graph traversal already done by --follow revset
1848 # and relating linkrevs to file names (which is not "correct" but
1859 # and relating linkrevs to file names (which is not "correct" but
1849 # good enough).
1860 # good enough).
1850 fcache = {}
1861 fcache = {}
1851 fcacheready = [False]
1862 fcacheready = [False]
1852 pctx = repo['.']
1863 pctx = repo['.']
1853
1864
1854 def populate():
1865 def populate():
1855 for fn in files:
1866 for fn in files:
1856 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1867 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1857 for c in i:
1868 for c in i:
1858 fcache.setdefault(c.linkrev(), set()).add(c.path())
1869 fcache.setdefault(c.linkrev(), set()).add(c.path())
1859
1870
1860 def filematcher(rev):
1871 def filematcher(rev):
1861 if not fcacheready[0]:
1872 if not fcacheready[0]:
1862 # Lazy initialization
1873 # Lazy initialization
1863 fcacheready[0] = True
1874 fcacheready[0] = True
1864 populate()
1875 populate()
1865 return scmutil.matchfiles(repo, fcache.get(rev, []))
1876 return scmutil.matchfiles(repo, fcache.get(rev, []))
1866
1877
1867 return filematcher
1878 return filematcher
1868
1879
1869 def _makenofollowlogfilematcher(repo, pats, opts):
1880 def _makenofollowlogfilematcher(repo, pats, opts):
1870 '''hook for extensions to override the filematcher for non-follow cases'''
1881 '''hook for extensions to override the filematcher for non-follow cases'''
1871 return None
1882 return None
1872
1883
1873 def _makelogrevset(repo, pats, opts, revs):
1884 def _makelogrevset(repo, pats, opts, revs):
1874 """Return (expr, filematcher) where expr is a revset string built
1885 """Return (expr, filematcher) where expr is a revset string built
1875 from log options and file patterns or None. If --stat or --patch
1886 from log options and file patterns or None. If --stat or --patch
1876 are not passed filematcher is None. Otherwise it is a callable
1887 are not passed filematcher is None. Otherwise it is a callable
1877 taking a revision number and returning a match objects filtering
1888 taking a revision number and returning a match objects filtering
1878 the files to be detailed when displaying the revision.
1889 the files to be detailed when displaying the revision.
1879 """
1890 """
1880 opt2revset = {
1891 opt2revset = {
1881 'no_merges': ('not merge()', None),
1892 'no_merges': ('not merge()', None),
1882 'only_merges': ('merge()', None),
1893 'only_merges': ('merge()', None),
1883 '_ancestors': ('ancestors(%(val)s)', None),
1894 '_ancestors': ('ancestors(%(val)s)', None),
1884 '_fancestors': ('_firstancestors(%(val)s)', None),
1895 '_fancestors': ('_firstancestors(%(val)s)', None),
1885 '_descendants': ('descendants(%(val)s)', None),
1896 '_descendants': ('descendants(%(val)s)', None),
1886 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1897 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1887 '_matchfiles': ('_matchfiles(%(val)s)', None),
1898 '_matchfiles': ('_matchfiles(%(val)s)', None),
1888 'date': ('date(%(val)r)', None),
1899 'date': ('date(%(val)r)', None),
1889 'branch': ('branch(%(val)r)', ' or '),
1900 'branch': ('branch(%(val)r)', ' or '),
1890 '_patslog': ('filelog(%(val)r)', ' or '),
1901 '_patslog': ('filelog(%(val)r)', ' or '),
1891 '_patsfollow': ('follow(%(val)r)', ' or '),
1902 '_patsfollow': ('follow(%(val)r)', ' or '),
1892 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1903 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1893 'keyword': ('keyword(%(val)r)', ' or '),
1904 'keyword': ('keyword(%(val)r)', ' or '),
1894 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1905 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1895 'user': ('user(%(val)r)', ' or '),
1906 'user': ('user(%(val)r)', ' or '),
1896 }
1907 }
1897
1908
1898 opts = dict(opts)
1909 opts = dict(opts)
1899 # follow or not follow?
1910 # follow or not follow?
1900 follow = opts.get('follow') or opts.get('follow_first')
1911 follow = opts.get('follow') or opts.get('follow_first')
1901 if opts.get('follow_first'):
1912 if opts.get('follow_first'):
1902 followfirst = 1
1913 followfirst = 1
1903 else:
1914 else:
1904 followfirst = 0
1915 followfirst = 0
1905 # --follow with FILE behaviour depends on revs...
1916 # --follow with FILE behaviour depends on revs...
1906 it = iter(revs)
1917 it = iter(revs)
1907 startrev = it.next()
1918 startrev = it.next()
1908 try:
1919 try:
1909 followdescendants = startrev < it.next()
1920 followdescendants = startrev < it.next()
1910 except (StopIteration):
1921 except (StopIteration):
1911 followdescendants = False
1922 followdescendants = False
1912
1923
1913 # branch and only_branch are really aliases and must be handled at
1924 # branch and only_branch are really aliases and must be handled at
1914 # the same time
1925 # the same time
1915 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1926 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1916 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1927 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1917 # pats/include/exclude are passed to match.match() directly in
1928 # pats/include/exclude are passed to match.match() directly in
1918 # _matchfiles() revset but walkchangerevs() builds its matcher with
1929 # _matchfiles() revset but walkchangerevs() builds its matcher with
1919 # scmutil.match(). The difference is input pats are globbed on
1930 # scmutil.match(). The difference is input pats are globbed on
1920 # platforms without shell expansion (windows).
1931 # platforms without shell expansion (windows).
1921 wctx = repo[None]
1932 wctx = repo[None]
1922 match, pats = scmutil.matchandpats(wctx, pats, opts)
1933 match, pats = scmutil.matchandpats(wctx, pats, opts)
1923 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1934 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1924 if not slowpath:
1935 if not slowpath:
1925 for f in match.files():
1936 for f in match.files():
1926 if follow and f not in wctx:
1937 if follow and f not in wctx:
1927 # If the file exists, it may be a directory, so let it
1938 # If the file exists, it may be a directory, so let it
1928 # take the slow path.
1939 # take the slow path.
1929 if os.path.exists(repo.wjoin(f)):
1940 if os.path.exists(repo.wjoin(f)):
1930 slowpath = True
1941 slowpath = True
1931 continue
1942 continue
1932 else:
1943 else:
1933 raise util.Abort(_('cannot follow file not in parent '
1944 raise util.Abort(_('cannot follow file not in parent '
1934 'revision: "%s"') % f)
1945 'revision: "%s"') % f)
1935 filelog = repo.file(f)
1946 filelog = repo.file(f)
1936 if not filelog:
1947 if not filelog:
1937 # A zero count may be a directory or deleted file, so
1948 # A zero count may be a directory or deleted file, so
1938 # try to find matching entries on the slow path.
1949 # try to find matching entries on the slow path.
1939 if follow:
1950 if follow:
1940 raise util.Abort(
1951 raise util.Abort(
1941 _('cannot follow nonexistent file: "%s"') % f)
1952 _('cannot follow nonexistent file: "%s"') % f)
1942 slowpath = True
1953 slowpath = True
1943
1954
1944 # We decided to fall back to the slowpath because at least one
1955 # We decided to fall back to the slowpath because at least one
1945 # of the paths was not a file. Check to see if at least one of them
1956 # of the paths was not a file. Check to see if at least one of them
1946 # existed in history - in that case, we'll continue down the
1957 # existed in history - in that case, we'll continue down the
1947 # slowpath; otherwise, we can turn off the slowpath
1958 # slowpath; otherwise, we can turn off the slowpath
1948 if slowpath:
1959 if slowpath:
1949 for path in match.files():
1960 for path in match.files():
1950 if path == '.' or path in repo.store:
1961 if path == '.' or path in repo.store:
1951 break
1962 break
1952 else:
1963 else:
1953 slowpath = False
1964 slowpath = False
1954
1965
1955 fpats = ('_patsfollow', '_patsfollowfirst')
1966 fpats = ('_patsfollow', '_patsfollowfirst')
1956 fnopats = (('_ancestors', '_fancestors'),
1967 fnopats = (('_ancestors', '_fancestors'),
1957 ('_descendants', '_fdescendants'))
1968 ('_descendants', '_fdescendants'))
1958 if slowpath:
1969 if slowpath:
1959 # See walkchangerevs() slow path.
1970 # See walkchangerevs() slow path.
1960 #
1971 #
1961 # pats/include/exclude cannot be represented as separate
1972 # pats/include/exclude cannot be represented as separate
1962 # revset expressions as their filtering logic applies at file
1973 # revset expressions as their filtering logic applies at file
1963 # level. For instance "-I a -X a" matches a revision touching
1974 # level. For instance "-I a -X a" matches a revision touching
1964 # "a" and "b" while "file(a) and not file(b)" does
1975 # "a" and "b" while "file(a) and not file(b)" does
1965 # not. Besides, filesets are evaluated against the working
1976 # not. Besides, filesets are evaluated against the working
1966 # directory.
1977 # directory.
1967 matchargs = ['r:', 'd:relpath']
1978 matchargs = ['r:', 'd:relpath']
1968 for p in pats:
1979 for p in pats:
1969 matchargs.append('p:' + p)
1980 matchargs.append('p:' + p)
1970 for p in opts.get('include', []):
1981 for p in opts.get('include', []):
1971 matchargs.append('i:' + p)
1982 matchargs.append('i:' + p)
1972 for p in opts.get('exclude', []):
1983 for p in opts.get('exclude', []):
1973 matchargs.append('x:' + p)
1984 matchargs.append('x:' + p)
1974 matchargs = ','.join(('%r' % p) for p in matchargs)
1985 matchargs = ','.join(('%r' % p) for p in matchargs)
1975 opts['_matchfiles'] = matchargs
1986 opts['_matchfiles'] = matchargs
1976 if follow:
1987 if follow:
1977 opts[fnopats[0][followfirst]] = '.'
1988 opts[fnopats[0][followfirst]] = '.'
1978 else:
1989 else:
1979 if follow:
1990 if follow:
1980 if pats:
1991 if pats:
1981 # follow() revset interprets its file argument as a
1992 # follow() revset interprets its file argument as a
1982 # manifest entry, so use match.files(), not pats.
1993 # manifest entry, so use match.files(), not pats.
1983 opts[fpats[followfirst]] = list(match.files())
1994 opts[fpats[followfirst]] = list(match.files())
1984 else:
1995 else:
1985 op = fnopats[followdescendants][followfirst]
1996 op = fnopats[followdescendants][followfirst]
1986 opts[op] = 'rev(%d)' % startrev
1997 opts[op] = 'rev(%d)' % startrev
1987 else:
1998 else:
1988 opts['_patslog'] = list(pats)
1999 opts['_patslog'] = list(pats)
1989
2000
1990 filematcher = None
2001 filematcher = None
1991 if opts.get('patch') or opts.get('stat'):
2002 if opts.get('patch') or opts.get('stat'):
1992 # When following files, track renames via a special matcher.
2003 # When following files, track renames via a special matcher.
1993 # If we're forced to take the slowpath it means we're following
2004 # If we're forced to take the slowpath it means we're following
1994 # at least one pattern/directory, so don't bother with rename tracking.
2005 # at least one pattern/directory, so don't bother with rename tracking.
1995 if follow and not match.always() and not slowpath:
2006 if follow and not match.always() and not slowpath:
1996 # _makefollowlogfilematcher expects its files argument to be
2007 # _makefollowlogfilematcher expects its files argument to be
1997 # relative to the repo root, so use match.files(), not pats.
2008 # relative to the repo root, so use match.files(), not pats.
1998 filematcher = _makefollowlogfilematcher(repo, match.files(),
2009 filematcher = _makefollowlogfilematcher(repo, match.files(),
1999 followfirst)
2010 followfirst)
2000 else:
2011 else:
2001 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2012 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2002 if filematcher is None:
2013 if filematcher is None:
2003 filematcher = lambda rev: match
2014 filematcher = lambda rev: match
2004
2015
2005 expr = []
2016 expr = []
2006 for op, val in sorted(opts.iteritems()):
2017 for op, val in sorted(opts.iteritems()):
2007 if not val:
2018 if not val:
2008 continue
2019 continue
2009 if op not in opt2revset:
2020 if op not in opt2revset:
2010 continue
2021 continue
2011 revop, andor = opt2revset[op]
2022 revop, andor = opt2revset[op]
2012 if '%(val)' not in revop:
2023 if '%(val)' not in revop:
2013 expr.append(revop)
2024 expr.append(revop)
2014 else:
2025 else:
2015 if not isinstance(val, list):
2026 if not isinstance(val, list):
2016 e = revop % {'val': val}
2027 e = revop % {'val': val}
2017 else:
2028 else:
2018 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2029 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2019 expr.append(e)
2030 expr.append(e)
2020
2031
2021 if expr:
2032 if expr:
2022 expr = '(' + ' and '.join(expr) + ')'
2033 expr = '(' + ' and '.join(expr) + ')'
2023 else:
2034 else:
2024 expr = None
2035 expr = None
2025 return expr, filematcher
2036 return expr, filematcher
2026
2037
2027 def _logrevs(repo, opts):
2038 def _logrevs(repo, opts):
2028 # Default --rev value depends on --follow but --follow behaviour
2039 # Default --rev value depends on --follow but --follow behaviour
2029 # depends on revisions resolved from --rev...
2040 # depends on revisions resolved from --rev...
2030 follow = opts.get('follow') or opts.get('follow_first')
2041 follow = opts.get('follow') or opts.get('follow_first')
2031 if opts.get('rev'):
2042 if opts.get('rev'):
2032 revs = scmutil.revrange(repo, opts['rev'])
2043 revs = scmutil.revrange(repo, opts['rev'])
2033 elif follow and repo.dirstate.p1() == nullid:
2044 elif follow and repo.dirstate.p1() == nullid:
2034 revs = revset.baseset()
2045 revs = revset.baseset()
2035 elif follow:
2046 elif follow:
2036 revs = repo.revs('reverse(:.)')
2047 revs = repo.revs('reverse(:.)')
2037 else:
2048 else:
2038 revs = revset.spanset(repo)
2049 revs = revset.spanset(repo)
2039 revs.reverse()
2050 revs.reverse()
2040 return revs
2051 return revs
2041
2052
2042 def getgraphlogrevs(repo, pats, opts):
2053 def getgraphlogrevs(repo, pats, opts):
2043 """Return (revs, expr, filematcher) where revs is an iterable of
2054 """Return (revs, expr, filematcher) where revs is an iterable of
2044 revision numbers, expr is a revset string built from log options
2055 revision numbers, expr is a revset string built from log options
2045 and file patterns or None, and used to filter 'revs'. If --stat or
2056 and file patterns or None, and used to filter 'revs'. If --stat or
2046 --patch are not passed filematcher is None. Otherwise it is a
2057 --patch are not passed filematcher is None. Otherwise it is a
2047 callable taking a revision number and returning a match objects
2058 callable taking a revision number and returning a match objects
2048 filtering the files to be detailed when displaying the revision.
2059 filtering the files to be detailed when displaying the revision.
2049 """
2060 """
2050 limit = loglimit(opts)
2061 limit = loglimit(opts)
2051 revs = _logrevs(repo, opts)
2062 revs = _logrevs(repo, opts)
2052 if not revs:
2063 if not revs:
2053 return revset.baseset(), None, None
2064 return revset.baseset(), None, None
2054 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2065 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2055 if opts.get('rev'):
2066 if opts.get('rev'):
2056 # User-specified revs might be unsorted, but don't sort before
2067 # User-specified revs might be unsorted, but don't sort before
2057 # _makelogrevset because it might depend on the order of revs
2068 # _makelogrevset because it might depend on the order of revs
2058 revs.sort(reverse=True)
2069 revs.sort(reverse=True)
2059 if expr:
2070 if expr:
2060 # Revset matchers often operate faster on revisions in changelog
2071 # Revset matchers often operate faster on revisions in changelog
2061 # order, because most filters deal with the changelog.
2072 # order, because most filters deal with the changelog.
2062 revs.reverse()
2073 revs.reverse()
2063 matcher = revset.match(repo.ui, expr)
2074 matcher = revset.match(repo.ui, expr)
2064 # Revset matches can reorder revisions. "A or B" typically returns
2075 # Revset matches can reorder revisions. "A or B" typically returns
2065 # returns the revision matching A then the revision matching B. Sort
2076 # returns the revision matching A then the revision matching B. Sort
2066 # again to fix that.
2077 # again to fix that.
2067 revs = matcher(repo, revs)
2078 revs = matcher(repo, revs)
2068 revs.sort(reverse=True)
2079 revs.sort(reverse=True)
2069 if limit is not None:
2080 if limit is not None:
2070 limitedrevs = []
2081 limitedrevs = []
2071 for idx, rev in enumerate(revs):
2082 for idx, rev in enumerate(revs):
2072 if idx >= limit:
2083 if idx >= limit:
2073 break
2084 break
2074 limitedrevs.append(rev)
2085 limitedrevs.append(rev)
2075 revs = revset.baseset(limitedrevs)
2086 revs = revset.baseset(limitedrevs)
2076
2087
2077 return revs, expr, filematcher
2088 return revs, expr, filematcher
2078
2089
2079 def getlogrevs(repo, pats, opts):
2090 def getlogrevs(repo, pats, opts):
2080 """Return (revs, expr, filematcher) where revs is an iterable of
2091 """Return (revs, expr, filematcher) where revs is an iterable of
2081 revision numbers, expr is a revset string built from log options
2092 revision numbers, expr is a revset string built from log options
2082 and file patterns or None, and used to filter 'revs'. If --stat or
2093 and file patterns or None, and used to filter 'revs'. If --stat or
2083 --patch are not passed filematcher is None. Otherwise it is a
2094 --patch are not passed filematcher is None. Otherwise it is a
2084 callable taking a revision number and returning a match objects
2095 callable taking a revision number and returning a match objects
2085 filtering the files to be detailed when displaying the revision.
2096 filtering the files to be detailed when displaying the revision.
2086 """
2097 """
2087 limit = loglimit(opts)
2098 limit = loglimit(opts)
2088 revs = _logrevs(repo, opts)
2099 revs = _logrevs(repo, opts)
2089 if not revs:
2100 if not revs:
2090 return revset.baseset([]), None, None
2101 return revset.baseset([]), None, None
2091 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2102 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2092 if expr:
2103 if expr:
2093 # Revset matchers often operate faster on revisions in changelog
2104 # Revset matchers often operate faster on revisions in changelog
2094 # order, because most filters deal with the changelog.
2105 # order, because most filters deal with the changelog.
2095 if not opts.get('rev'):
2106 if not opts.get('rev'):
2096 revs.reverse()
2107 revs.reverse()
2097 matcher = revset.match(repo.ui, expr)
2108 matcher = revset.match(repo.ui, expr)
2098 # Revset matches can reorder revisions. "A or B" typically returns
2109 # Revset matches can reorder revisions. "A or B" typically returns
2099 # returns the revision matching A then the revision matching B. Sort
2110 # returns the revision matching A then the revision matching B. Sort
2100 # again to fix that.
2111 # again to fix that.
2101 revs = matcher(repo, revs)
2112 revs = matcher(repo, revs)
2102 if not opts.get('rev'):
2113 if not opts.get('rev'):
2103 revs.sort(reverse=True)
2114 revs.sort(reverse=True)
2104 if limit is not None:
2115 if limit is not None:
2105 count = 0
2116 count = 0
2106 limitedrevs = []
2117 limitedrevs = []
2107 it = iter(revs)
2118 it = iter(revs)
2108 while count < limit:
2119 while count < limit:
2109 try:
2120 try:
2110 limitedrevs.append(it.next())
2121 limitedrevs.append(it.next())
2111 except (StopIteration):
2122 except (StopIteration):
2112 break
2123 break
2113 count += 1
2124 count += 1
2114 revs = revset.baseset(limitedrevs)
2125 revs = revset.baseset(limitedrevs)
2115
2126
2116 return revs, expr, filematcher
2127 return revs, expr, filematcher
2117
2128
2118 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2129 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2119 filematcher=None):
2130 filematcher=None):
2120 seen, state = [], graphmod.asciistate()
2131 seen, state = [], graphmod.asciistate()
2121 for rev, type, ctx, parents in dag:
2132 for rev, type, ctx, parents in dag:
2122 char = 'o'
2133 char = 'o'
2123 if ctx.node() in showparents:
2134 if ctx.node() in showparents:
2124 char = '@'
2135 char = '@'
2125 elif ctx.obsolete():
2136 elif ctx.obsolete():
2126 char = 'x'
2137 char = 'x'
2127 elif ctx.closesbranch():
2138 elif ctx.closesbranch():
2128 char = '_'
2139 char = '_'
2129 copies = None
2140 copies = None
2130 if getrenamed and ctx.rev():
2141 if getrenamed and ctx.rev():
2131 copies = []
2142 copies = []
2132 for fn in ctx.files():
2143 for fn in ctx.files():
2133 rename = getrenamed(fn, ctx.rev())
2144 rename = getrenamed(fn, ctx.rev())
2134 if rename:
2145 if rename:
2135 copies.append((fn, rename[0]))
2146 copies.append((fn, rename[0]))
2136 revmatchfn = None
2147 revmatchfn = None
2137 if filematcher is not None:
2148 if filematcher is not None:
2138 revmatchfn = filematcher(ctx.rev())
2149 revmatchfn = filematcher(ctx.rev())
2139 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2150 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2140 lines = displayer.hunk.pop(rev).split('\n')
2151 lines = displayer.hunk.pop(rev).split('\n')
2141 if not lines[-1]:
2152 if not lines[-1]:
2142 del lines[-1]
2153 del lines[-1]
2143 displayer.flush(rev)
2154 displayer.flush(rev)
2144 edges = edgefn(type, char, lines, seen, rev, parents)
2155 edges = edgefn(type, char, lines, seen, rev, parents)
2145 for type, char, lines, coldata in edges:
2156 for type, char, lines, coldata in edges:
2146 graphmod.ascii(ui, state, type, char, lines, coldata)
2157 graphmod.ascii(ui, state, type, char, lines, coldata)
2147 displayer.close()
2158 displayer.close()
2148
2159
2149 def graphlog(ui, repo, *pats, **opts):
2160 def graphlog(ui, repo, *pats, **opts):
2150 # Parameters are identical to log command ones
2161 # Parameters are identical to log command ones
2151 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2162 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2152 revdag = graphmod.dagwalker(repo, revs)
2163 revdag = graphmod.dagwalker(repo, revs)
2153
2164
2154 getrenamed = None
2165 getrenamed = None
2155 if opts.get('copies'):
2166 if opts.get('copies'):
2156 endrev = None
2167 endrev = None
2157 if opts.get('rev'):
2168 if opts.get('rev'):
2158 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2169 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2159 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2170 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2160 displayer = show_changeset(ui, repo, opts, buffered=True)
2171 displayer = show_changeset(ui, repo, opts, buffered=True)
2161 showparents = [ctx.node() for ctx in repo[None].parents()]
2172 showparents = [ctx.node() for ctx in repo[None].parents()]
2162 displaygraph(ui, revdag, displayer, showparents,
2173 displaygraph(ui, revdag, displayer, showparents,
2163 graphmod.asciiedges, getrenamed, filematcher)
2174 graphmod.asciiedges, getrenamed, filematcher)
2164
2175
2165 def checkunsupportedgraphflags(pats, opts):
2176 def checkunsupportedgraphflags(pats, opts):
2166 for op in ["newest_first"]:
2177 for op in ["newest_first"]:
2167 if op in opts and opts[op]:
2178 if op in opts and opts[op]:
2168 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2179 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2169 % op.replace("_", "-"))
2180 % op.replace("_", "-"))
2170
2181
2171 def graphrevs(repo, nodes, opts):
2182 def graphrevs(repo, nodes, opts):
2172 limit = loglimit(opts)
2183 limit = loglimit(opts)
2173 nodes.reverse()
2184 nodes.reverse()
2174 if limit is not None:
2185 if limit is not None:
2175 nodes = nodes[:limit]
2186 nodes = nodes[:limit]
2176 return graphmod.nodes(repo, nodes)
2187 return graphmod.nodes(repo, nodes)
2177
2188
2178 def add(ui, repo, match, prefix, explicitonly, **opts):
2189 def add(ui, repo, match, prefix, explicitonly, **opts):
2179 join = lambda f: os.path.join(prefix, f)
2190 join = lambda f: os.path.join(prefix, f)
2180 bad = []
2191 bad = []
2181 oldbad = match.bad
2192 oldbad = match.bad
2182 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2193 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2183 names = []
2194 names = []
2184 wctx = repo[None]
2195 wctx = repo[None]
2185 cca = None
2196 cca = None
2186 abort, warn = scmutil.checkportabilityalert(ui)
2197 abort, warn = scmutil.checkportabilityalert(ui)
2187 if abort or warn:
2198 if abort or warn:
2188 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2199 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2189 for f in wctx.walk(match):
2200 for f in wctx.walk(match):
2190 exact = match.exact(f)
2201 exact = match.exact(f)
2191 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2202 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2192 if cca:
2203 if cca:
2193 cca(f)
2204 cca(f)
2194 names.append(f)
2205 names.append(f)
2195 if ui.verbose or not exact:
2206 if ui.verbose or not exact:
2196 ui.status(_('adding %s\n') % match.rel(f))
2207 ui.status(_('adding %s\n') % match.rel(f))
2197
2208
2198 for subpath in sorted(wctx.substate):
2209 for subpath in sorted(wctx.substate):
2199 sub = wctx.sub(subpath)
2210 sub = wctx.sub(subpath)
2200 try:
2211 try:
2201 submatch = matchmod.narrowmatcher(subpath, match)
2212 submatch = matchmod.narrowmatcher(subpath, match)
2202 if opts.get('subrepos'):
2213 if opts.get('subrepos'):
2203 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2214 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2204 else:
2215 else:
2205 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2216 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2206 except error.LookupError:
2217 except error.LookupError:
2207 ui.status(_("skipping missing subrepository: %s\n")
2218 ui.status(_("skipping missing subrepository: %s\n")
2208 % join(subpath))
2219 % join(subpath))
2209
2220
2210 if not opts.get('dry_run'):
2221 if not opts.get('dry_run'):
2211 rejected = wctx.add(names, prefix)
2222 rejected = wctx.add(names, prefix)
2212 bad.extend(f for f in rejected if f in match.files())
2223 bad.extend(f for f in rejected if f in match.files())
2213 return bad
2224 return bad
2214
2225
2215 def forget(ui, repo, match, prefix, explicitonly):
2226 def forget(ui, repo, match, prefix, explicitonly):
2216 join = lambda f: os.path.join(prefix, f)
2227 join = lambda f: os.path.join(prefix, f)
2217 bad = []
2228 bad = []
2218 oldbad = match.bad
2229 oldbad = match.bad
2219 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2230 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2220 wctx = repo[None]
2231 wctx = repo[None]
2221 forgot = []
2232 forgot = []
2222 s = repo.status(match=match, clean=True)
2233 s = repo.status(match=match, clean=True)
2223 forget = sorted(s[0] + s[1] + s[3] + s[6])
2234 forget = sorted(s[0] + s[1] + s[3] + s[6])
2224 if explicitonly:
2235 if explicitonly:
2225 forget = [f for f in forget if match.exact(f)]
2236 forget = [f for f in forget if match.exact(f)]
2226
2237
2227 for subpath in sorted(wctx.substate):
2238 for subpath in sorted(wctx.substate):
2228 sub = wctx.sub(subpath)
2239 sub = wctx.sub(subpath)
2229 try:
2240 try:
2230 submatch = matchmod.narrowmatcher(subpath, match)
2241 submatch = matchmod.narrowmatcher(subpath, match)
2231 subbad, subforgot = sub.forget(submatch, prefix)
2242 subbad, subforgot = sub.forget(submatch, prefix)
2232 bad.extend([subpath + '/' + f for f in subbad])
2243 bad.extend([subpath + '/' + f for f in subbad])
2233 forgot.extend([subpath + '/' + f for f in subforgot])
2244 forgot.extend([subpath + '/' + f for f in subforgot])
2234 except error.LookupError:
2245 except error.LookupError:
2235 ui.status(_("skipping missing subrepository: %s\n")
2246 ui.status(_("skipping missing subrepository: %s\n")
2236 % join(subpath))
2247 % join(subpath))
2237
2248
2238 if not explicitonly:
2249 if not explicitonly:
2239 for f in match.files():
2250 for f in match.files():
2240 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2251 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2241 if f not in forgot:
2252 if f not in forgot:
2242 if repo.wvfs.exists(f):
2253 if repo.wvfs.exists(f):
2243 # Don't complain if the exact case match wasn't given.
2254 # Don't complain if the exact case match wasn't given.
2244 # But don't do this until after checking 'forgot', so
2255 # But don't do this until after checking 'forgot', so
2245 # that subrepo files aren't normalized, and this op is
2256 # that subrepo files aren't normalized, and this op is
2246 # purely from data cached by the status walk above.
2257 # purely from data cached by the status walk above.
2247 if repo.dirstate.normalize(f) in repo.dirstate:
2258 if repo.dirstate.normalize(f) in repo.dirstate:
2248 continue
2259 continue
2249 ui.warn(_('not removing %s: '
2260 ui.warn(_('not removing %s: '
2250 'file is already untracked\n')
2261 'file is already untracked\n')
2251 % match.rel(f))
2262 % match.rel(f))
2252 bad.append(f)
2263 bad.append(f)
2253
2264
2254 for f in forget:
2265 for f in forget:
2255 if ui.verbose or not match.exact(f):
2266 if ui.verbose or not match.exact(f):
2256 ui.status(_('removing %s\n') % match.rel(f))
2267 ui.status(_('removing %s\n') % match.rel(f))
2257
2268
2258 rejected = wctx.forget(forget, prefix)
2269 rejected = wctx.forget(forget, prefix)
2259 bad.extend(f for f in rejected if f in match.files())
2270 bad.extend(f for f in rejected if f in match.files())
2260 forgot.extend(f for f in forget if f not in rejected)
2271 forgot.extend(f for f in forget if f not in rejected)
2261 return bad, forgot
2272 return bad, forgot
2262
2273
2263 def files(ui, ctx, m, fm, fmt, subrepos):
2274 def files(ui, ctx, m, fm, fmt, subrepos):
2264 rev = ctx.rev()
2275 rev = ctx.rev()
2265 ret = 1
2276 ret = 1
2266 ds = ctx.repo().dirstate
2277 ds = ctx.repo().dirstate
2267
2278
2268 for f in ctx.matches(m):
2279 for f in ctx.matches(m):
2269 if rev is None and ds[f] == 'r':
2280 if rev is None and ds[f] == 'r':
2270 continue
2281 continue
2271 fm.startitem()
2282 fm.startitem()
2272 if ui.verbose:
2283 if ui.verbose:
2273 fc = ctx[f]
2284 fc = ctx[f]
2274 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2285 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2275 fm.data(abspath=f)
2286 fm.data(abspath=f)
2276 fm.write('path', fmt, m.rel(f))
2287 fm.write('path', fmt, m.rel(f))
2277 ret = 0
2288 ret = 0
2278
2289
2279 if subrepos:
2290 if subrepos:
2280 for subpath in sorted(ctx.substate):
2291 for subpath in sorted(ctx.substate):
2281 sub = ctx.sub(subpath)
2292 sub = ctx.sub(subpath)
2282 try:
2293 try:
2283 submatch = matchmod.narrowmatcher(subpath, m)
2294 submatch = matchmod.narrowmatcher(subpath, m)
2284 if sub.printfiles(ui, submatch, fm, fmt) == 0:
2295 if sub.printfiles(ui, submatch, fm, fmt) == 0:
2285 ret = 0
2296 ret = 0
2286 except error.LookupError:
2297 except error.LookupError:
2287 ui.status(_("skipping missing subrepository: %s\n")
2298 ui.status(_("skipping missing subrepository: %s\n")
2288 % m.abs(subpath))
2299 % m.abs(subpath))
2289
2300
2290 return ret
2301 return ret
2291
2302
2292 def remove(ui, repo, m, prefix, after, force, subrepos):
2303 def remove(ui, repo, m, prefix, after, force, subrepos):
2293 join = lambda f: os.path.join(prefix, f)
2304 join = lambda f: os.path.join(prefix, f)
2294 ret = 0
2305 ret = 0
2295 s = repo.status(match=m, clean=True)
2306 s = repo.status(match=m, clean=True)
2296 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2307 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2297
2308
2298 wctx = repo[None]
2309 wctx = repo[None]
2299
2310
2300 for subpath in sorted(wctx.substate):
2311 for subpath in sorted(wctx.substate):
2301 def matchessubrepo(matcher, subpath):
2312 def matchessubrepo(matcher, subpath):
2302 if matcher.exact(subpath):
2313 if matcher.exact(subpath):
2303 return True
2314 return True
2304 for f in matcher.files():
2315 for f in matcher.files():
2305 if f.startswith(subpath):
2316 if f.startswith(subpath):
2306 return True
2317 return True
2307 return False
2318 return False
2308
2319
2309 if subrepos or matchessubrepo(m, subpath):
2320 if subrepos or matchessubrepo(m, subpath):
2310 sub = wctx.sub(subpath)
2321 sub = wctx.sub(subpath)
2311 try:
2322 try:
2312 submatch = matchmod.narrowmatcher(subpath, m)
2323 submatch = matchmod.narrowmatcher(subpath, m)
2313 if sub.removefiles(submatch, prefix, after, force, subrepos):
2324 if sub.removefiles(submatch, prefix, after, force, subrepos):
2314 ret = 1
2325 ret = 1
2315 except error.LookupError:
2326 except error.LookupError:
2316 ui.status(_("skipping missing subrepository: %s\n")
2327 ui.status(_("skipping missing subrepository: %s\n")
2317 % join(subpath))
2328 % join(subpath))
2318
2329
2319 # warn about failure to delete explicit files/dirs
2330 # warn about failure to delete explicit files/dirs
2320 deleteddirs = util.dirs(deleted)
2331 deleteddirs = util.dirs(deleted)
2321 for f in m.files():
2332 for f in m.files():
2322 def insubrepo():
2333 def insubrepo():
2323 for subpath in wctx.substate:
2334 for subpath in wctx.substate:
2324 if f.startswith(subpath):
2335 if f.startswith(subpath):
2325 return True
2336 return True
2326 return False
2337 return False
2327
2338
2328 isdir = f in deleteddirs or f in wctx.dirs()
2339 isdir = f in deleteddirs or f in wctx.dirs()
2329 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2340 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2330 continue
2341 continue
2331
2342
2332 if repo.wvfs.exists(f):
2343 if repo.wvfs.exists(f):
2333 if repo.wvfs.isdir(f):
2344 if repo.wvfs.isdir(f):
2334 ui.warn(_('not removing %s: no tracked files\n')
2345 ui.warn(_('not removing %s: no tracked files\n')
2335 % m.rel(f))
2346 % m.rel(f))
2336 else:
2347 else:
2337 ui.warn(_('not removing %s: file is untracked\n')
2348 ui.warn(_('not removing %s: file is untracked\n')
2338 % m.rel(f))
2349 % m.rel(f))
2339 # missing files will generate a warning elsewhere
2350 # missing files will generate a warning elsewhere
2340 ret = 1
2351 ret = 1
2341
2352
2342 if force:
2353 if force:
2343 list = modified + deleted + clean + added
2354 list = modified + deleted + clean + added
2344 elif after:
2355 elif after:
2345 list = deleted
2356 list = deleted
2346 for f in modified + added + clean:
2357 for f in modified + added + clean:
2347 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2358 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2348 ret = 1
2359 ret = 1
2349 else:
2360 else:
2350 list = deleted + clean
2361 list = deleted + clean
2351 for f in modified:
2362 for f in modified:
2352 ui.warn(_('not removing %s: file is modified (use -f'
2363 ui.warn(_('not removing %s: file is modified (use -f'
2353 ' to force removal)\n') % m.rel(f))
2364 ' to force removal)\n') % m.rel(f))
2354 ret = 1
2365 ret = 1
2355 for f in added:
2366 for f in added:
2356 ui.warn(_('not removing %s: file has been marked for add'
2367 ui.warn(_('not removing %s: file has been marked for add'
2357 ' (use forget to undo)\n') % m.rel(f))
2368 ' (use forget to undo)\n') % m.rel(f))
2358 ret = 1
2369 ret = 1
2359
2370
2360 for f in sorted(list):
2371 for f in sorted(list):
2361 if ui.verbose or not m.exact(f):
2372 if ui.verbose or not m.exact(f):
2362 ui.status(_('removing %s\n') % m.rel(f))
2373 ui.status(_('removing %s\n') % m.rel(f))
2363
2374
2364 wlock = repo.wlock()
2375 wlock = repo.wlock()
2365 try:
2376 try:
2366 if not after:
2377 if not after:
2367 for f in list:
2378 for f in list:
2368 if f in added:
2379 if f in added:
2369 continue # we never unlink added files on remove
2380 continue # we never unlink added files on remove
2370 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2381 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2371 repo[None].forget(list)
2382 repo[None].forget(list)
2372 finally:
2383 finally:
2373 wlock.release()
2384 wlock.release()
2374
2385
2375 return ret
2386 return ret
2376
2387
2377 def cat(ui, repo, ctx, matcher, prefix, **opts):
2388 def cat(ui, repo, ctx, matcher, prefix, **opts):
2378 err = 1
2389 err = 1
2379
2390
2380 def write(path):
2391 def write(path):
2381 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2392 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2382 pathname=os.path.join(prefix, path))
2393 pathname=os.path.join(prefix, path))
2383 data = ctx[path].data()
2394 data = ctx[path].data()
2384 if opts.get('decode'):
2395 if opts.get('decode'):
2385 data = repo.wwritedata(path, data)
2396 data = repo.wwritedata(path, data)
2386 fp.write(data)
2397 fp.write(data)
2387 fp.close()
2398 fp.close()
2388
2399
2389 # Automation often uses hg cat on single files, so special case it
2400 # Automation often uses hg cat on single files, so special case it
2390 # for performance to avoid the cost of parsing the manifest.
2401 # for performance to avoid the cost of parsing the manifest.
2391 if len(matcher.files()) == 1 and not matcher.anypats():
2402 if len(matcher.files()) == 1 and not matcher.anypats():
2392 file = matcher.files()[0]
2403 file = matcher.files()[0]
2393 mf = repo.manifest
2404 mf = repo.manifest
2394 mfnode = ctx.manifestnode()
2405 mfnode = ctx.manifestnode()
2395 if mfnode and mf.find(mfnode, file)[0]:
2406 if mfnode and mf.find(mfnode, file)[0]:
2396 write(file)
2407 write(file)
2397 return 0
2408 return 0
2398
2409
2399 # Don't warn about "missing" files that are really in subrepos
2410 # Don't warn about "missing" files that are really in subrepos
2400 bad = matcher.bad
2411 bad = matcher.bad
2401
2412
2402 def badfn(path, msg):
2413 def badfn(path, msg):
2403 for subpath in ctx.substate:
2414 for subpath in ctx.substate:
2404 if path.startswith(subpath):
2415 if path.startswith(subpath):
2405 return
2416 return
2406 bad(path, msg)
2417 bad(path, msg)
2407
2418
2408 matcher.bad = badfn
2419 matcher.bad = badfn
2409
2420
2410 for abs in ctx.walk(matcher):
2421 for abs in ctx.walk(matcher):
2411 write(abs)
2422 write(abs)
2412 err = 0
2423 err = 0
2413
2424
2414 matcher.bad = bad
2425 matcher.bad = bad
2415
2426
2416 for subpath in sorted(ctx.substate):
2427 for subpath in sorted(ctx.substate):
2417 sub = ctx.sub(subpath)
2428 sub = ctx.sub(subpath)
2418 try:
2429 try:
2419 submatch = matchmod.narrowmatcher(subpath, matcher)
2430 submatch = matchmod.narrowmatcher(subpath, matcher)
2420
2431
2421 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2432 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2422 **opts):
2433 **opts):
2423 err = 0
2434 err = 0
2424 except error.RepoLookupError:
2435 except error.RepoLookupError:
2425 ui.status(_("skipping missing subrepository: %s\n")
2436 ui.status(_("skipping missing subrepository: %s\n")
2426 % os.path.join(prefix, subpath))
2437 % os.path.join(prefix, subpath))
2427
2438
2428 return err
2439 return err
2429
2440
2430 def commit(ui, repo, commitfunc, pats, opts):
2441 def commit(ui, repo, commitfunc, pats, opts):
2431 '''commit the specified files or all outstanding changes'''
2442 '''commit the specified files or all outstanding changes'''
2432 date = opts.get('date')
2443 date = opts.get('date')
2433 if date:
2444 if date:
2434 opts['date'] = util.parsedate(date)
2445 opts['date'] = util.parsedate(date)
2435 message = logmessage(ui, opts)
2446 message = logmessage(ui, opts)
2436 matcher = scmutil.match(repo[None], pats, opts)
2447 matcher = scmutil.match(repo[None], pats, opts)
2437
2448
2438 # extract addremove carefully -- this function can be called from a command
2449 # extract addremove carefully -- this function can be called from a command
2439 # that doesn't support addremove
2450 # that doesn't support addremove
2440 if opts.get('addremove'):
2451 if opts.get('addremove'):
2441 if scmutil.addremove(repo, matcher, "", opts) != 0:
2452 if scmutil.addremove(repo, matcher, "", opts) != 0:
2442 raise util.Abort(
2453 raise util.Abort(
2443 _("failed to mark all new/missing files as added/removed"))
2454 _("failed to mark all new/missing files as added/removed"))
2444
2455
2445 return commitfunc(ui, repo, message, matcher, opts)
2456 return commitfunc(ui, repo, message, matcher, opts)
2446
2457
2447 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2458 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2448 # amend will reuse the existing user if not specified, but the obsolete
2459 # amend will reuse the existing user if not specified, but the obsolete
2449 # marker creation requires that the current user's name is specified.
2460 # marker creation requires that the current user's name is specified.
2450 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2461 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2451 ui.username() # raise exception if username not set
2462 ui.username() # raise exception if username not set
2452
2463
2453 ui.note(_('amending changeset %s\n') % old)
2464 ui.note(_('amending changeset %s\n') % old)
2454 base = old.p1()
2465 base = old.p1()
2455
2466
2456 wlock = lock = newid = None
2467 wlock = lock = newid = None
2457 try:
2468 try:
2458 wlock = repo.wlock()
2469 wlock = repo.wlock()
2459 lock = repo.lock()
2470 lock = repo.lock()
2460 tr = repo.transaction('amend')
2471 tr = repo.transaction('amend')
2461 try:
2472 try:
2462 # See if we got a message from -m or -l, if not, open the editor
2473 # See if we got a message from -m or -l, if not, open the editor
2463 # with the message of the changeset to amend
2474 # with the message of the changeset to amend
2464 message = logmessage(ui, opts)
2475 message = logmessage(ui, opts)
2465 # ensure logfile does not conflict with later enforcement of the
2476 # ensure logfile does not conflict with later enforcement of the
2466 # message. potential logfile content has been processed by
2477 # message. potential logfile content has been processed by
2467 # `logmessage` anyway.
2478 # `logmessage` anyway.
2468 opts.pop('logfile')
2479 opts.pop('logfile')
2469 # First, do a regular commit to record all changes in the working
2480 # First, do a regular commit to record all changes in the working
2470 # directory (if there are any)
2481 # directory (if there are any)
2471 ui.callhooks = False
2482 ui.callhooks = False
2472 currentbookmark = repo._bookmarkcurrent
2483 currentbookmark = repo._bookmarkcurrent
2473 try:
2484 try:
2474 repo._bookmarkcurrent = None
2485 repo._bookmarkcurrent = None
2475 opts['message'] = 'temporary amend commit for %s' % old
2486 opts['message'] = 'temporary amend commit for %s' % old
2476 node = commit(ui, repo, commitfunc, pats, opts)
2487 node = commit(ui, repo, commitfunc, pats, opts)
2477 finally:
2488 finally:
2478 repo._bookmarkcurrent = currentbookmark
2489 repo._bookmarkcurrent = currentbookmark
2479 ui.callhooks = True
2490 ui.callhooks = True
2480 ctx = repo[node]
2491 ctx = repo[node]
2481
2492
2482 # Participating changesets:
2493 # Participating changesets:
2483 #
2494 #
2484 # node/ctx o - new (intermediate) commit that contains changes
2495 # node/ctx o - new (intermediate) commit that contains changes
2485 # | from working dir to go into amending commit
2496 # | from working dir to go into amending commit
2486 # | (or a workingctx if there were no changes)
2497 # | (or a workingctx if there were no changes)
2487 # |
2498 # |
2488 # old o - changeset to amend
2499 # old o - changeset to amend
2489 # |
2500 # |
2490 # base o - parent of amending changeset
2501 # base o - parent of amending changeset
2491
2502
2492 # Update extra dict from amended commit (e.g. to preserve graft
2503 # Update extra dict from amended commit (e.g. to preserve graft
2493 # source)
2504 # source)
2494 extra.update(old.extra())
2505 extra.update(old.extra())
2495
2506
2496 # Also update it from the intermediate commit or from the wctx
2507 # Also update it from the intermediate commit or from the wctx
2497 extra.update(ctx.extra())
2508 extra.update(ctx.extra())
2498
2509
2499 if len(old.parents()) > 1:
2510 if len(old.parents()) > 1:
2500 # ctx.files() isn't reliable for merges, so fall back to the
2511 # ctx.files() isn't reliable for merges, so fall back to the
2501 # slower repo.status() method
2512 # slower repo.status() method
2502 files = set([fn for st in repo.status(base, old)[:3]
2513 files = set([fn for st in repo.status(base, old)[:3]
2503 for fn in st])
2514 for fn in st])
2504 else:
2515 else:
2505 files = set(old.files())
2516 files = set(old.files())
2506
2517
2507 # Second, we use either the commit we just did, or if there were no
2518 # Second, we use either the commit we just did, or if there were no
2508 # changes the parent of the working directory as the version of the
2519 # changes the parent of the working directory as the version of the
2509 # files in the final amend commit
2520 # files in the final amend commit
2510 if node:
2521 if node:
2511 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2522 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2512
2523
2513 user = ctx.user()
2524 user = ctx.user()
2514 date = ctx.date()
2525 date = ctx.date()
2515 # Recompute copies (avoid recording a -> b -> a)
2526 # Recompute copies (avoid recording a -> b -> a)
2516 copied = copies.pathcopies(base, ctx)
2527 copied = copies.pathcopies(base, ctx)
2517 if old.p2:
2528 if old.p2:
2518 copied.update(copies.pathcopies(old.p2(), ctx))
2529 copied.update(copies.pathcopies(old.p2(), ctx))
2519
2530
2520 # Prune files which were reverted by the updates: if old
2531 # Prune files which were reverted by the updates: if old
2521 # introduced file X and our intermediate commit, node,
2532 # introduced file X and our intermediate commit, node,
2522 # renamed that file, then those two files are the same and
2533 # renamed that file, then those two files are the same and
2523 # we can discard X from our list of files. Likewise if X
2534 # we can discard X from our list of files. Likewise if X
2524 # was deleted, it's no longer relevant
2535 # was deleted, it's no longer relevant
2525 files.update(ctx.files())
2536 files.update(ctx.files())
2526
2537
2527 def samefile(f):
2538 def samefile(f):
2528 if f in ctx.manifest():
2539 if f in ctx.manifest():
2529 a = ctx.filectx(f)
2540 a = ctx.filectx(f)
2530 if f in base.manifest():
2541 if f in base.manifest():
2531 b = base.filectx(f)
2542 b = base.filectx(f)
2532 return (not a.cmp(b)
2543 return (not a.cmp(b)
2533 and a.flags() == b.flags())
2544 and a.flags() == b.flags())
2534 else:
2545 else:
2535 return False
2546 return False
2536 else:
2547 else:
2537 return f not in base.manifest()
2548 return f not in base.manifest()
2538 files = [f for f in files if not samefile(f)]
2549 files = [f for f in files if not samefile(f)]
2539
2550
2540 def filectxfn(repo, ctx_, path):
2551 def filectxfn(repo, ctx_, path):
2541 try:
2552 try:
2542 fctx = ctx[path]
2553 fctx = ctx[path]
2543 flags = fctx.flags()
2554 flags = fctx.flags()
2544 mctx = context.memfilectx(repo,
2555 mctx = context.memfilectx(repo,
2545 fctx.path(), fctx.data(),
2556 fctx.path(), fctx.data(),
2546 islink='l' in flags,
2557 islink='l' in flags,
2547 isexec='x' in flags,
2558 isexec='x' in flags,
2548 copied=copied.get(path))
2559 copied=copied.get(path))
2549 return mctx
2560 return mctx
2550 except KeyError:
2561 except KeyError:
2551 return None
2562 return None
2552 else:
2563 else:
2553 ui.note(_('copying changeset %s to %s\n') % (old, base))
2564 ui.note(_('copying changeset %s to %s\n') % (old, base))
2554
2565
2555 # Use version of files as in the old cset
2566 # Use version of files as in the old cset
2556 def filectxfn(repo, ctx_, path):
2567 def filectxfn(repo, ctx_, path):
2557 try:
2568 try:
2558 return old.filectx(path)
2569 return old.filectx(path)
2559 except KeyError:
2570 except KeyError:
2560 return None
2571 return None
2561
2572
2562 user = opts.get('user') or old.user()
2573 user = opts.get('user') or old.user()
2563 date = opts.get('date') or old.date()
2574 date = opts.get('date') or old.date()
2564 editform = mergeeditform(old, 'commit.amend')
2575 editform = mergeeditform(old, 'commit.amend')
2565 editor = getcommiteditor(editform=editform, **opts)
2576 editor = getcommiteditor(editform=editform, **opts)
2566 if not message:
2577 if not message:
2567 editor = getcommiteditor(edit=True, editform=editform)
2578 editor = getcommiteditor(edit=True, editform=editform)
2568 message = old.description()
2579 message = old.description()
2569
2580
2570 pureextra = extra.copy()
2581 pureextra = extra.copy()
2571 extra['amend_source'] = old.hex()
2582 extra['amend_source'] = old.hex()
2572
2583
2573 new = context.memctx(repo,
2584 new = context.memctx(repo,
2574 parents=[base.node(), old.p2().node()],
2585 parents=[base.node(), old.p2().node()],
2575 text=message,
2586 text=message,
2576 files=files,
2587 files=files,
2577 filectxfn=filectxfn,
2588 filectxfn=filectxfn,
2578 user=user,
2589 user=user,
2579 date=date,
2590 date=date,
2580 extra=extra,
2591 extra=extra,
2581 editor=editor)
2592 editor=editor)
2582
2593
2583 newdesc = changelog.stripdesc(new.description())
2594 newdesc = changelog.stripdesc(new.description())
2584 if ((not node)
2595 if ((not node)
2585 and newdesc == old.description()
2596 and newdesc == old.description()
2586 and user == old.user()
2597 and user == old.user()
2587 and date == old.date()
2598 and date == old.date()
2588 and pureextra == old.extra()):
2599 and pureextra == old.extra()):
2589 # nothing changed. continuing here would create a new node
2600 # nothing changed. continuing here would create a new node
2590 # anyway because of the amend_source noise.
2601 # anyway because of the amend_source noise.
2591 #
2602 #
2592 # This not what we expect from amend.
2603 # This not what we expect from amend.
2593 return old.node()
2604 return old.node()
2594
2605
2595 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2606 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2596 try:
2607 try:
2597 if opts.get('secret'):
2608 if opts.get('secret'):
2598 commitphase = 'secret'
2609 commitphase = 'secret'
2599 else:
2610 else:
2600 commitphase = old.phase()
2611 commitphase = old.phase()
2601 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2612 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2602 newid = repo.commitctx(new)
2613 newid = repo.commitctx(new)
2603 finally:
2614 finally:
2604 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2615 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2605 if newid != old.node():
2616 if newid != old.node():
2606 # Reroute the working copy parent to the new changeset
2617 # Reroute the working copy parent to the new changeset
2607 repo.setparents(newid, nullid)
2618 repo.setparents(newid, nullid)
2608
2619
2609 # Move bookmarks from old parent to amend commit
2620 # Move bookmarks from old parent to amend commit
2610 bms = repo.nodebookmarks(old.node())
2621 bms = repo.nodebookmarks(old.node())
2611 if bms:
2622 if bms:
2612 marks = repo._bookmarks
2623 marks = repo._bookmarks
2613 for bm in bms:
2624 for bm in bms:
2614 marks[bm] = newid
2625 marks[bm] = newid
2615 marks.write()
2626 marks.write()
2616 #commit the whole amend process
2627 #commit the whole amend process
2617 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2628 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2618 if createmarkers and newid != old.node():
2629 if createmarkers and newid != old.node():
2619 # mark the new changeset as successor of the rewritten one
2630 # mark the new changeset as successor of the rewritten one
2620 new = repo[newid]
2631 new = repo[newid]
2621 obs = [(old, (new,))]
2632 obs = [(old, (new,))]
2622 if node:
2633 if node:
2623 obs.append((ctx, ()))
2634 obs.append((ctx, ()))
2624
2635
2625 obsolete.createmarkers(repo, obs)
2636 obsolete.createmarkers(repo, obs)
2626 tr.close()
2637 tr.close()
2627 finally:
2638 finally:
2628 tr.release()
2639 tr.release()
2629 if not createmarkers and newid != old.node():
2640 if not createmarkers and newid != old.node():
2630 # Strip the intermediate commit (if there was one) and the amended
2641 # Strip the intermediate commit (if there was one) and the amended
2631 # commit
2642 # commit
2632 if node:
2643 if node:
2633 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2644 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2634 ui.note(_('stripping amended changeset %s\n') % old)
2645 ui.note(_('stripping amended changeset %s\n') % old)
2635 repair.strip(ui, repo, old.node(), topic='amend-backup')
2646 repair.strip(ui, repo, old.node(), topic='amend-backup')
2636 finally:
2647 finally:
2637 if newid is None:
2648 if newid is None:
2638 repo.dirstate.invalidate()
2649 repo.dirstate.invalidate()
2639 lockmod.release(lock, wlock)
2650 lockmod.release(lock, wlock)
2640 return newid
2651 return newid
2641
2652
2642 def commiteditor(repo, ctx, subs, editform=''):
2653 def commiteditor(repo, ctx, subs, editform=''):
2643 if ctx.description():
2654 if ctx.description():
2644 return ctx.description()
2655 return ctx.description()
2645 return commitforceeditor(repo, ctx, subs, editform=editform)
2656 return commitforceeditor(repo, ctx, subs, editform=editform)
2646
2657
2647 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2658 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2648 editform=''):
2659 editform=''):
2649 if not extramsg:
2660 if not extramsg:
2650 extramsg = _("Leave message empty to abort commit.")
2661 extramsg = _("Leave message empty to abort commit.")
2651
2662
2652 forms = [e for e in editform.split('.') if e]
2663 forms = [e for e in editform.split('.') if e]
2653 forms.insert(0, 'changeset')
2664 forms.insert(0, 'changeset')
2654 while forms:
2665 while forms:
2655 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2666 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2656 if tmpl:
2667 if tmpl:
2657 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2668 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2658 break
2669 break
2659 forms.pop()
2670 forms.pop()
2660 else:
2671 else:
2661 committext = buildcommittext(repo, ctx, subs, extramsg)
2672 committext = buildcommittext(repo, ctx, subs, extramsg)
2662
2673
2663 # run editor in the repository root
2674 # run editor in the repository root
2664 olddir = os.getcwd()
2675 olddir = os.getcwd()
2665 os.chdir(repo.root)
2676 os.chdir(repo.root)
2666 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2677 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2667 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2678 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2668 os.chdir(olddir)
2679 os.chdir(olddir)
2669
2680
2670 if finishdesc:
2681 if finishdesc:
2671 text = finishdesc(text)
2682 text = finishdesc(text)
2672 if not text.strip():
2683 if not text.strip():
2673 raise util.Abort(_("empty commit message"))
2684 raise util.Abort(_("empty commit message"))
2674
2685
2675 return text
2686 return text
2676
2687
2677 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2688 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2678 ui = repo.ui
2689 ui = repo.ui
2679 tmpl, mapfile = gettemplate(ui, tmpl, None)
2690 tmpl, mapfile = gettemplate(ui, tmpl, None)
2680
2691
2681 try:
2692 try:
2682 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2693 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2683 except SyntaxError, inst:
2694 except SyntaxError, inst:
2684 raise util.Abort(inst.args[0])
2695 raise util.Abort(inst.args[0])
2685
2696
2686 for k, v in repo.ui.configitems('committemplate'):
2697 for k, v in repo.ui.configitems('committemplate'):
2687 if k != 'changeset':
2698 if k != 'changeset':
2688 t.t.cache[k] = v
2699 t.t.cache[k] = v
2689
2700
2690 if not extramsg:
2701 if not extramsg:
2691 extramsg = '' # ensure that extramsg is string
2702 extramsg = '' # ensure that extramsg is string
2692
2703
2693 ui.pushbuffer()
2704 ui.pushbuffer()
2694 t.show(ctx, extramsg=extramsg)
2705 t.show(ctx, extramsg=extramsg)
2695 return ui.popbuffer()
2706 return ui.popbuffer()
2696
2707
2697 def buildcommittext(repo, ctx, subs, extramsg):
2708 def buildcommittext(repo, ctx, subs, extramsg):
2698 edittext = []
2709 edittext = []
2699 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2710 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2700 if ctx.description():
2711 if ctx.description():
2701 edittext.append(ctx.description())
2712 edittext.append(ctx.description())
2702 edittext.append("")
2713 edittext.append("")
2703 edittext.append("") # Empty line between message and comments.
2714 edittext.append("") # Empty line between message and comments.
2704 edittext.append(_("HG: Enter commit message."
2715 edittext.append(_("HG: Enter commit message."
2705 " Lines beginning with 'HG:' are removed."))
2716 " Lines beginning with 'HG:' are removed."))
2706 edittext.append("HG: %s" % extramsg)
2717 edittext.append("HG: %s" % extramsg)
2707 edittext.append("HG: --")
2718 edittext.append("HG: --")
2708 edittext.append(_("HG: user: %s") % ctx.user())
2719 edittext.append(_("HG: user: %s") % ctx.user())
2709 if ctx.p2():
2720 if ctx.p2():
2710 edittext.append(_("HG: branch merge"))
2721 edittext.append(_("HG: branch merge"))
2711 if ctx.branch():
2722 if ctx.branch():
2712 edittext.append(_("HG: branch '%s'") % ctx.branch())
2723 edittext.append(_("HG: branch '%s'") % ctx.branch())
2713 if bookmarks.iscurrent(repo):
2724 if bookmarks.iscurrent(repo):
2714 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2725 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2715 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2726 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2716 edittext.extend([_("HG: added %s") % f for f in added])
2727 edittext.extend([_("HG: added %s") % f for f in added])
2717 edittext.extend([_("HG: changed %s") % f for f in modified])
2728 edittext.extend([_("HG: changed %s") % f for f in modified])
2718 edittext.extend([_("HG: removed %s") % f for f in removed])
2729 edittext.extend([_("HG: removed %s") % f for f in removed])
2719 if not added and not modified and not removed:
2730 if not added and not modified and not removed:
2720 edittext.append(_("HG: no files changed"))
2731 edittext.append(_("HG: no files changed"))
2721 edittext.append("")
2732 edittext.append("")
2722
2733
2723 return "\n".join(edittext)
2734 return "\n".join(edittext)
2724
2735
2725 def commitstatus(repo, node, branch, bheads=None, opts={}):
2736 def commitstatus(repo, node, branch, bheads=None, opts={}):
2726 ctx = repo[node]
2737 ctx = repo[node]
2727 parents = ctx.parents()
2738 parents = ctx.parents()
2728
2739
2729 if (not opts.get('amend') and bheads and node not in bheads and not
2740 if (not opts.get('amend') and bheads and node not in bheads and not
2730 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2741 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2731 repo.ui.status(_('created new head\n'))
2742 repo.ui.status(_('created new head\n'))
2732 # The message is not printed for initial roots. For the other
2743 # The message is not printed for initial roots. For the other
2733 # changesets, it is printed in the following situations:
2744 # changesets, it is printed in the following situations:
2734 #
2745 #
2735 # Par column: for the 2 parents with ...
2746 # Par column: for the 2 parents with ...
2736 # N: null or no parent
2747 # N: null or no parent
2737 # B: parent is on another named branch
2748 # B: parent is on another named branch
2738 # C: parent is a regular non head changeset
2749 # C: parent is a regular non head changeset
2739 # H: parent was a branch head of the current branch
2750 # H: parent was a branch head of the current branch
2740 # Msg column: whether we print "created new head" message
2751 # Msg column: whether we print "created new head" message
2741 # In the following, it is assumed that there already exists some
2752 # In the following, it is assumed that there already exists some
2742 # initial branch heads of the current branch, otherwise nothing is
2753 # initial branch heads of the current branch, otherwise nothing is
2743 # printed anyway.
2754 # printed anyway.
2744 #
2755 #
2745 # Par Msg Comment
2756 # Par Msg Comment
2746 # N N y additional topo root
2757 # N N y additional topo root
2747 #
2758 #
2748 # B N y additional branch root
2759 # B N y additional branch root
2749 # C N y additional topo head
2760 # C N y additional topo head
2750 # H N n usual case
2761 # H N n usual case
2751 #
2762 #
2752 # B B y weird additional branch root
2763 # B B y weird additional branch root
2753 # C B y branch merge
2764 # C B y branch merge
2754 # H B n merge with named branch
2765 # H B n merge with named branch
2755 #
2766 #
2756 # C C y additional head from merge
2767 # C C y additional head from merge
2757 # C H n merge with a head
2768 # C H n merge with a head
2758 #
2769 #
2759 # H H n head merge: head count decreases
2770 # H H n head merge: head count decreases
2760
2771
2761 if not opts.get('close_branch'):
2772 if not opts.get('close_branch'):
2762 for r in parents:
2773 for r in parents:
2763 if r.closesbranch() and r.branch() == branch:
2774 if r.closesbranch() and r.branch() == branch:
2764 repo.ui.status(_('reopening closed branch head %d\n') % r)
2775 repo.ui.status(_('reopening closed branch head %d\n') % r)
2765
2776
2766 if repo.ui.debugflag:
2777 if repo.ui.debugflag:
2767 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2778 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2768 elif repo.ui.verbose:
2779 elif repo.ui.verbose:
2769 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2780 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2770
2781
2771 def revert(ui, repo, ctx, parents, *pats, **opts):
2782 def revert(ui, repo, ctx, parents, *pats, **opts):
2772 parent, p2 = parents
2783 parent, p2 = parents
2773 node = ctx.node()
2784 node = ctx.node()
2774
2785
2775 mf = ctx.manifest()
2786 mf = ctx.manifest()
2776 if node == p2:
2787 if node == p2:
2777 parent = p2
2788 parent = p2
2778 if node == parent:
2789 if node == parent:
2779 pmf = mf
2790 pmf = mf
2780 else:
2791 else:
2781 pmf = None
2792 pmf = None
2782
2793
2783 # need all matching names in dirstate and manifest of target rev,
2794 # need all matching names in dirstate and manifest of target rev,
2784 # so have to walk both. do not print errors if files exist in one
2795 # so have to walk both. do not print errors if files exist in one
2785 # but not other. in both cases, filesets should be evaluated against
2796 # but not other. in both cases, filesets should be evaluated against
2786 # workingctx to get consistent result (issue4497). this means 'set:**'
2797 # workingctx to get consistent result (issue4497). this means 'set:**'
2787 # cannot be used to select missing files from target rev.
2798 # cannot be used to select missing files from target rev.
2788
2799
2789 # `names` is a mapping for all elements in working copy and target revision
2800 # `names` is a mapping for all elements in working copy and target revision
2790 # The mapping is in the form:
2801 # The mapping is in the form:
2791 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2802 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2792 names = {}
2803 names = {}
2793
2804
2794 wlock = repo.wlock()
2805 wlock = repo.wlock()
2795 try:
2806 try:
2796 ## filling of the `names` mapping
2807 ## filling of the `names` mapping
2797 # walk dirstate to fill `names`
2808 # walk dirstate to fill `names`
2798
2809
2799 interactive = opts.get('interactive', False)
2810 interactive = opts.get('interactive', False)
2800 wctx = repo[None]
2811 wctx = repo[None]
2801 m = scmutil.match(wctx, pats, opts)
2812 m = scmutil.match(wctx, pats, opts)
2802
2813
2803 # we'll need this later
2814 # we'll need this later
2804 targetsubs = sorted(s for s in wctx.substate if m(s))
2815 targetsubs = sorted(s for s in wctx.substate if m(s))
2805
2816
2806 if not m.always():
2817 if not m.always():
2807 m.bad = lambda x, y: False
2818 m.bad = lambda x, y: False
2808 for abs in repo.walk(m):
2819 for abs in repo.walk(m):
2809 names[abs] = m.rel(abs), m.exact(abs)
2820 names[abs] = m.rel(abs), m.exact(abs)
2810
2821
2811 # walk target manifest to fill `names`
2822 # walk target manifest to fill `names`
2812
2823
2813 def badfn(path, msg):
2824 def badfn(path, msg):
2814 if path in names:
2825 if path in names:
2815 return
2826 return
2816 if path in ctx.substate:
2827 if path in ctx.substate:
2817 return
2828 return
2818 path_ = path + '/'
2829 path_ = path + '/'
2819 for f in names:
2830 for f in names:
2820 if f.startswith(path_):
2831 if f.startswith(path_):
2821 return
2832 return
2822 ui.warn("%s: %s\n" % (m.rel(path), msg))
2833 ui.warn("%s: %s\n" % (m.rel(path), msg))
2823
2834
2824 m.bad = badfn
2835 m.bad = badfn
2825 for abs in ctx.walk(m):
2836 for abs in ctx.walk(m):
2826 if abs not in names:
2837 if abs not in names:
2827 names[abs] = m.rel(abs), m.exact(abs)
2838 names[abs] = m.rel(abs), m.exact(abs)
2828
2839
2829 # Find status of all file in `names`.
2840 # Find status of all file in `names`.
2830 m = scmutil.matchfiles(repo, names)
2841 m = scmutil.matchfiles(repo, names)
2831
2842
2832 changes = repo.status(node1=node, match=m,
2843 changes = repo.status(node1=node, match=m,
2833 unknown=True, ignored=True, clean=True)
2844 unknown=True, ignored=True, clean=True)
2834 else:
2845 else:
2835 changes = repo.status(node1=node, match=m)
2846 changes = repo.status(node1=node, match=m)
2836 for kind in changes:
2847 for kind in changes:
2837 for abs in kind:
2848 for abs in kind:
2838 names[abs] = m.rel(abs), m.exact(abs)
2849 names[abs] = m.rel(abs), m.exact(abs)
2839
2850
2840 m = scmutil.matchfiles(repo, names)
2851 m = scmutil.matchfiles(repo, names)
2841
2852
2842 modified = set(changes.modified)
2853 modified = set(changes.modified)
2843 added = set(changes.added)
2854 added = set(changes.added)
2844 removed = set(changes.removed)
2855 removed = set(changes.removed)
2845 _deleted = set(changes.deleted)
2856 _deleted = set(changes.deleted)
2846 unknown = set(changes.unknown)
2857 unknown = set(changes.unknown)
2847 unknown.update(changes.ignored)
2858 unknown.update(changes.ignored)
2848 clean = set(changes.clean)
2859 clean = set(changes.clean)
2849 modadded = set()
2860 modadded = set()
2850
2861
2851 # split between files known in target manifest and the others
2862 # split between files known in target manifest and the others
2852 smf = set(mf)
2863 smf = set(mf)
2853
2864
2854 # determine the exact nature of the deleted changesets
2865 # determine the exact nature of the deleted changesets
2855 deladded = _deleted - smf
2866 deladded = _deleted - smf
2856 deleted = _deleted - deladded
2867 deleted = _deleted - deladded
2857
2868
2858 # We need to account for the state of the file in the dirstate,
2869 # We need to account for the state of the file in the dirstate,
2859 # even when we revert against something else than parent. This will
2870 # even when we revert against something else than parent. This will
2860 # slightly alter the behavior of revert (doing back up or not, delete
2871 # slightly alter the behavior of revert (doing back up or not, delete
2861 # or just forget etc).
2872 # or just forget etc).
2862 if parent == node:
2873 if parent == node:
2863 dsmodified = modified
2874 dsmodified = modified
2864 dsadded = added
2875 dsadded = added
2865 dsremoved = removed
2876 dsremoved = removed
2866 # store all local modifications, useful later for rename detection
2877 # store all local modifications, useful later for rename detection
2867 localchanges = dsmodified | dsadded
2878 localchanges = dsmodified | dsadded
2868 modified, added, removed = set(), set(), set()
2879 modified, added, removed = set(), set(), set()
2869 else:
2880 else:
2870 changes = repo.status(node1=parent, match=m)
2881 changes = repo.status(node1=parent, match=m)
2871 dsmodified = set(changes.modified)
2882 dsmodified = set(changes.modified)
2872 dsadded = set(changes.added)
2883 dsadded = set(changes.added)
2873 dsremoved = set(changes.removed)
2884 dsremoved = set(changes.removed)
2874 # store all local modifications, useful later for rename detection
2885 # store all local modifications, useful later for rename detection
2875 localchanges = dsmodified | dsadded
2886 localchanges = dsmodified | dsadded
2876
2887
2877 # only take into account for removes between wc and target
2888 # only take into account for removes between wc and target
2878 clean |= dsremoved - removed
2889 clean |= dsremoved - removed
2879 dsremoved &= removed
2890 dsremoved &= removed
2880 # distinct between dirstate remove and other
2891 # distinct between dirstate remove and other
2881 removed -= dsremoved
2892 removed -= dsremoved
2882
2893
2883 modadded = added & dsmodified
2894 modadded = added & dsmodified
2884 added -= modadded
2895 added -= modadded
2885
2896
2886 # tell newly modified apart.
2897 # tell newly modified apart.
2887 dsmodified &= modified
2898 dsmodified &= modified
2888 dsmodified |= modified & dsadded # dirstate added may needs backup
2899 dsmodified |= modified & dsadded # dirstate added may needs backup
2889 modified -= dsmodified
2900 modified -= dsmodified
2890
2901
2891 # We need to wait for some post-processing to update this set
2902 # We need to wait for some post-processing to update this set
2892 # before making the distinction. The dirstate will be used for
2903 # before making the distinction. The dirstate will be used for
2893 # that purpose.
2904 # that purpose.
2894 dsadded = added
2905 dsadded = added
2895
2906
2896 # in case of merge, files that are actually added can be reported as
2907 # in case of merge, files that are actually added can be reported as
2897 # modified, we need to post process the result
2908 # modified, we need to post process the result
2898 if p2 != nullid:
2909 if p2 != nullid:
2899 if pmf is None:
2910 if pmf is None:
2900 # only need parent manifest in the merge case,
2911 # only need parent manifest in the merge case,
2901 # so do not read by default
2912 # so do not read by default
2902 pmf = repo[parent].manifest()
2913 pmf = repo[parent].manifest()
2903 mergeadd = dsmodified - set(pmf)
2914 mergeadd = dsmodified - set(pmf)
2904 dsadded |= mergeadd
2915 dsadded |= mergeadd
2905 dsmodified -= mergeadd
2916 dsmodified -= mergeadd
2906
2917
2907 # if f is a rename, update `names` to also revert the source
2918 # if f is a rename, update `names` to also revert the source
2908 cwd = repo.getcwd()
2919 cwd = repo.getcwd()
2909 for f in localchanges:
2920 for f in localchanges:
2910 src = repo.dirstate.copied(f)
2921 src = repo.dirstate.copied(f)
2911 # XXX should we check for rename down to target node?
2922 # XXX should we check for rename down to target node?
2912 if src and src not in names and repo.dirstate[src] == 'r':
2923 if src and src not in names and repo.dirstate[src] == 'r':
2913 dsremoved.add(src)
2924 dsremoved.add(src)
2914 names[src] = (repo.pathto(src, cwd), True)
2925 names[src] = (repo.pathto(src, cwd), True)
2915
2926
2916 # distinguish between file to forget and the other
2927 # distinguish between file to forget and the other
2917 added = set()
2928 added = set()
2918 for abs in dsadded:
2929 for abs in dsadded:
2919 if repo.dirstate[abs] != 'a':
2930 if repo.dirstate[abs] != 'a':
2920 added.add(abs)
2931 added.add(abs)
2921 dsadded -= added
2932 dsadded -= added
2922
2933
2923 for abs in deladded:
2934 for abs in deladded:
2924 if repo.dirstate[abs] == 'a':
2935 if repo.dirstate[abs] == 'a':
2925 dsadded.add(abs)
2936 dsadded.add(abs)
2926 deladded -= dsadded
2937 deladded -= dsadded
2927
2938
2928 # For files marked as removed, we check if an unknown file is present at
2939 # For files marked as removed, we check if an unknown file is present at
2929 # the same path. If a such file exists it may need to be backed up.
2940 # the same path. If a such file exists it may need to be backed up.
2930 # Making the distinction at this stage helps have simpler backup
2941 # Making the distinction at this stage helps have simpler backup
2931 # logic.
2942 # logic.
2932 removunk = set()
2943 removunk = set()
2933 for abs in removed:
2944 for abs in removed:
2934 target = repo.wjoin(abs)
2945 target = repo.wjoin(abs)
2935 if os.path.lexists(target):
2946 if os.path.lexists(target):
2936 removunk.add(abs)
2947 removunk.add(abs)
2937 removed -= removunk
2948 removed -= removunk
2938
2949
2939 dsremovunk = set()
2950 dsremovunk = set()
2940 for abs in dsremoved:
2951 for abs in dsremoved:
2941 target = repo.wjoin(abs)
2952 target = repo.wjoin(abs)
2942 if os.path.lexists(target):
2953 if os.path.lexists(target):
2943 dsremovunk.add(abs)
2954 dsremovunk.add(abs)
2944 dsremoved -= dsremovunk
2955 dsremoved -= dsremovunk
2945
2956
2946 # action to be actually performed by revert
2957 # action to be actually performed by revert
2947 # (<list of file>, message>) tuple
2958 # (<list of file>, message>) tuple
2948 actions = {'revert': ([], _('reverting %s\n')),
2959 actions = {'revert': ([], _('reverting %s\n')),
2949 'add': ([], _('adding %s\n')),
2960 'add': ([], _('adding %s\n')),
2950 'remove': ([], _('removing %s\n')),
2961 'remove': ([], _('removing %s\n')),
2951 'drop': ([], _('removing %s\n')),
2962 'drop': ([], _('removing %s\n')),
2952 'forget': ([], _('forgetting %s\n')),
2963 'forget': ([], _('forgetting %s\n')),
2953 'undelete': ([], _('undeleting %s\n')),
2964 'undelete': ([], _('undeleting %s\n')),
2954 'noop': (None, _('no changes needed to %s\n')),
2965 'noop': (None, _('no changes needed to %s\n')),
2955 'unknown': (None, _('file not managed: %s\n')),
2966 'unknown': (None, _('file not managed: %s\n')),
2956 }
2967 }
2957
2968
2958 # "constant" that convey the backup strategy.
2969 # "constant" that convey the backup strategy.
2959 # All set to `discard` if `no-backup` is set do avoid checking
2970 # All set to `discard` if `no-backup` is set do avoid checking
2960 # no_backup lower in the code.
2971 # no_backup lower in the code.
2961 # These values are ordered for comparison purposes
2972 # These values are ordered for comparison purposes
2962 backup = 2 # unconditionally do backup
2973 backup = 2 # unconditionally do backup
2963 check = 1 # check if the existing file differs from target
2974 check = 1 # check if the existing file differs from target
2964 discard = 0 # never do backup
2975 discard = 0 # never do backup
2965 if opts.get('no_backup'):
2976 if opts.get('no_backup'):
2966 backup = check = discard
2977 backup = check = discard
2967
2978
2968 backupanddel = actions['remove']
2979 backupanddel = actions['remove']
2969 if not opts.get('no_backup'):
2980 if not opts.get('no_backup'):
2970 backupanddel = actions['drop']
2981 backupanddel = actions['drop']
2971
2982
2972 disptable = (
2983 disptable = (
2973 # dispatch table:
2984 # dispatch table:
2974 # file state
2985 # file state
2975 # action
2986 # action
2976 # make backup
2987 # make backup
2977
2988
2978 ## Sets that results that will change file on disk
2989 ## Sets that results that will change file on disk
2979 # Modified compared to target, no local change
2990 # Modified compared to target, no local change
2980 (modified, actions['revert'], discard),
2991 (modified, actions['revert'], discard),
2981 # Modified compared to target, but local file is deleted
2992 # Modified compared to target, but local file is deleted
2982 (deleted, actions['revert'], discard),
2993 (deleted, actions['revert'], discard),
2983 # Modified compared to target, local change
2994 # Modified compared to target, local change
2984 (dsmodified, actions['revert'], backup),
2995 (dsmodified, actions['revert'], backup),
2985 # Added since target
2996 # Added since target
2986 (added, actions['remove'], discard),
2997 (added, actions['remove'], discard),
2987 # Added in working directory
2998 # Added in working directory
2988 (dsadded, actions['forget'], discard),
2999 (dsadded, actions['forget'], discard),
2989 # Added since target, have local modification
3000 # Added since target, have local modification
2990 (modadded, backupanddel, backup),
3001 (modadded, backupanddel, backup),
2991 # Added since target but file is missing in working directory
3002 # Added since target but file is missing in working directory
2992 (deladded, actions['drop'], discard),
3003 (deladded, actions['drop'], discard),
2993 # Removed since target, before working copy parent
3004 # Removed since target, before working copy parent
2994 (removed, actions['add'], discard),
3005 (removed, actions['add'], discard),
2995 # Same as `removed` but an unknown file exists at the same path
3006 # Same as `removed` but an unknown file exists at the same path
2996 (removunk, actions['add'], check),
3007 (removunk, actions['add'], check),
2997 # Removed since targe, marked as such in working copy parent
3008 # Removed since targe, marked as such in working copy parent
2998 (dsremoved, actions['undelete'], discard),
3009 (dsremoved, actions['undelete'], discard),
2999 # Same as `dsremoved` but an unknown file exists at the same path
3010 # Same as `dsremoved` but an unknown file exists at the same path
3000 (dsremovunk, actions['undelete'], check),
3011 (dsremovunk, actions['undelete'], check),
3001 ## the following sets does not result in any file changes
3012 ## the following sets does not result in any file changes
3002 # File with no modification
3013 # File with no modification
3003 (clean, actions['noop'], discard),
3014 (clean, actions['noop'], discard),
3004 # Existing file, not tracked anywhere
3015 # Existing file, not tracked anywhere
3005 (unknown, actions['unknown'], discard),
3016 (unknown, actions['unknown'], discard),
3006 )
3017 )
3007
3018
3008 for abs, (rel, exact) in sorted(names.items()):
3019 for abs, (rel, exact) in sorted(names.items()):
3009 # target file to be touch on disk (relative to cwd)
3020 # target file to be touch on disk (relative to cwd)
3010 target = repo.wjoin(abs)
3021 target = repo.wjoin(abs)
3011 # search the entry in the dispatch table.
3022 # search the entry in the dispatch table.
3012 # if the file is in any of these sets, it was touched in the working
3023 # if the file is in any of these sets, it was touched in the working
3013 # directory parent and we are sure it needs to be reverted.
3024 # directory parent and we are sure it needs to be reverted.
3014 for table, (xlist, msg), dobackup in disptable:
3025 for table, (xlist, msg), dobackup in disptable:
3015 if abs not in table:
3026 if abs not in table:
3016 continue
3027 continue
3017 if xlist is not None:
3028 if xlist is not None:
3018 xlist.append(abs)
3029 xlist.append(abs)
3019 if dobackup and (backup <= dobackup
3030 if dobackup and (backup <= dobackup
3020 or wctx[abs].cmp(ctx[abs])):
3031 or wctx[abs].cmp(ctx[abs])):
3021 bakname = "%s.orig" % rel
3032 bakname = "%s.orig" % rel
3022 ui.note(_('saving current version of %s as %s\n') %
3033 ui.note(_('saving current version of %s as %s\n') %
3023 (rel, bakname))
3034 (rel, bakname))
3024 if not opts.get('dry_run'):
3035 if not opts.get('dry_run'):
3025 if interactive:
3036 if interactive:
3026 util.copyfile(target, bakname)
3037 util.copyfile(target, bakname)
3027 else:
3038 else:
3028 util.rename(target, bakname)
3039 util.rename(target, bakname)
3029 if ui.verbose or not exact:
3040 if ui.verbose or not exact:
3030 if not isinstance(msg, basestring):
3041 if not isinstance(msg, basestring):
3031 msg = msg(abs)
3042 msg = msg(abs)
3032 ui.status(msg % rel)
3043 ui.status(msg % rel)
3033 elif exact:
3044 elif exact:
3034 ui.warn(msg % rel)
3045 ui.warn(msg % rel)
3035 break
3046 break
3036
3047
3037 if not opts.get('dry_run'):
3048 if not opts.get('dry_run'):
3038 needdata = ('revert', 'add', 'undelete')
3049 needdata = ('revert', 'add', 'undelete')
3039 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3050 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3040 _performrevert(repo, parents, ctx, actions, interactive)
3051 _performrevert(repo, parents, ctx, actions, interactive)
3041
3052
3042 if targetsubs:
3053 if targetsubs:
3043 # Revert the subrepos on the revert list
3054 # Revert the subrepos on the revert list
3044 for sub in targetsubs:
3055 for sub in targetsubs:
3045 try:
3056 try:
3046 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3057 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3047 except KeyError:
3058 except KeyError:
3048 raise util.Abort("subrepository '%s' does not exist in %s!"
3059 raise util.Abort("subrepository '%s' does not exist in %s!"
3049 % (sub, short(ctx.node())))
3060 % (sub, short(ctx.node())))
3050 finally:
3061 finally:
3051 wlock.release()
3062 wlock.release()
3052
3063
3053 def _revertprefetch(repo, ctx, *files):
3064 def _revertprefetch(repo, ctx, *files):
3054 """Let extension changing the storage layer prefetch content"""
3065 """Let extension changing the storage layer prefetch content"""
3055 pass
3066 pass
3056
3067
3057 def _performrevert(repo, parents, ctx, actions, interactive=False):
3068 def _performrevert(repo, parents, ctx, actions, interactive=False):
3058 """function that actually perform all the actions computed for revert
3069 """function that actually perform all the actions computed for revert
3059
3070
3060 This is an independent function to let extension to plug in and react to
3071 This is an independent function to let extension to plug in and react to
3061 the imminent revert.
3072 the imminent revert.
3062
3073
3063 Make sure you have the working directory locked when calling this function.
3074 Make sure you have the working directory locked when calling this function.
3064 """
3075 """
3065 parent, p2 = parents
3076 parent, p2 = parents
3066 node = ctx.node()
3077 node = ctx.node()
3067 def checkout(f):
3078 def checkout(f):
3068 fc = ctx[f]
3079 fc = ctx[f]
3069 return repo.wwrite(f, fc.data(), fc.flags())
3080 return repo.wwrite(f, fc.data(), fc.flags())
3070
3081
3071 audit_path = pathutil.pathauditor(repo.root)
3082 audit_path = pathutil.pathauditor(repo.root)
3072 for f in actions['forget'][0]:
3083 for f in actions['forget'][0]:
3073 repo.dirstate.drop(f)
3084 repo.dirstate.drop(f)
3074 for f in actions['remove'][0]:
3085 for f in actions['remove'][0]:
3075 audit_path(f)
3086 audit_path(f)
3076 util.unlinkpath(repo.wjoin(f))
3087 util.unlinkpath(repo.wjoin(f))
3077 repo.dirstate.remove(f)
3088 repo.dirstate.remove(f)
3078 for f in actions['drop'][0]:
3089 for f in actions['drop'][0]:
3079 audit_path(f)
3090 audit_path(f)
3080 repo.dirstate.remove(f)
3091 repo.dirstate.remove(f)
3081
3092
3082 normal = None
3093 normal = None
3083 if node == parent:
3094 if node == parent:
3084 # We're reverting to our parent. If possible, we'd like status
3095 # We're reverting to our parent. If possible, we'd like status
3085 # to report the file as clean. We have to use normallookup for
3096 # to report the file as clean. We have to use normallookup for
3086 # merges to avoid losing information about merged/dirty files.
3097 # merges to avoid losing information about merged/dirty files.
3087 if p2 != nullid:
3098 if p2 != nullid:
3088 normal = repo.dirstate.normallookup
3099 normal = repo.dirstate.normallookup
3089 else:
3100 else:
3090 normal = repo.dirstate.normal
3101 normal = repo.dirstate.normal
3091
3102
3092 if interactive:
3103 if interactive:
3093 # Prompt the user for changes to revert
3104 # Prompt the user for changes to revert
3094 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3105 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3095 m = scmutil.match(ctx, torevert, {})
3106 m = scmutil.match(ctx, torevert, {})
3096 diff = patch.diff(repo, None, ctx.node(), m)
3107 diff = patch.diff(repo, None, ctx.node(), m)
3097 originalchunks = patch.parsepatch(diff)
3108 originalchunks = patch.parsepatch(diff)
3098 try:
3109 try:
3099 chunks = recordfilter(repo.ui, originalchunks)
3110 chunks = recordfilter(repo.ui, originalchunks)
3100 except patch.PatchError, err:
3111 except patch.PatchError, err:
3101 raise util.Abort(_('error parsing patch: %s') % err)
3112 raise util.Abort(_('error parsing patch: %s') % err)
3102
3113
3103 # Apply changes
3114 # Apply changes
3104 fp = cStringIO.StringIO()
3115 fp = cStringIO.StringIO()
3105 for c in chunks:
3116 for c in chunks:
3106 c.write(fp)
3117 c.write(fp)
3107 dopatch = fp.tell()
3118 dopatch = fp.tell()
3108 fp.seek(0)
3119 fp.seek(0)
3109 if dopatch:
3120 if dopatch:
3110 try:
3121 try:
3111 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3122 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3112 except patch.PatchError, err:
3123 except patch.PatchError, err:
3113 raise util.Abort(str(err))
3124 raise util.Abort(str(err))
3114 del fp
3125 del fp
3115 else:
3126 else:
3116 for f in actions['revert'][0]:
3127 for f in actions['revert'][0]:
3117 wsize = checkout(f)
3128 wsize = checkout(f)
3118 if normal:
3129 if normal:
3119 normal(f)
3130 normal(f)
3120 elif wsize == repo.dirstate._map[f][2]:
3131 elif wsize == repo.dirstate._map[f][2]:
3121 # changes may be overlooked without normallookup,
3132 # changes may be overlooked without normallookup,
3122 # if size isn't changed at reverting
3133 # if size isn't changed at reverting
3123 repo.dirstate.normallookup(f)
3134 repo.dirstate.normallookup(f)
3124
3135
3125 for f in actions['add'][0]:
3136 for f in actions['add'][0]:
3126 checkout(f)
3137 checkout(f)
3127 repo.dirstate.add(f)
3138 repo.dirstate.add(f)
3128
3139
3129 normal = repo.dirstate.normallookup
3140 normal = repo.dirstate.normallookup
3130 if node == parent and p2 == nullid:
3141 if node == parent and p2 == nullid:
3131 normal = repo.dirstate.normal
3142 normal = repo.dirstate.normal
3132 for f in actions['undelete'][0]:
3143 for f in actions['undelete'][0]:
3133 checkout(f)
3144 checkout(f)
3134 normal(f)
3145 normal(f)
3135
3146
3136 copied = copies.pathcopies(repo[parent], ctx)
3147 copied = copies.pathcopies(repo[parent], ctx)
3137
3148
3138 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3149 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3139 if f in copied:
3150 if f in copied:
3140 repo.dirstate.copy(copied[f], f)
3151 repo.dirstate.copy(copied[f], f)
3141
3152
3142 def command(table):
3153 def command(table):
3143 """Returns a function object to be used as a decorator for making commands.
3154 """Returns a function object to be used as a decorator for making commands.
3144
3155
3145 This function receives a command table as its argument. The table should
3156 This function receives a command table as its argument. The table should
3146 be a dict.
3157 be a dict.
3147
3158
3148 The returned function can be used as a decorator for adding commands
3159 The returned function can be used as a decorator for adding commands
3149 to that command table. This function accepts multiple arguments to define
3160 to that command table. This function accepts multiple arguments to define
3150 a command.
3161 a command.
3151
3162
3152 The first argument is the command name.
3163 The first argument is the command name.
3153
3164
3154 The options argument is an iterable of tuples defining command arguments.
3165 The options argument is an iterable of tuples defining command arguments.
3155 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3166 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3156
3167
3157 The synopsis argument defines a short, one line summary of how to use the
3168 The synopsis argument defines a short, one line summary of how to use the
3158 command. This shows up in the help output.
3169 command. This shows up in the help output.
3159
3170
3160 The norepo argument defines whether the command does not require a
3171 The norepo argument defines whether the command does not require a
3161 local repository. Most commands operate against a repository, thus the
3172 local repository. Most commands operate against a repository, thus the
3162 default is False.
3173 default is False.
3163
3174
3164 The optionalrepo argument defines whether the command optionally requires
3175 The optionalrepo argument defines whether the command optionally requires
3165 a local repository.
3176 a local repository.
3166
3177
3167 The inferrepo argument defines whether to try to find a repository from the
3178 The inferrepo argument defines whether to try to find a repository from the
3168 command line arguments. If True, arguments will be examined for potential
3179 command line arguments. If True, arguments will be examined for potential
3169 repository locations. See ``findrepo()``. If a repository is found, it
3180 repository locations. See ``findrepo()``. If a repository is found, it
3170 will be used.
3181 will be used.
3171 """
3182 """
3172 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3183 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3173 inferrepo=False):
3184 inferrepo=False):
3174 def decorator(func):
3185 def decorator(func):
3175 if synopsis:
3186 if synopsis:
3176 table[name] = func, list(options), synopsis
3187 table[name] = func, list(options), synopsis
3177 else:
3188 else:
3178 table[name] = func, list(options)
3189 table[name] = func, list(options)
3179
3190
3180 if norepo:
3191 if norepo:
3181 # Avoid import cycle.
3192 # Avoid import cycle.
3182 import commands
3193 import commands
3183 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3194 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3184
3195
3185 if optionalrepo:
3196 if optionalrepo:
3186 import commands
3197 import commands
3187 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3198 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3188
3199
3189 if inferrepo:
3200 if inferrepo:
3190 import commands
3201 import commands
3191 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3202 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3192
3203
3193 return func
3204 return func
3194 return decorator
3205 return decorator
3195
3206
3196 return cmd
3207 return cmd
3197
3208
3198 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3209 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3199 # commands.outgoing. "missing" is "missing" of the result of
3210 # commands.outgoing. "missing" is "missing" of the result of
3200 # "findcommonoutgoing()"
3211 # "findcommonoutgoing()"
3201 outgoinghooks = util.hooks()
3212 outgoinghooks = util.hooks()
3202
3213
3203 # a list of (ui, repo) functions called by commands.summary
3214 # a list of (ui, repo) functions called by commands.summary
3204 summaryhooks = util.hooks()
3215 summaryhooks = util.hooks()
3205
3216
3206 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3217 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3207 #
3218 #
3208 # functions should return tuple of booleans below, if 'changes' is None:
3219 # functions should return tuple of booleans below, if 'changes' is None:
3209 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3220 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3210 #
3221 #
3211 # otherwise, 'changes' is a tuple of tuples below:
3222 # otherwise, 'changes' is a tuple of tuples below:
3212 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3223 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3213 # - (desturl, destbranch, destpeer, outgoing)
3224 # - (desturl, destbranch, destpeer, outgoing)
3214 summaryremotehooks = util.hooks()
3225 summaryremotehooks = util.hooks()
3215
3226
3216 # A list of state files kept by multistep operations like graft.
3227 # A list of state files kept by multistep operations like graft.
3217 # Since graft cannot be aborted, it is considered 'clearable' by update.
3228 # Since graft cannot be aborted, it is considered 'clearable' by update.
3218 # note: bisect is intentionally excluded
3229 # note: bisect is intentionally excluded
3219 # (state file, clearable, allowcommit, error, hint)
3230 # (state file, clearable, allowcommit, error, hint)
3220 unfinishedstates = [
3231 unfinishedstates = [
3221 ('graftstate', True, False, _('graft in progress'),
3232 ('graftstate', True, False, _('graft in progress'),
3222 _("use 'hg graft --continue' or 'hg update' to abort")),
3233 _("use 'hg graft --continue' or 'hg update' to abort")),
3223 ('updatestate', True, False, _('last update was interrupted'),
3234 ('updatestate', True, False, _('last update was interrupted'),
3224 _("use 'hg update' to get a consistent checkout"))
3235 _("use 'hg update' to get a consistent checkout"))
3225 ]
3236 ]
3226
3237
3227 def checkunfinished(repo, commit=False):
3238 def checkunfinished(repo, commit=False):
3228 '''Look for an unfinished multistep operation, like graft, and abort
3239 '''Look for an unfinished multistep operation, like graft, and abort
3229 if found. It's probably good to check this right before
3240 if found. It's probably good to check this right before
3230 bailifchanged().
3241 bailifchanged().
3231 '''
3242 '''
3232 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3243 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3233 if commit and allowcommit:
3244 if commit and allowcommit:
3234 continue
3245 continue
3235 if repo.vfs.exists(f):
3246 if repo.vfs.exists(f):
3236 raise util.Abort(msg, hint=hint)
3247 raise util.Abort(msg, hint=hint)
3237
3248
3238 def clearunfinished(repo):
3249 def clearunfinished(repo):
3239 '''Check for unfinished operations (as above), and clear the ones
3250 '''Check for unfinished operations (as above), and clear the ones
3240 that are clearable.
3251 that are clearable.
3241 '''
3252 '''
3242 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3253 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3243 if not clearable and repo.vfs.exists(f):
3254 if not clearable and repo.vfs.exists(f):
3244 raise util.Abort(msg, hint=hint)
3255 raise util.Abort(msg, hint=hint)
3245 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3256 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3246 if clearable and repo.vfs.exists(f):
3257 if clearable and repo.vfs.exists(f):
3247 util.unlink(repo.join(f))
3258 util.unlink(repo.join(f))
@@ -1,2464 +1,2478 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|new file|deleted file) ')
823 allhunks_re = re.compile('(?:index|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|deleted|copy|rename) ')
826 newfile_re = re.compile('(?:new file)')
826
827
827 def __init__(self, header):
828 def __init__(self, header):
828 self.header = header
829 self.header = header
829 self.hunks = []
830 self.hunks = []
830
831
831 def binary(self):
832 def binary(self):
832 return util.any(h.startswith('index ') for h in self.header)
833 return util.any(h.startswith('index ') for h in self.header)
833
834
834 def pretty(self, fp):
835 def pretty(self, fp):
835 for h in self.header:
836 for h in self.header:
836 if h.startswith('index '):
837 if h.startswith('index '):
837 fp.write(_('this modifies a binary file (all or nothing)\n'))
838 fp.write(_('this modifies a binary file (all or nothing)\n'))
838 break
839 break
839 if self.pretty_re.match(h):
840 if self.pretty_re.match(h):
840 fp.write(h)
841 fp.write(h)
841 if self.binary():
842 if self.binary():
842 fp.write(_('this is a binary file\n'))
843 fp.write(_('this is a binary file\n'))
843 break
844 break
844 if h.startswith('---'):
845 if h.startswith('---'):
845 fp.write(_('%d hunks, %d lines changed\n') %
846 fp.write(_('%d hunks, %d lines changed\n') %
846 (len(self.hunks),
847 (len(self.hunks),
847 sum([max(h.added, h.removed) for h in self.hunks])))
848 sum([max(h.added, h.removed) for h in self.hunks])))
848 break
849 break
849 fp.write(h)
850 fp.write(h)
850
851
851 def write(self, fp):
852 def write(self, fp):
852 fp.write(''.join(self.header))
853 fp.write(''.join(self.header))
853
854
854 def allhunks(self):
855 def allhunks(self):
855 return util.any(self.allhunks_re.match(h) for h in self.header)
856 return util.any(self.allhunks_re.match(h) for h in self.header)
856
857
857 def files(self):
858 def files(self):
858 match = self.diffgit_re.match(self.header[0])
859 match = self.diffgit_re.match(self.header[0])
859 if match:
860 if match:
860 fromfile, tofile = match.groups()
861 fromfile, tofile = match.groups()
861 if fromfile == tofile:
862 if fromfile == tofile:
862 return [fromfile]
863 return [fromfile]
863 return [fromfile, tofile]
864 return [fromfile, tofile]
864 else:
865 else:
865 return self.diff_re.match(self.header[0]).groups()
866 return self.diff_re.match(self.header[0]).groups()
866
867
867 def filename(self):
868 def filename(self):
868 return self.files()[-1]
869 return self.files()[-1]
869
870
870 def __repr__(self):
871 def __repr__(self):
871 return '<header %s>' % (' '.join(map(repr, self.files())))
872 return '<header %s>' % (' '.join(map(repr, self.files())))
872
873
874 def isnewfile(self):
875 return util.any(self.newfile_re.match(h) for h in self.header)
876
873 def special(self):
877 def special(self):
874 return util.any(self.special_re.match(h) for h in self.header)
878 # Special files are shown only at the header level and not at the hunk
879 # level for example a file that has been deleted is a special file.
880 # The user cannot change the content of the operation, in the case of
881 # the deleted file he has to take the deletion or not take it, he
882 # cannot take some of it.
883 # Newly added files are special if they are empty, they are not special
884 # if they have some content as we want to be able to change it
885 nocontent = len(self.header) == 2
886 emptynewfile = self.isnewfile() and nocontent
887 return emptynewfile or \
888 util.any(self.special_re.match(h) for h in self.header)
875
889
876 class recordhunk(object):
890 class recordhunk(object):
877 """patch hunk
891 """patch hunk
878
892
879 XXX shouldn't we merge this with the other hunk class?
893 XXX shouldn't we merge this with the other hunk class?
880 """
894 """
881 maxcontext = 3
895 maxcontext = 3
882
896
883 def __init__(self, header, fromline, toline, proc, before, hunk, after):
897 def __init__(self, header, fromline, toline, proc, before, hunk, after):
884 def trimcontext(number, lines):
898 def trimcontext(number, lines):
885 delta = len(lines) - self.maxcontext
899 delta = len(lines) - self.maxcontext
886 if False and delta > 0:
900 if False and delta > 0:
887 return number + delta, lines[:self.maxcontext]
901 return number + delta, lines[:self.maxcontext]
888 return number, lines
902 return number, lines
889
903
890 self.header = header
904 self.header = header
891 self.fromline, self.before = trimcontext(fromline, before)
905 self.fromline, self.before = trimcontext(fromline, before)
892 self.toline, self.after = trimcontext(toline, after)
906 self.toline, self.after = trimcontext(toline, after)
893 self.proc = proc
907 self.proc = proc
894 self.hunk = hunk
908 self.hunk = hunk
895 self.added, self.removed = self.countchanges(self.hunk)
909 self.added, self.removed = self.countchanges(self.hunk)
896
910
897 def __eq__(self, v):
911 def __eq__(self, v):
898 if not isinstance(v, recordhunk):
912 if not isinstance(v, recordhunk):
899 return False
913 return False
900
914
901 return ((v.hunk == self.hunk) and
915 return ((v.hunk == self.hunk) and
902 (v.proc == self.proc) and
916 (v.proc == self.proc) and
903 (self.fromline == v.fromline) and
917 (self.fromline == v.fromline) and
904 (self.header.files() == v.header.files()))
918 (self.header.files() == v.header.files()))
905
919
906 def __hash__(self):
920 def __hash__(self):
907 return hash((tuple(self.hunk),
921 return hash((tuple(self.hunk),
908 tuple(self.header.files()),
922 tuple(self.header.files()),
909 self.fromline,
923 self.fromline,
910 self.proc))
924 self.proc))
911
925
912 def countchanges(self, hunk):
926 def countchanges(self, hunk):
913 """hunk -> (n+,n-)"""
927 """hunk -> (n+,n-)"""
914 add = len([h for h in hunk if h[0] == '+'])
928 add = len([h for h in hunk if h[0] == '+'])
915 rem = len([h for h in hunk if h[0] == '-'])
929 rem = len([h for h in hunk if h[0] == '-'])
916 return add, rem
930 return add, rem
917
931
918 def write(self, fp):
932 def write(self, fp):
919 delta = len(self.before) + len(self.after)
933 delta = len(self.before) + len(self.after)
920 if self.after and self.after[-1] == '\\ No newline at end of file\n':
934 if self.after and self.after[-1] == '\\ No newline at end of file\n':
921 delta -= 1
935 delta -= 1
922 fromlen = delta + self.removed
936 fromlen = delta + self.removed
923 tolen = delta + self.added
937 tolen = delta + self.added
924 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
938 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
925 (self.fromline, fromlen, self.toline, tolen,
939 (self.fromline, fromlen, self.toline, tolen,
926 self.proc and (' ' + self.proc)))
940 self.proc and (' ' + self.proc)))
927 fp.write(''.join(self.before + self.hunk + self.after))
941 fp.write(''.join(self.before + self.hunk + self.after))
928
942
929 pretty = write
943 pretty = write
930
944
931 def filename(self):
945 def filename(self):
932 return self.header.filename()
946 return self.header.filename()
933
947
934 def __repr__(self):
948 def __repr__(self):
935 return '<hunk %r@%d>' % (self.filename(), self.fromline)
949 return '<hunk %r@%d>' % (self.filename(), self.fromline)
936
950
937 def filterpatch(ui, headers):
951 def filterpatch(ui, headers):
938 """Interactively filter patch chunks into applied-only chunks"""
952 """Interactively filter patch chunks into applied-only chunks"""
939
953
940 def prompt(skipfile, skipall, query, chunk):
954 def prompt(skipfile, skipall, query, chunk):
941 """prompt query, and process base inputs
955 """prompt query, and process base inputs
942
956
943 - y/n for the rest of file
957 - y/n for the rest of file
944 - y/n for the rest
958 - y/n for the rest
945 - ? (help)
959 - ? (help)
946 - q (quit)
960 - q (quit)
947
961
948 Return True/False and possibly updated skipfile and skipall.
962 Return True/False and possibly updated skipfile and skipall.
949 """
963 """
950 newpatches = None
964 newpatches = None
951 if skipall is not None:
965 if skipall is not None:
952 return skipall, skipfile, skipall, newpatches
966 return skipall, skipfile, skipall, newpatches
953 if skipfile is not None:
967 if skipfile is not None:
954 return skipfile, skipfile, skipall, newpatches
968 return skipfile, skipfile, skipall, newpatches
955 while True:
969 while True:
956 resps = _('[Ynesfdaq?]'
970 resps = _('[Ynesfdaq?]'
957 '$$ &Yes, record this change'
971 '$$ &Yes, record this change'
958 '$$ &No, skip this change'
972 '$$ &No, skip this change'
959 '$$ &Edit this change manually'
973 '$$ &Edit this change manually'
960 '$$ &Skip remaining changes to this file'
974 '$$ &Skip remaining changes to this file'
961 '$$ Record remaining changes to this &file'
975 '$$ Record remaining changes to this &file'
962 '$$ &Done, skip remaining changes and files'
976 '$$ &Done, skip remaining changes and files'
963 '$$ Record &all changes to all remaining files'
977 '$$ Record &all changes to all remaining files'
964 '$$ &Quit, recording no changes'
978 '$$ &Quit, recording no changes'
965 '$$ &? (display help)')
979 '$$ &? (display help)')
966 r = ui.promptchoice("%s %s" % (query, resps))
980 r = ui.promptchoice("%s %s" % (query, resps))
967 ui.write("\n")
981 ui.write("\n")
968 if r == 8: # ?
982 if r == 8: # ?
969 for c, t in ui.extractchoices(resps)[1]:
983 for c, t in ui.extractchoices(resps)[1]:
970 ui.write('%s - %s\n' % (c, t.lower()))
984 ui.write('%s - %s\n' % (c, t.lower()))
971 continue
985 continue
972 elif r == 0: # yes
986 elif r == 0: # yes
973 ret = True
987 ret = True
974 elif r == 1: # no
988 elif r == 1: # no
975 ret = False
989 ret = False
976 elif r == 2: # Edit patch
990 elif r == 2: # Edit patch
977 if chunk is None:
991 if chunk is None:
978 ui.write(_('cannot edit patch for whole file'))
992 ui.write(_('cannot edit patch for whole file'))
979 ui.write("\n")
993 ui.write("\n")
980 continue
994 continue
981 if chunk.header.binary():
995 if chunk.header.binary():
982 ui.write(_('cannot edit patch for binary file'))
996 ui.write(_('cannot edit patch for binary file'))
983 ui.write("\n")
997 ui.write("\n")
984 continue
998 continue
985 # Patch comment based on the Git one (based on comment at end of
999 # Patch comment based on the Git one (based on comment at end of
986 # http://mercurial.selenic.com/wiki/RecordExtension)
1000 # http://mercurial.selenic.com/wiki/RecordExtension)
987 phelp = '---' + _("""
1001 phelp = '---' + _("""
988 To remove '-' lines, make them ' ' lines (context).
1002 To remove '-' lines, make them ' ' lines (context).
989 To remove '+' lines, delete them.
1003 To remove '+' lines, delete them.
990 Lines starting with # will be removed from the patch.
1004 Lines starting with # will be removed from the patch.
991
1005
992 If the patch applies cleanly, the edited hunk will immediately be
1006 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
1007 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
1008 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
1009 all lines of the hunk are removed, then the edit is aborted and
996 the hunk is left unchanged.
1010 the hunk is left unchanged.
997 """)
1011 """)
998 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1012 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
999 suffix=".diff", text=True)
1013 suffix=".diff", text=True)
1000 ncpatchfp = None
1014 ncpatchfp = None
1001 try:
1015 try:
1002 # Write the initial patch
1016 # Write the initial patch
1003 f = os.fdopen(patchfd, "w")
1017 f = os.fdopen(patchfd, "w")
1004 chunk.header.write(f)
1018 chunk.header.write(f)
1005 chunk.write(f)
1019 chunk.write(f)
1006 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1020 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1007 f.close()
1021 f.close()
1008 # Start the editor and wait for it to complete
1022 # Start the editor and wait for it to complete
1009 editor = ui.geteditor()
1023 editor = ui.geteditor()
1010 ui.system("%s \"%s\"" % (editor, patchfn),
1024 ui.system("%s \"%s\"" % (editor, patchfn),
1011 environ={'HGUSER': ui.username()},
1025 environ={'HGUSER': ui.username()},
1012 onerr=util.Abort, errprefix=_("edit failed"))
1026 onerr=util.Abort, errprefix=_("edit failed"))
1013 # Remove comment lines
1027 # Remove comment lines
1014 patchfp = open(patchfn)
1028 patchfp = open(patchfn)
1015 ncpatchfp = cStringIO.StringIO()
1029 ncpatchfp = cStringIO.StringIO()
1016 for line in patchfp:
1030 for line in patchfp:
1017 if not line.startswith('#'):
1031 if not line.startswith('#'):
1018 ncpatchfp.write(line)
1032 ncpatchfp.write(line)
1019 patchfp.close()
1033 patchfp.close()
1020 ncpatchfp.seek(0)
1034 ncpatchfp.seek(0)
1021 newpatches = parsepatch(ncpatchfp)
1035 newpatches = parsepatch(ncpatchfp)
1022 finally:
1036 finally:
1023 os.unlink(patchfn)
1037 os.unlink(patchfn)
1024 del ncpatchfp
1038 del ncpatchfp
1025 # Signal that the chunk shouldn't be applied as-is, but
1039 # Signal that the chunk shouldn't be applied as-is, but
1026 # provide the new patch to be used instead.
1040 # provide the new patch to be used instead.
1027 ret = False
1041 ret = False
1028 elif r == 3: # Skip
1042 elif r == 3: # Skip
1029 ret = skipfile = False
1043 ret = skipfile = False
1030 elif r == 4: # file (Record remaining)
1044 elif r == 4: # file (Record remaining)
1031 ret = skipfile = True
1045 ret = skipfile = True
1032 elif r == 5: # done, skip remaining
1046 elif r == 5: # done, skip remaining
1033 ret = skipall = False
1047 ret = skipall = False
1034 elif r == 6: # all
1048 elif r == 6: # all
1035 ret = skipall = True
1049 ret = skipall = True
1036 elif r == 7: # quit
1050 elif r == 7: # quit
1037 raise util.Abort(_('user quit'))
1051 raise util.Abort(_('user quit'))
1038 return ret, skipfile, skipall, newpatches
1052 return ret, skipfile, skipall, newpatches
1039
1053
1040 seen = set()
1054 seen = set()
1041 applied = {} # 'filename' -> [] of chunks
1055 applied = {} # 'filename' -> [] of chunks
1042 skipfile, skipall = None, None
1056 skipfile, skipall = None, None
1043 pos, total = 1, sum(len(h.hunks) for h in headers)
1057 pos, total = 1, sum(len(h.hunks) for h in headers)
1044 for h in headers:
1058 for h in headers:
1045 pos += len(h.hunks)
1059 pos += len(h.hunks)
1046 skipfile = None
1060 skipfile = None
1047 fixoffset = 0
1061 fixoffset = 0
1048 hdr = ''.join(h.header)
1062 hdr = ''.join(h.header)
1049 if hdr in seen:
1063 if hdr in seen:
1050 continue
1064 continue
1051 seen.add(hdr)
1065 seen.add(hdr)
1052 if skipall is None:
1066 if skipall is None:
1053 h.pretty(ui)
1067 h.pretty(ui)
1054 msg = (_('examine changes to %s?') %
1068 msg = (_('examine changes to %s?') %
1055 _(' and ').join("'%s'" % f for f in h.files()))
1069 _(' and ').join("'%s'" % f for f in h.files()))
1056 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1070 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1057 if not r:
1071 if not r:
1058 continue
1072 continue
1059 applied[h.filename()] = [h]
1073 applied[h.filename()] = [h]
1060 if h.allhunks():
1074 if h.allhunks():
1061 applied[h.filename()] += h.hunks
1075 applied[h.filename()] += h.hunks
1062 continue
1076 continue
1063 for i, chunk in enumerate(h.hunks):
1077 for i, chunk in enumerate(h.hunks):
1064 if skipfile is None and skipall is None:
1078 if skipfile is None and skipall is None:
1065 chunk.pretty(ui)
1079 chunk.pretty(ui)
1066 if total == 1:
1080 if total == 1:
1067 msg = _("record this change to '%s'?") % chunk.filename()
1081 msg = _("record this change to '%s'?") % chunk.filename()
1068 else:
1082 else:
1069 idx = pos - len(h.hunks) + i
1083 idx = pos - len(h.hunks) + i
1070 msg = _("record change %d/%d to '%s'?") % (idx, total,
1084 msg = _("record change %d/%d to '%s'?") % (idx, total,
1071 chunk.filename())
1085 chunk.filename())
1072 r, skipfile, skipall, newpatches = prompt(skipfile,
1086 r, skipfile, skipall, newpatches = prompt(skipfile,
1073 skipall, msg, chunk)
1087 skipall, msg, chunk)
1074 if r:
1088 if r:
1075 if fixoffset:
1089 if fixoffset:
1076 chunk = copy.copy(chunk)
1090 chunk = copy.copy(chunk)
1077 chunk.toline += fixoffset
1091 chunk.toline += fixoffset
1078 applied[chunk.filename()].append(chunk)
1092 applied[chunk.filename()].append(chunk)
1079 elif newpatches is not None:
1093 elif newpatches is not None:
1080 for newpatch in newpatches:
1094 for newpatch in newpatches:
1081 for newhunk in newpatch.hunks:
1095 for newhunk in newpatch.hunks:
1082 if fixoffset:
1096 if fixoffset:
1083 newhunk.toline += fixoffset
1097 newhunk.toline += fixoffset
1084 applied[newhunk.filename()].append(newhunk)
1098 applied[newhunk.filename()].append(newhunk)
1085 else:
1099 else:
1086 fixoffset += chunk.removed - chunk.added
1100 fixoffset += chunk.removed - chunk.added
1087 return sum([h for h in applied.itervalues()
1101 return sum([h for h in applied.itervalues()
1088 if h[0].special() or len(h) > 1], [])
1102 if h[0].special() or len(h) > 1], [])
1089 class hunk(object):
1103 class hunk(object):
1090 def __init__(self, desc, num, lr, context):
1104 def __init__(self, desc, num, lr, context):
1091 self.number = num
1105 self.number = num
1092 self.desc = desc
1106 self.desc = desc
1093 self.hunk = [desc]
1107 self.hunk = [desc]
1094 self.a = []
1108 self.a = []
1095 self.b = []
1109 self.b = []
1096 self.starta = self.lena = None
1110 self.starta = self.lena = None
1097 self.startb = self.lenb = None
1111 self.startb = self.lenb = None
1098 if lr is not None:
1112 if lr is not None:
1099 if context:
1113 if context:
1100 self.read_context_hunk(lr)
1114 self.read_context_hunk(lr)
1101 else:
1115 else:
1102 self.read_unified_hunk(lr)
1116 self.read_unified_hunk(lr)
1103
1117
1104 def getnormalized(self):
1118 def getnormalized(self):
1105 """Return a copy with line endings normalized to LF."""
1119 """Return a copy with line endings normalized to LF."""
1106
1120
1107 def normalize(lines):
1121 def normalize(lines):
1108 nlines = []
1122 nlines = []
1109 for line in lines:
1123 for line in lines:
1110 if line.endswith('\r\n'):
1124 if line.endswith('\r\n'):
1111 line = line[:-2] + '\n'
1125 line = line[:-2] + '\n'
1112 nlines.append(line)
1126 nlines.append(line)
1113 return nlines
1127 return nlines
1114
1128
1115 # Dummy object, it is rebuilt manually
1129 # Dummy object, it is rebuilt manually
1116 nh = hunk(self.desc, self.number, None, None)
1130 nh = hunk(self.desc, self.number, None, None)
1117 nh.number = self.number
1131 nh.number = self.number
1118 nh.desc = self.desc
1132 nh.desc = self.desc
1119 nh.hunk = self.hunk
1133 nh.hunk = self.hunk
1120 nh.a = normalize(self.a)
1134 nh.a = normalize(self.a)
1121 nh.b = normalize(self.b)
1135 nh.b = normalize(self.b)
1122 nh.starta = self.starta
1136 nh.starta = self.starta
1123 nh.startb = self.startb
1137 nh.startb = self.startb
1124 nh.lena = self.lena
1138 nh.lena = self.lena
1125 nh.lenb = self.lenb
1139 nh.lenb = self.lenb
1126 return nh
1140 return nh
1127
1141
1128 def read_unified_hunk(self, lr):
1142 def read_unified_hunk(self, lr):
1129 m = unidesc.match(self.desc)
1143 m = unidesc.match(self.desc)
1130 if not m:
1144 if not m:
1131 raise PatchError(_("bad hunk #%d") % self.number)
1145 raise PatchError(_("bad hunk #%d") % self.number)
1132 self.starta, self.lena, self.startb, self.lenb = m.groups()
1146 self.starta, self.lena, self.startb, self.lenb = m.groups()
1133 if self.lena is None:
1147 if self.lena is None:
1134 self.lena = 1
1148 self.lena = 1
1135 else:
1149 else:
1136 self.lena = int(self.lena)
1150 self.lena = int(self.lena)
1137 if self.lenb is None:
1151 if self.lenb is None:
1138 self.lenb = 1
1152 self.lenb = 1
1139 else:
1153 else:
1140 self.lenb = int(self.lenb)
1154 self.lenb = int(self.lenb)
1141 self.starta = int(self.starta)
1155 self.starta = int(self.starta)
1142 self.startb = int(self.startb)
1156 self.startb = int(self.startb)
1143 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1157 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1144 self.b)
1158 self.b)
1145 # if we hit eof before finishing out the hunk, the last line will
1159 # if we hit eof before finishing out the hunk, the last line will
1146 # be zero length. Lets try to fix it up.
1160 # be zero length. Lets try to fix it up.
1147 while len(self.hunk[-1]) == 0:
1161 while len(self.hunk[-1]) == 0:
1148 del self.hunk[-1]
1162 del self.hunk[-1]
1149 del self.a[-1]
1163 del self.a[-1]
1150 del self.b[-1]
1164 del self.b[-1]
1151 self.lena -= 1
1165 self.lena -= 1
1152 self.lenb -= 1
1166 self.lenb -= 1
1153 self._fixnewline(lr)
1167 self._fixnewline(lr)
1154
1168
1155 def read_context_hunk(self, lr):
1169 def read_context_hunk(self, lr):
1156 self.desc = lr.readline()
1170 self.desc = lr.readline()
1157 m = contextdesc.match(self.desc)
1171 m = contextdesc.match(self.desc)
1158 if not m:
1172 if not m:
1159 raise PatchError(_("bad hunk #%d") % self.number)
1173 raise PatchError(_("bad hunk #%d") % self.number)
1160 self.starta, aend = m.groups()
1174 self.starta, aend = m.groups()
1161 self.starta = int(self.starta)
1175 self.starta = int(self.starta)
1162 if aend is None:
1176 if aend is None:
1163 aend = self.starta
1177 aend = self.starta
1164 self.lena = int(aend) - self.starta
1178 self.lena = int(aend) - self.starta
1165 if self.starta:
1179 if self.starta:
1166 self.lena += 1
1180 self.lena += 1
1167 for x in xrange(self.lena):
1181 for x in xrange(self.lena):
1168 l = lr.readline()
1182 l = lr.readline()
1169 if l.startswith('---'):
1183 if l.startswith('---'):
1170 # lines addition, old block is empty
1184 # lines addition, old block is empty
1171 lr.push(l)
1185 lr.push(l)
1172 break
1186 break
1173 s = l[2:]
1187 s = l[2:]
1174 if l.startswith('- ') or l.startswith('! '):
1188 if l.startswith('- ') or l.startswith('! '):
1175 u = '-' + s
1189 u = '-' + s
1176 elif l.startswith(' '):
1190 elif l.startswith(' '):
1177 u = ' ' + s
1191 u = ' ' + s
1178 else:
1192 else:
1179 raise PatchError(_("bad hunk #%d old text line %d") %
1193 raise PatchError(_("bad hunk #%d old text line %d") %
1180 (self.number, x))
1194 (self.number, x))
1181 self.a.append(u)
1195 self.a.append(u)
1182 self.hunk.append(u)
1196 self.hunk.append(u)
1183
1197
1184 l = lr.readline()
1198 l = lr.readline()
1185 if l.startswith('\ '):
1199 if l.startswith('\ '):
1186 s = self.a[-1][:-1]
1200 s = self.a[-1][:-1]
1187 self.a[-1] = s
1201 self.a[-1] = s
1188 self.hunk[-1] = s
1202 self.hunk[-1] = s
1189 l = lr.readline()
1203 l = lr.readline()
1190 m = contextdesc.match(l)
1204 m = contextdesc.match(l)
1191 if not m:
1205 if not m:
1192 raise PatchError(_("bad hunk #%d") % self.number)
1206 raise PatchError(_("bad hunk #%d") % self.number)
1193 self.startb, bend = m.groups()
1207 self.startb, bend = m.groups()
1194 self.startb = int(self.startb)
1208 self.startb = int(self.startb)
1195 if bend is None:
1209 if bend is None:
1196 bend = self.startb
1210 bend = self.startb
1197 self.lenb = int(bend) - self.startb
1211 self.lenb = int(bend) - self.startb
1198 if self.startb:
1212 if self.startb:
1199 self.lenb += 1
1213 self.lenb += 1
1200 hunki = 1
1214 hunki = 1
1201 for x in xrange(self.lenb):
1215 for x in xrange(self.lenb):
1202 l = lr.readline()
1216 l = lr.readline()
1203 if l.startswith('\ '):
1217 if l.startswith('\ '):
1204 # XXX: the only way to hit this is with an invalid line range.
1218 # 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
1219 # The no-eol marker is not counted in the line range, but I
1206 # guess there are diff(1) out there which behave differently.
1220 # guess there are diff(1) out there which behave differently.
1207 s = self.b[-1][:-1]
1221 s = self.b[-1][:-1]
1208 self.b[-1] = s
1222 self.b[-1] = s
1209 self.hunk[hunki - 1] = s
1223 self.hunk[hunki - 1] = s
1210 continue
1224 continue
1211 if not l:
1225 if not l:
1212 # line deletions, new block is empty and we hit EOF
1226 # line deletions, new block is empty and we hit EOF
1213 lr.push(l)
1227 lr.push(l)
1214 break
1228 break
1215 s = l[2:]
1229 s = l[2:]
1216 if l.startswith('+ ') or l.startswith('! '):
1230 if l.startswith('+ ') or l.startswith('! '):
1217 u = '+' + s
1231 u = '+' + s
1218 elif l.startswith(' '):
1232 elif l.startswith(' '):
1219 u = ' ' + s
1233 u = ' ' + s
1220 elif len(self.b) == 0:
1234 elif len(self.b) == 0:
1221 # line deletions, new block is empty
1235 # line deletions, new block is empty
1222 lr.push(l)
1236 lr.push(l)
1223 break
1237 break
1224 else:
1238 else:
1225 raise PatchError(_("bad hunk #%d old text line %d") %
1239 raise PatchError(_("bad hunk #%d old text line %d") %
1226 (self.number, x))
1240 (self.number, x))
1227 self.b.append(s)
1241 self.b.append(s)
1228 while True:
1242 while True:
1229 if hunki >= len(self.hunk):
1243 if hunki >= len(self.hunk):
1230 h = ""
1244 h = ""
1231 else:
1245 else:
1232 h = self.hunk[hunki]
1246 h = self.hunk[hunki]
1233 hunki += 1
1247 hunki += 1
1234 if h == u:
1248 if h == u:
1235 break
1249 break
1236 elif h.startswith('-'):
1250 elif h.startswith('-'):
1237 continue
1251 continue
1238 else:
1252 else:
1239 self.hunk.insert(hunki - 1, u)
1253 self.hunk.insert(hunki - 1, u)
1240 break
1254 break
1241
1255
1242 if not self.a:
1256 if not self.a:
1243 # this happens when lines were only added to the hunk
1257 # this happens when lines were only added to the hunk
1244 for x in self.hunk:
1258 for x in self.hunk:
1245 if x.startswith('-') or x.startswith(' '):
1259 if x.startswith('-') or x.startswith(' '):
1246 self.a.append(x)
1260 self.a.append(x)
1247 if not self.b:
1261 if not self.b:
1248 # this happens when lines were only deleted from the hunk
1262 # this happens when lines were only deleted from the hunk
1249 for x in self.hunk:
1263 for x in self.hunk:
1250 if x.startswith('+') or x.startswith(' '):
1264 if x.startswith('+') or x.startswith(' '):
1251 self.b.append(x[1:])
1265 self.b.append(x[1:])
1252 # @@ -start,len +start,len @@
1266 # @@ -start,len +start,len @@
1253 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1267 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1254 self.startb, self.lenb)
1268 self.startb, self.lenb)
1255 self.hunk[0] = self.desc
1269 self.hunk[0] = self.desc
1256 self._fixnewline(lr)
1270 self._fixnewline(lr)
1257
1271
1258 def _fixnewline(self, lr):
1272 def _fixnewline(self, lr):
1259 l = lr.readline()
1273 l = lr.readline()
1260 if l.startswith('\ '):
1274 if l.startswith('\ '):
1261 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1275 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1262 else:
1276 else:
1263 lr.push(l)
1277 lr.push(l)
1264
1278
1265 def complete(self):
1279 def complete(self):
1266 return len(self.a) == self.lena and len(self.b) == self.lenb
1280 return len(self.a) == self.lena and len(self.b) == self.lenb
1267
1281
1268 def _fuzzit(self, old, new, fuzz, toponly):
1282 def _fuzzit(self, old, new, fuzz, toponly):
1269 # this removes context lines from the top and bottom of list 'l'. It
1283 # 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
1284 # checks the hunk to make sure only context lines are removed, and then
1271 # returns a new shortened list of lines.
1285 # returns a new shortened list of lines.
1272 fuzz = min(fuzz, len(old))
1286 fuzz = min(fuzz, len(old))
1273 if fuzz:
1287 if fuzz:
1274 top = 0
1288 top = 0
1275 bot = 0
1289 bot = 0
1276 hlen = len(self.hunk)
1290 hlen = len(self.hunk)
1277 for x in xrange(hlen - 1):
1291 for x in xrange(hlen - 1):
1278 # the hunk starts with the @@ line, so use x+1
1292 # the hunk starts with the @@ line, so use x+1
1279 if self.hunk[x + 1][0] == ' ':
1293 if self.hunk[x + 1][0] == ' ':
1280 top += 1
1294 top += 1
1281 else:
1295 else:
1282 break
1296 break
1283 if not toponly:
1297 if not toponly:
1284 for x in xrange(hlen - 1):
1298 for x in xrange(hlen - 1):
1285 if self.hunk[hlen - bot - 1][0] == ' ':
1299 if self.hunk[hlen - bot - 1][0] == ' ':
1286 bot += 1
1300 bot += 1
1287 else:
1301 else:
1288 break
1302 break
1289
1303
1290 bot = min(fuzz, bot)
1304 bot = min(fuzz, bot)
1291 top = min(fuzz, top)
1305 top = min(fuzz, top)
1292 return old[top:len(old) - bot], new[top:len(new) - bot], top
1306 return old[top:len(old) - bot], new[top:len(new) - bot], top
1293 return old, new, 0
1307 return old, new, 0
1294
1308
1295 def fuzzit(self, fuzz, toponly):
1309 def fuzzit(self, fuzz, toponly):
1296 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1310 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1297 oldstart = self.starta + top
1311 oldstart = self.starta + top
1298 newstart = self.startb + top
1312 newstart = self.startb + top
1299 # zero length hunk ranges already have their start decremented
1313 # zero length hunk ranges already have their start decremented
1300 if self.lena and oldstart > 0:
1314 if self.lena and oldstart > 0:
1301 oldstart -= 1
1315 oldstart -= 1
1302 if self.lenb and newstart > 0:
1316 if self.lenb and newstart > 0:
1303 newstart -= 1
1317 newstart -= 1
1304 return old, oldstart, new, newstart
1318 return old, oldstart, new, newstart
1305
1319
1306 class binhunk(object):
1320 class binhunk(object):
1307 'A binary patch file.'
1321 'A binary patch file.'
1308 def __init__(self, lr, fname):
1322 def __init__(self, lr, fname):
1309 self.text = None
1323 self.text = None
1310 self.delta = False
1324 self.delta = False
1311 self.hunk = ['GIT binary patch\n']
1325 self.hunk = ['GIT binary patch\n']
1312 self._fname = fname
1326 self._fname = fname
1313 self._read(lr)
1327 self._read(lr)
1314
1328
1315 def complete(self):
1329 def complete(self):
1316 return self.text is not None
1330 return self.text is not None
1317
1331
1318 def new(self, lines):
1332 def new(self, lines):
1319 if self.delta:
1333 if self.delta:
1320 return [applybindelta(self.text, ''.join(lines))]
1334 return [applybindelta(self.text, ''.join(lines))]
1321 return [self.text]
1335 return [self.text]
1322
1336
1323 def _read(self, lr):
1337 def _read(self, lr):
1324 def getline(lr, hunk):
1338 def getline(lr, hunk):
1325 l = lr.readline()
1339 l = lr.readline()
1326 hunk.append(l)
1340 hunk.append(l)
1327 return l.rstrip('\r\n')
1341 return l.rstrip('\r\n')
1328
1342
1329 size = 0
1343 size = 0
1330 while True:
1344 while True:
1331 line = getline(lr, self.hunk)
1345 line = getline(lr, self.hunk)
1332 if not line:
1346 if not line:
1333 raise PatchError(_('could not extract "%s" binary data')
1347 raise PatchError(_('could not extract "%s" binary data')
1334 % self._fname)
1348 % self._fname)
1335 if line.startswith('literal '):
1349 if line.startswith('literal '):
1336 size = int(line[8:].rstrip())
1350 size = int(line[8:].rstrip())
1337 break
1351 break
1338 if line.startswith('delta '):
1352 if line.startswith('delta '):
1339 size = int(line[6:].rstrip())
1353 size = int(line[6:].rstrip())
1340 self.delta = True
1354 self.delta = True
1341 break
1355 break
1342 dec = []
1356 dec = []
1343 line = getline(lr, self.hunk)
1357 line = getline(lr, self.hunk)
1344 while len(line) > 1:
1358 while len(line) > 1:
1345 l = line[0]
1359 l = line[0]
1346 if l <= 'Z' and l >= 'A':
1360 if l <= 'Z' and l >= 'A':
1347 l = ord(l) - ord('A') + 1
1361 l = ord(l) - ord('A') + 1
1348 else:
1362 else:
1349 l = ord(l) - ord('a') + 27
1363 l = ord(l) - ord('a') + 27
1350 try:
1364 try:
1351 dec.append(base85.b85decode(line[1:])[:l])
1365 dec.append(base85.b85decode(line[1:])[:l])
1352 except ValueError, e:
1366 except ValueError, e:
1353 raise PatchError(_('could not decode "%s" binary patch: %s')
1367 raise PatchError(_('could not decode "%s" binary patch: %s')
1354 % (self._fname, str(e)))
1368 % (self._fname, str(e)))
1355 line = getline(lr, self.hunk)
1369 line = getline(lr, self.hunk)
1356 text = zlib.decompress(''.join(dec))
1370 text = zlib.decompress(''.join(dec))
1357 if len(text) != size:
1371 if len(text) != size:
1358 raise PatchError(_('"%s" length is %d bytes, should be %d')
1372 raise PatchError(_('"%s" length is %d bytes, should be %d')
1359 % (self._fname, len(text), size))
1373 % (self._fname, len(text), size))
1360 self.text = text
1374 self.text = text
1361
1375
1362 def parsefilename(str):
1376 def parsefilename(str):
1363 # --- filename \t|space stuff
1377 # --- filename \t|space stuff
1364 s = str[4:].rstrip('\r\n')
1378 s = str[4:].rstrip('\r\n')
1365 i = s.find('\t')
1379 i = s.find('\t')
1366 if i < 0:
1380 if i < 0:
1367 i = s.find(' ')
1381 i = s.find(' ')
1368 if i < 0:
1382 if i < 0:
1369 return s
1383 return s
1370 return s[:i]
1384 return s[:i]
1371
1385
1372 def parsepatch(originalchunks):
1386 def parsepatch(originalchunks):
1373 """patch -> [] of headers -> [] of hunks """
1387 """patch -> [] of headers -> [] of hunks """
1374 class parser(object):
1388 class parser(object):
1375 """patch parsing state machine"""
1389 """patch parsing state machine"""
1376 def __init__(self):
1390 def __init__(self):
1377 self.fromline = 0
1391 self.fromline = 0
1378 self.toline = 0
1392 self.toline = 0
1379 self.proc = ''
1393 self.proc = ''
1380 self.header = None
1394 self.header = None
1381 self.context = []
1395 self.context = []
1382 self.before = []
1396 self.before = []
1383 self.hunk = []
1397 self.hunk = []
1384 self.headers = []
1398 self.headers = []
1385
1399
1386 def addrange(self, limits):
1400 def addrange(self, limits):
1387 fromstart, fromend, tostart, toend, proc = limits
1401 fromstart, fromend, tostart, toend, proc = limits
1388 self.fromline = int(fromstart)
1402 self.fromline = int(fromstart)
1389 self.toline = int(tostart)
1403 self.toline = int(tostart)
1390 self.proc = proc
1404 self.proc = proc
1391
1405
1392 def addcontext(self, context):
1406 def addcontext(self, context):
1393 if self.hunk:
1407 if self.hunk:
1394 h = recordhunk(self.header, self.fromline, self.toline,
1408 h = recordhunk(self.header, self.fromline, self.toline,
1395 self.proc, self.before, self.hunk, context)
1409 self.proc, self.before, self.hunk, context)
1396 self.header.hunks.append(h)
1410 self.header.hunks.append(h)
1397 self.fromline += len(self.before) + h.removed
1411 self.fromline += len(self.before) + h.removed
1398 self.toline += len(self.before) + h.added
1412 self.toline += len(self.before) + h.added
1399 self.before = []
1413 self.before = []
1400 self.hunk = []
1414 self.hunk = []
1401 self.proc = ''
1415 self.proc = ''
1402 self.context = context
1416 self.context = context
1403
1417
1404 def addhunk(self, hunk):
1418 def addhunk(self, hunk):
1405 if self.context:
1419 if self.context:
1406 self.before = self.context
1420 self.before = self.context
1407 self.context = []
1421 self.context = []
1408 self.hunk = hunk
1422 self.hunk = hunk
1409
1423
1410 def newfile(self, hdr):
1424 def newfile(self, hdr):
1411 self.addcontext([])
1425 self.addcontext([])
1412 h = header(hdr)
1426 h = header(hdr)
1413 self.headers.append(h)
1427 self.headers.append(h)
1414 self.header = h
1428 self.header = h
1415
1429
1416 def addother(self, line):
1430 def addother(self, line):
1417 pass # 'other' lines are ignored
1431 pass # 'other' lines are ignored
1418
1432
1419 def finished(self):
1433 def finished(self):
1420 self.addcontext([])
1434 self.addcontext([])
1421 return self.headers
1435 return self.headers
1422
1436
1423 transitions = {
1437 transitions = {
1424 'file': {'context': addcontext,
1438 'file': {'context': addcontext,
1425 'file': newfile,
1439 'file': newfile,
1426 'hunk': addhunk,
1440 'hunk': addhunk,
1427 'range': addrange},
1441 'range': addrange},
1428 'context': {'file': newfile,
1442 'context': {'file': newfile,
1429 'hunk': addhunk,
1443 'hunk': addhunk,
1430 'range': addrange,
1444 'range': addrange,
1431 'other': addother},
1445 'other': addother},
1432 'hunk': {'context': addcontext,
1446 'hunk': {'context': addcontext,
1433 'file': newfile,
1447 'file': newfile,
1434 'range': addrange},
1448 'range': addrange},
1435 'range': {'context': addcontext,
1449 'range': {'context': addcontext,
1436 'hunk': addhunk},
1450 'hunk': addhunk},
1437 'other': {'other': addother},
1451 'other': {'other': addother},
1438 }
1452 }
1439
1453
1440 p = parser()
1454 p = parser()
1441 fp = cStringIO.StringIO()
1455 fp = cStringIO.StringIO()
1442 fp.write(''.join(originalchunks))
1456 fp.write(''.join(originalchunks))
1443 fp.seek(0)
1457 fp.seek(0)
1444
1458
1445 state = 'context'
1459 state = 'context'
1446 for newstate, data in scanpatch(fp):
1460 for newstate, data in scanpatch(fp):
1447 try:
1461 try:
1448 p.transitions[state][newstate](p, data)
1462 p.transitions[state][newstate](p, data)
1449 except KeyError:
1463 except KeyError:
1450 raise PatchError('unhandled transition: %s -> %s' %
1464 raise PatchError('unhandled transition: %s -> %s' %
1451 (state, newstate))
1465 (state, newstate))
1452 state = newstate
1466 state = newstate
1453 del fp
1467 del fp
1454 return p.finished()
1468 return p.finished()
1455
1469
1456 def pathtransform(path, strip, prefix):
1470 def pathtransform(path, strip, prefix):
1457 '''turn a path from a patch into a path suitable for the repository
1471 '''turn a path from a patch into a path suitable for the repository
1458
1472
1459 prefix, if not empty, is expected to be normalized with a / at the end.
1473 prefix, if not empty, is expected to be normalized with a / at the end.
1460
1474
1461 Returns (stripped components, path in repository).
1475 Returns (stripped components, path in repository).
1462
1476
1463 >>> pathtransform('a/b/c', 0, '')
1477 >>> pathtransform('a/b/c', 0, '')
1464 ('', 'a/b/c')
1478 ('', 'a/b/c')
1465 >>> pathtransform(' a/b/c ', 0, '')
1479 >>> pathtransform(' a/b/c ', 0, '')
1466 ('', ' a/b/c')
1480 ('', ' a/b/c')
1467 >>> pathtransform(' a/b/c ', 2, '')
1481 >>> pathtransform(' a/b/c ', 2, '')
1468 ('a/b/', 'c')
1482 ('a/b/', 'c')
1469 >>> pathtransform('a/b/c', 0, 'd/e/')
1483 >>> pathtransform('a/b/c', 0, 'd/e/')
1470 ('', 'd/e/a/b/c')
1484 ('', 'd/e/a/b/c')
1471 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1485 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1472 ('a//b/', 'd/e/c')
1486 ('a//b/', 'd/e/c')
1473 >>> pathtransform('a/b/c', 3, '')
1487 >>> pathtransform('a/b/c', 3, '')
1474 Traceback (most recent call last):
1488 Traceback (most recent call last):
1475 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1489 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1476 '''
1490 '''
1477 pathlen = len(path)
1491 pathlen = len(path)
1478 i = 0
1492 i = 0
1479 if strip == 0:
1493 if strip == 0:
1480 return '', prefix + path.rstrip()
1494 return '', prefix + path.rstrip()
1481 count = strip
1495 count = strip
1482 while count > 0:
1496 while count > 0:
1483 i = path.find('/', i)
1497 i = path.find('/', i)
1484 if i == -1:
1498 if i == -1:
1485 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1499 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1486 (count, strip, path))
1500 (count, strip, path))
1487 i += 1
1501 i += 1
1488 # consume '//' in the path
1502 # consume '//' in the path
1489 while i < pathlen - 1 and path[i] == '/':
1503 while i < pathlen - 1 and path[i] == '/':
1490 i += 1
1504 i += 1
1491 count -= 1
1505 count -= 1
1492 return path[:i].lstrip(), prefix + path[i:].rstrip()
1506 return path[:i].lstrip(), prefix + path[i:].rstrip()
1493
1507
1494 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1508 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1495 nulla = afile_orig == "/dev/null"
1509 nulla = afile_orig == "/dev/null"
1496 nullb = bfile_orig == "/dev/null"
1510 nullb = bfile_orig == "/dev/null"
1497 create = nulla and hunk.starta == 0 and hunk.lena == 0
1511 create = nulla and hunk.starta == 0 and hunk.lena == 0
1498 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1512 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1499 abase, afile = pathtransform(afile_orig, strip, prefix)
1513 abase, afile = pathtransform(afile_orig, strip, prefix)
1500 gooda = not nulla and backend.exists(afile)
1514 gooda = not nulla and backend.exists(afile)
1501 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1515 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1502 if afile == bfile:
1516 if afile == bfile:
1503 goodb = gooda
1517 goodb = gooda
1504 else:
1518 else:
1505 goodb = not nullb and backend.exists(bfile)
1519 goodb = not nullb and backend.exists(bfile)
1506 missing = not goodb and not gooda and not create
1520 missing = not goodb and not gooda and not create
1507
1521
1508 # some diff programs apparently produce patches where the afile is
1522 # some diff programs apparently produce patches where the afile is
1509 # not /dev/null, but afile starts with bfile
1523 # not /dev/null, but afile starts with bfile
1510 abasedir = afile[:afile.rfind('/') + 1]
1524 abasedir = afile[:afile.rfind('/') + 1]
1511 bbasedir = bfile[:bfile.rfind('/') + 1]
1525 bbasedir = bfile[:bfile.rfind('/') + 1]
1512 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1526 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1513 and hunk.starta == 0 and hunk.lena == 0):
1527 and hunk.starta == 0 and hunk.lena == 0):
1514 create = True
1528 create = True
1515 missing = False
1529 missing = False
1516
1530
1517 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1531 # 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
1532 # diff is between a file and its backup. In this case, the original
1519 # file should be patched (see original mpatch code).
1533 # file should be patched (see original mpatch code).
1520 isbackup = (abase == bbase and bfile.startswith(afile))
1534 isbackup = (abase == bbase and bfile.startswith(afile))
1521 fname = None
1535 fname = None
1522 if not missing:
1536 if not missing:
1523 if gooda and goodb:
1537 if gooda and goodb:
1524 if isbackup:
1538 if isbackup:
1525 fname = afile
1539 fname = afile
1526 else:
1540 else:
1527 fname = bfile
1541 fname = bfile
1528 elif gooda:
1542 elif gooda:
1529 fname = afile
1543 fname = afile
1530
1544
1531 if not fname:
1545 if not fname:
1532 if not nullb:
1546 if not nullb:
1533 if isbackup:
1547 if isbackup:
1534 fname = afile
1548 fname = afile
1535 else:
1549 else:
1536 fname = bfile
1550 fname = bfile
1537 elif not nulla:
1551 elif not nulla:
1538 fname = afile
1552 fname = afile
1539 else:
1553 else:
1540 raise PatchError(_("undefined source and destination files"))
1554 raise PatchError(_("undefined source and destination files"))
1541
1555
1542 gp = patchmeta(fname)
1556 gp = patchmeta(fname)
1543 if create:
1557 if create:
1544 gp.op = 'ADD'
1558 gp.op = 'ADD'
1545 elif remove:
1559 elif remove:
1546 gp.op = 'DELETE'
1560 gp.op = 'DELETE'
1547 return gp
1561 return gp
1548
1562
1549 def scanpatch(fp):
1563 def scanpatch(fp):
1550 """like patch.iterhunks, but yield different events
1564 """like patch.iterhunks, but yield different events
1551
1565
1552 - ('file', [header_lines + fromfile + tofile])
1566 - ('file', [header_lines + fromfile + tofile])
1553 - ('context', [context_lines])
1567 - ('context', [context_lines])
1554 - ('hunk', [hunk_lines])
1568 - ('hunk', [hunk_lines])
1555 - ('range', (-start,len, +start,len, proc))
1569 - ('range', (-start,len, +start,len, proc))
1556 """
1570 """
1557 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1571 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1558 lr = linereader(fp)
1572 lr = linereader(fp)
1559
1573
1560 def scanwhile(first, p):
1574 def scanwhile(first, p):
1561 """scan lr while predicate holds"""
1575 """scan lr while predicate holds"""
1562 lines = [first]
1576 lines = [first]
1563 while True:
1577 while True:
1564 line = lr.readline()
1578 line = lr.readline()
1565 if not line:
1579 if not line:
1566 break
1580 break
1567 if p(line):
1581 if p(line):
1568 lines.append(line)
1582 lines.append(line)
1569 else:
1583 else:
1570 lr.push(line)
1584 lr.push(line)
1571 break
1585 break
1572 return lines
1586 return lines
1573
1587
1574 while True:
1588 while True:
1575 line = lr.readline()
1589 line = lr.readline()
1576 if not line:
1590 if not line:
1577 break
1591 break
1578 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1592 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1579 def notheader(line):
1593 def notheader(line):
1580 s = line.split(None, 1)
1594 s = line.split(None, 1)
1581 return not s or s[0] not in ('---', 'diff')
1595 return not s or s[0] not in ('---', 'diff')
1582 header = scanwhile(line, notheader)
1596 header = scanwhile(line, notheader)
1583 fromfile = lr.readline()
1597 fromfile = lr.readline()
1584 if fromfile.startswith('---'):
1598 if fromfile.startswith('---'):
1585 tofile = lr.readline()
1599 tofile = lr.readline()
1586 header += [fromfile, tofile]
1600 header += [fromfile, tofile]
1587 else:
1601 else:
1588 lr.push(fromfile)
1602 lr.push(fromfile)
1589 yield 'file', header
1603 yield 'file', header
1590 elif line[0] == ' ':
1604 elif line[0] == ' ':
1591 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1605 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1592 elif line[0] in '-+':
1606 elif line[0] in '-+':
1593 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1607 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1594 else:
1608 else:
1595 m = lines_re.match(line)
1609 m = lines_re.match(line)
1596 if m:
1610 if m:
1597 yield 'range', m.groups()
1611 yield 'range', m.groups()
1598 else:
1612 else:
1599 yield 'other', line
1613 yield 'other', line
1600
1614
1601 def scangitpatch(lr, firstline):
1615 def scangitpatch(lr, firstline):
1602 """
1616 """
1603 Git patches can emit:
1617 Git patches can emit:
1604 - rename a to b
1618 - rename a to b
1605 - change b
1619 - change b
1606 - copy a to c
1620 - copy a to c
1607 - change c
1621 - change c
1608
1622
1609 We cannot apply this sequence as-is, the renamed 'a' could not be
1623 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
1624 found for it would have been renamed already. And we cannot copy
1611 from 'b' instead because 'b' would have been changed already. So
1625 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
1626 we scan the git patch for copy and rename commands so we can
1613 perform the copies ahead of time.
1627 perform the copies ahead of time.
1614 """
1628 """
1615 pos = 0
1629 pos = 0
1616 try:
1630 try:
1617 pos = lr.fp.tell()
1631 pos = lr.fp.tell()
1618 fp = lr.fp
1632 fp = lr.fp
1619 except IOError:
1633 except IOError:
1620 fp = cStringIO.StringIO(lr.fp.read())
1634 fp = cStringIO.StringIO(lr.fp.read())
1621 gitlr = linereader(fp)
1635 gitlr = linereader(fp)
1622 gitlr.push(firstline)
1636 gitlr.push(firstline)
1623 gitpatches = readgitpatch(gitlr)
1637 gitpatches = readgitpatch(gitlr)
1624 fp.seek(pos)
1638 fp.seek(pos)
1625 return gitpatches
1639 return gitpatches
1626
1640
1627 def iterhunks(fp):
1641 def iterhunks(fp):
1628 """Read a patch and yield the following events:
1642 """Read a patch and yield the following events:
1629 - ("file", afile, bfile, firsthunk): select a new target file.
1643 - ("file", afile, bfile, firsthunk): select a new target file.
1630 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1644 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1631 "file" event.
1645 "file" event.
1632 - ("git", gitchanges): current diff is in git format, gitchanges
1646 - ("git", gitchanges): current diff is in git format, gitchanges
1633 maps filenames to gitpatch records. Unique event.
1647 maps filenames to gitpatch records. Unique event.
1634 """
1648 """
1635 afile = ""
1649 afile = ""
1636 bfile = ""
1650 bfile = ""
1637 state = None
1651 state = None
1638 hunknum = 0
1652 hunknum = 0
1639 emitfile = newfile = False
1653 emitfile = newfile = False
1640 gitpatches = None
1654 gitpatches = None
1641
1655
1642 # our states
1656 # our states
1643 BFILE = 1
1657 BFILE = 1
1644 context = None
1658 context = None
1645 lr = linereader(fp)
1659 lr = linereader(fp)
1646
1660
1647 while True:
1661 while True:
1648 x = lr.readline()
1662 x = lr.readline()
1649 if not x:
1663 if not x:
1650 break
1664 break
1651 if state == BFILE and (
1665 if state == BFILE and (
1652 (not context and x[0] == '@')
1666 (not context and x[0] == '@')
1653 or (context is not False and x.startswith('***************'))
1667 or (context is not False and x.startswith('***************'))
1654 or x.startswith('GIT binary patch')):
1668 or x.startswith('GIT binary patch')):
1655 gp = None
1669 gp = None
1656 if (gitpatches and
1670 if (gitpatches and
1657 gitpatches[-1].ispatching(afile, bfile)):
1671 gitpatches[-1].ispatching(afile, bfile)):
1658 gp = gitpatches.pop()
1672 gp = gitpatches.pop()
1659 if x.startswith('GIT binary patch'):
1673 if x.startswith('GIT binary patch'):
1660 h = binhunk(lr, gp.path)
1674 h = binhunk(lr, gp.path)
1661 else:
1675 else:
1662 if context is None and x.startswith('***************'):
1676 if context is None and x.startswith('***************'):
1663 context = True
1677 context = True
1664 h = hunk(x, hunknum + 1, lr, context)
1678 h = hunk(x, hunknum + 1, lr, context)
1665 hunknum += 1
1679 hunknum += 1
1666 if emitfile:
1680 if emitfile:
1667 emitfile = False
1681 emitfile = False
1668 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1682 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1669 yield 'hunk', h
1683 yield 'hunk', h
1670 elif x.startswith('diff --git a/'):
1684 elif x.startswith('diff --git a/'):
1671 m = gitre.match(x.rstrip(' \r\n'))
1685 m = gitre.match(x.rstrip(' \r\n'))
1672 if not m:
1686 if not m:
1673 continue
1687 continue
1674 if gitpatches is None:
1688 if gitpatches is None:
1675 # scan whole input for git metadata
1689 # scan whole input for git metadata
1676 gitpatches = scangitpatch(lr, x)
1690 gitpatches = scangitpatch(lr, x)
1677 yield 'git', [g.copy() for g in gitpatches
1691 yield 'git', [g.copy() for g in gitpatches
1678 if g.op in ('COPY', 'RENAME')]
1692 if g.op in ('COPY', 'RENAME')]
1679 gitpatches.reverse()
1693 gitpatches.reverse()
1680 afile = 'a/' + m.group(1)
1694 afile = 'a/' + m.group(1)
1681 bfile = 'b/' + m.group(2)
1695 bfile = 'b/' + m.group(2)
1682 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1696 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1683 gp = gitpatches.pop()
1697 gp = gitpatches.pop()
1684 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1698 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1685 if not gitpatches:
1699 if not gitpatches:
1686 raise PatchError(_('failed to synchronize metadata for "%s"')
1700 raise PatchError(_('failed to synchronize metadata for "%s"')
1687 % afile[2:])
1701 % afile[2:])
1688 gp = gitpatches[-1]
1702 gp = gitpatches[-1]
1689 newfile = True
1703 newfile = True
1690 elif x.startswith('---'):
1704 elif x.startswith('---'):
1691 # check for a unified diff
1705 # check for a unified diff
1692 l2 = lr.readline()
1706 l2 = lr.readline()
1693 if not l2.startswith('+++'):
1707 if not l2.startswith('+++'):
1694 lr.push(l2)
1708 lr.push(l2)
1695 continue
1709 continue
1696 newfile = True
1710 newfile = True
1697 context = False
1711 context = False
1698 afile = parsefilename(x)
1712 afile = parsefilename(x)
1699 bfile = parsefilename(l2)
1713 bfile = parsefilename(l2)
1700 elif x.startswith('***'):
1714 elif x.startswith('***'):
1701 # check for a context diff
1715 # check for a context diff
1702 l2 = lr.readline()
1716 l2 = lr.readline()
1703 if not l2.startswith('---'):
1717 if not l2.startswith('---'):
1704 lr.push(l2)
1718 lr.push(l2)
1705 continue
1719 continue
1706 l3 = lr.readline()
1720 l3 = lr.readline()
1707 lr.push(l3)
1721 lr.push(l3)
1708 if not l3.startswith("***************"):
1722 if not l3.startswith("***************"):
1709 lr.push(l2)
1723 lr.push(l2)
1710 continue
1724 continue
1711 newfile = True
1725 newfile = True
1712 context = True
1726 context = True
1713 afile = parsefilename(x)
1727 afile = parsefilename(x)
1714 bfile = parsefilename(l2)
1728 bfile = parsefilename(l2)
1715
1729
1716 if newfile:
1730 if newfile:
1717 newfile = False
1731 newfile = False
1718 emitfile = True
1732 emitfile = True
1719 state = BFILE
1733 state = BFILE
1720 hunknum = 0
1734 hunknum = 0
1721
1735
1722 while gitpatches:
1736 while gitpatches:
1723 gp = gitpatches.pop()
1737 gp = gitpatches.pop()
1724 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1738 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1725
1739
1726 def applybindelta(binchunk, data):
1740 def applybindelta(binchunk, data):
1727 """Apply a binary delta hunk
1741 """Apply a binary delta hunk
1728 The algorithm used is the algorithm from git's patch-delta.c
1742 The algorithm used is the algorithm from git's patch-delta.c
1729 """
1743 """
1730 def deltahead(binchunk):
1744 def deltahead(binchunk):
1731 i = 0
1745 i = 0
1732 for c in binchunk:
1746 for c in binchunk:
1733 i += 1
1747 i += 1
1734 if not (ord(c) & 0x80):
1748 if not (ord(c) & 0x80):
1735 return i
1749 return i
1736 return i
1750 return i
1737 out = ""
1751 out = ""
1738 s = deltahead(binchunk)
1752 s = deltahead(binchunk)
1739 binchunk = binchunk[s:]
1753 binchunk = binchunk[s:]
1740 s = deltahead(binchunk)
1754 s = deltahead(binchunk)
1741 binchunk = binchunk[s:]
1755 binchunk = binchunk[s:]
1742 i = 0
1756 i = 0
1743 while i < len(binchunk):
1757 while i < len(binchunk):
1744 cmd = ord(binchunk[i])
1758 cmd = ord(binchunk[i])
1745 i += 1
1759 i += 1
1746 if (cmd & 0x80):
1760 if (cmd & 0x80):
1747 offset = 0
1761 offset = 0
1748 size = 0
1762 size = 0
1749 if (cmd & 0x01):
1763 if (cmd & 0x01):
1750 offset = ord(binchunk[i])
1764 offset = ord(binchunk[i])
1751 i += 1
1765 i += 1
1752 if (cmd & 0x02):
1766 if (cmd & 0x02):
1753 offset |= ord(binchunk[i]) << 8
1767 offset |= ord(binchunk[i]) << 8
1754 i += 1
1768 i += 1
1755 if (cmd & 0x04):
1769 if (cmd & 0x04):
1756 offset |= ord(binchunk[i]) << 16
1770 offset |= ord(binchunk[i]) << 16
1757 i += 1
1771 i += 1
1758 if (cmd & 0x08):
1772 if (cmd & 0x08):
1759 offset |= ord(binchunk[i]) << 24
1773 offset |= ord(binchunk[i]) << 24
1760 i += 1
1774 i += 1
1761 if (cmd & 0x10):
1775 if (cmd & 0x10):
1762 size = ord(binchunk[i])
1776 size = ord(binchunk[i])
1763 i += 1
1777 i += 1
1764 if (cmd & 0x20):
1778 if (cmd & 0x20):
1765 size |= ord(binchunk[i]) << 8
1779 size |= ord(binchunk[i]) << 8
1766 i += 1
1780 i += 1
1767 if (cmd & 0x40):
1781 if (cmd & 0x40):
1768 size |= ord(binchunk[i]) << 16
1782 size |= ord(binchunk[i]) << 16
1769 i += 1
1783 i += 1
1770 if size == 0:
1784 if size == 0:
1771 size = 0x10000
1785 size = 0x10000
1772 offset_end = offset + size
1786 offset_end = offset + size
1773 out += data[offset:offset_end]
1787 out += data[offset:offset_end]
1774 elif cmd != 0:
1788 elif cmd != 0:
1775 offset_end = i + cmd
1789 offset_end = i + cmd
1776 out += binchunk[i:offset_end]
1790 out += binchunk[i:offset_end]
1777 i += cmd
1791 i += cmd
1778 else:
1792 else:
1779 raise PatchError(_('unexpected delta opcode 0'))
1793 raise PatchError(_('unexpected delta opcode 0'))
1780 return out
1794 return out
1781
1795
1782 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1796 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1783 """Reads a patch from fp and tries to apply it.
1797 """Reads a patch from fp and tries to apply it.
1784
1798
1785 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1799 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1786 there was any fuzz.
1800 there was any fuzz.
1787
1801
1788 If 'eolmode' is 'strict', the patch content and patched file are
1802 If 'eolmode' is 'strict', the patch content and patched file are
1789 read in binary mode. Otherwise, line endings are ignored when
1803 read in binary mode. Otherwise, line endings are ignored when
1790 patching then normalized according to 'eolmode'.
1804 patching then normalized according to 'eolmode'.
1791 """
1805 """
1792 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1806 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1793 prefix=prefix, eolmode=eolmode)
1807 prefix=prefix, eolmode=eolmode)
1794
1808
1795 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1809 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1796 eolmode='strict'):
1810 eolmode='strict'):
1797
1811
1798 if prefix:
1812 if prefix:
1799 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1813 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1800 prefix)
1814 prefix)
1801 if prefix != '':
1815 if prefix != '':
1802 prefix += '/'
1816 prefix += '/'
1803 def pstrip(p):
1817 def pstrip(p):
1804 return pathtransform(p, strip - 1, prefix)[1]
1818 return pathtransform(p, strip - 1, prefix)[1]
1805
1819
1806 rejects = 0
1820 rejects = 0
1807 err = 0
1821 err = 0
1808 current_file = None
1822 current_file = None
1809
1823
1810 for state, values in iterhunks(fp):
1824 for state, values in iterhunks(fp):
1811 if state == 'hunk':
1825 if state == 'hunk':
1812 if not current_file:
1826 if not current_file:
1813 continue
1827 continue
1814 ret = current_file.apply(values)
1828 ret = current_file.apply(values)
1815 if ret > 0:
1829 if ret > 0:
1816 err = 1
1830 err = 1
1817 elif state == 'file':
1831 elif state == 'file':
1818 if current_file:
1832 if current_file:
1819 rejects += current_file.close()
1833 rejects += current_file.close()
1820 current_file = None
1834 current_file = None
1821 afile, bfile, first_hunk, gp = values
1835 afile, bfile, first_hunk, gp = values
1822 if gp:
1836 if gp:
1823 gp.path = pstrip(gp.path)
1837 gp.path = pstrip(gp.path)
1824 if gp.oldpath:
1838 if gp.oldpath:
1825 gp.oldpath = pstrip(gp.oldpath)
1839 gp.oldpath = pstrip(gp.oldpath)
1826 else:
1840 else:
1827 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1841 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1828 prefix)
1842 prefix)
1829 if gp.op == 'RENAME':
1843 if gp.op == 'RENAME':
1830 backend.unlink(gp.oldpath)
1844 backend.unlink(gp.oldpath)
1831 if not first_hunk:
1845 if not first_hunk:
1832 if gp.op == 'DELETE':
1846 if gp.op == 'DELETE':
1833 backend.unlink(gp.path)
1847 backend.unlink(gp.path)
1834 continue
1848 continue
1835 data, mode = None, None
1849 data, mode = None, None
1836 if gp.op in ('RENAME', 'COPY'):
1850 if gp.op in ('RENAME', 'COPY'):
1837 data, mode = store.getfile(gp.oldpath)[:2]
1851 data, mode = store.getfile(gp.oldpath)[:2]
1838 # FIXME: failing getfile has never been handled here
1852 # FIXME: failing getfile has never been handled here
1839 assert data is not None
1853 assert data is not None
1840 if gp.mode:
1854 if gp.mode:
1841 mode = gp.mode
1855 mode = gp.mode
1842 if gp.op == 'ADD':
1856 if gp.op == 'ADD':
1843 # Added files without content have no hunk and
1857 # Added files without content have no hunk and
1844 # must be created
1858 # must be created
1845 data = ''
1859 data = ''
1846 if data or mode:
1860 if data or mode:
1847 if (gp.op in ('ADD', 'RENAME', 'COPY')
1861 if (gp.op in ('ADD', 'RENAME', 'COPY')
1848 and backend.exists(gp.path)):
1862 and backend.exists(gp.path)):
1849 raise PatchError(_("cannot create %s: destination "
1863 raise PatchError(_("cannot create %s: destination "
1850 "already exists") % gp.path)
1864 "already exists") % gp.path)
1851 backend.setfile(gp.path, data, mode, gp.oldpath)
1865 backend.setfile(gp.path, data, mode, gp.oldpath)
1852 continue
1866 continue
1853 try:
1867 try:
1854 current_file = patcher(ui, gp, backend, store,
1868 current_file = patcher(ui, gp, backend, store,
1855 eolmode=eolmode)
1869 eolmode=eolmode)
1856 except PatchError, inst:
1870 except PatchError, inst:
1857 ui.warn(str(inst) + '\n')
1871 ui.warn(str(inst) + '\n')
1858 current_file = None
1872 current_file = None
1859 rejects += 1
1873 rejects += 1
1860 continue
1874 continue
1861 elif state == 'git':
1875 elif state == 'git':
1862 for gp in values:
1876 for gp in values:
1863 path = pstrip(gp.oldpath)
1877 path = pstrip(gp.oldpath)
1864 data, mode = backend.getfile(path)
1878 data, mode = backend.getfile(path)
1865 if data is None:
1879 if data is None:
1866 # The error ignored here will trigger a getfile()
1880 # The error ignored here will trigger a getfile()
1867 # error in a place more appropriate for error
1881 # error in a place more appropriate for error
1868 # handling, and will not interrupt the patching
1882 # handling, and will not interrupt the patching
1869 # process.
1883 # process.
1870 pass
1884 pass
1871 else:
1885 else:
1872 store.setfile(path, data, mode)
1886 store.setfile(path, data, mode)
1873 else:
1887 else:
1874 raise util.Abort(_('unsupported parser state: %s') % state)
1888 raise util.Abort(_('unsupported parser state: %s') % state)
1875
1889
1876 if current_file:
1890 if current_file:
1877 rejects += current_file.close()
1891 rejects += current_file.close()
1878
1892
1879 if rejects:
1893 if rejects:
1880 return -1
1894 return -1
1881 return err
1895 return err
1882
1896
1883 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1897 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1884 similarity):
1898 similarity):
1885 """use <patcher> to apply <patchname> to the working directory.
1899 """use <patcher> to apply <patchname> to the working directory.
1886 returns whether patch was applied with fuzz factor."""
1900 returns whether patch was applied with fuzz factor."""
1887
1901
1888 fuzz = False
1902 fuzz = False
1889 args = []
1903 args = []
1890 cwd = repo.root
1904 cwd = repo.root
1891 if cwd:
1905 if cwd:
1892 args.append('-d %s' % util.shellquote(cwd))
1906 args.append('-d %s' % util.shellquote(cwd))
1893 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1907 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1894 util.shellquote(patchname)))
1908 util.shellquote(patchname)))
1895 try:
1909 try:
1896 for line in fp:
1910 for line in fp:
1897 line = line.rstrip()
1911 line = line.rstrip()
1898 ui.note(line + '\n')
1912 ui.note(line + '\n')
1899 if line.startswith('patching file '):
1913 if line.startswith('patching file '):
1900 pf = util.parsepatchoutput(line)
1914 pf = util.parsepatchoutput(line)
1901 printed_file = False
1915 printed_file = False
1902 files.add(pf)
1916 files.add(pf)
1903 elif line.find('with fuzz') >= 0:
1917 elif line.find('with fuzz') >= 0:
1904 fuzz = True
1918 fuzz = True
1905 if not printed_file:
1919 if not printed_file:
1906 ui.warn(pf + '\n')
1920 ui.warn(pf + '\n')
1907 printed_file = True
1921 printed_file = True
1908 ui.warn(line + '\n')
1922 ui.warn(line + '\n')
1909 elif line.find('saving rejects to file') >= 0:
1923 elif line.find('saving rejects to file') >= 0:
1910 ui.warn(line + '\n')
1924 ui.warn(line + '\n')
1911 elif line.find('FAILED') >= 0:
1925 elif line.find('FAILED') >= 0:
1912 if not printed_file:
1926 if not printed_file:
1913 ui.warn(pf + '\n')
1927 ui.warn(pf + '\n')
1914 printed_file = True
1928 printed_file = True
1915 ui.warn(line + '\n')
1929 ui.warn(line + '\n')
1916 finally:
1930 finally:
1917 if files:
1931 if files:
1918 scmutil.marktouched(repo, files, similarity)
1932 scmutil.marktouched(repo, files, similarity)
1919 code = fp.close()
1933 code = fp.close()
1920 if code:
1934 if code:
1921 raise PatchError(_("patch command failed: %s") %
1935 raise PatchError(_("patch command failed: %s") %
1922 util.explainexit(code)[0])
1936 util.explainexit(code)[0])
1923 return fuzz
1937 return fuzz
1924
1938
1925 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1939 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1926 eolmode='strict'):
1940 eolmode='strict'):
1927 if files is None:
1941 if files is None:
1928 files = set()
1942 files = set()
1929 if eolmode is None:
1943 if eolmode is None:
1930 eolmode = ui.config('patch', 'eol', 'strict')
1944 eolmode = ui.config('patch', 'eol', 'strict')
1931 if eolmode.lower() not in eolmodes:
1945 if eolmode.lower() not in eolmodes:
1932 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1946 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1933 eolmode = eolmode.lower()
1947 eolmode = eolmode.lower()
1934
1948
1935 store = filestore()
1949 store = filestore()
1936 try:
1950 try:
1937 fp = open(patchobj, 'rb')
1951 fp = open(patchobj, 'rb')
1938 except TypeError:
1952 except TypeError:
1939 fp = patchobj
1953 fp = patchobj
1940 try:
1954 try:
1941 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1955 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1942 eolmode=eolmode)
1956 eolmode=eolmode)
1943 finally:
1957 finally:
1944 if fp != patchobj:
1958 if fp != patchobj:
1945 fp.close()
1959 fp.close()
1946 files.update(backend.close())
1960 files.update(backend.close())
1947 store.close()
1961 store.close()
1948 if ret < 0:
1962 if ret < 0:
1949 raise PatchError(_('patch failed to apply'))
1963 raise PatchError(_('patch failed to apply'))
1950 return ret > 0
1964 return ret > 0
1951
1965
1952 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
1966 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
1953 eolmode='strict', similarity=0):
1967 eolmode='strict', similarity=0):
1954 """use builtin patch to apply <patchobj> to the working directory.
1968 """use builtin patch to apply <patchobj> to the working directory.
1955 returns whether patch was applied with fuzz factor."""
1969 returns whether patch was applied with fuzz factor."""
1956 backend = workingbackend(ui, repo, similarity)
1970 backend = workingbackend(ui, repo, similarity)
1957 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1971 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1958
1972
1959 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
1973 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
1960 eolmode='strict'):
1974 eolmode='strict'):
1961 backend = repobackend(ui, repo, ctx, store)
1975 backend = repobackend(ui, repo, ctx, store)
1962 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1976 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1963
1977
1964 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1978 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1965 similarity=0):
1979 similarity=0):
1966 """Apply <patchname> to the working directory.
1980 """Apply <patchname> to the working directory.
1967
1981
1968 'eolmode' specifies how end of lines should be handled. It can be:
1982 'eolmode' specifies how end of lines should be handled. It can be:
1969 - 'strict': inputs are read in binary mode, EOLs are preserved
1983 - 'strict': inputs are read in binary mode, EOLs are preserved
1970 - 'crlf': EOLs are ignored when patching and reset to CRLF
1984 - 'crlf': EOLs are ignored when patching and reset to CRLF
1971 - 'lf': EOLs are ignored when patching and reset to LF
1985 - 'lf': EOLs are ignored when patching and reset to LF
1972 - None: get it from user settings, default to 'strict'
1986 - None: get it from user settings, default to 'strict'
1973 'eolmode' is ignored when using an external patcher program.
1987 'eolmode' is ignored when using an external patcher program.
1974
1988
1975 Returns whether patch was applied with fuzz factor.
1989 Returns whether patch was applied with fuzz factor.
1976 """
1990 """
1977 patcher = ui.config('ui', 'patch')
1991 patcher = ui.config('ui', 'patch')
1978 if files is None:
1992 if files is None:
1979 files = set()
1993 files = set()
1980 if patcher:
1994 if patcher:
1981 return _externalpatch(ui, repo, patcher, patchname, strip,
1995 return _externalpatch(ui, repo, patcher, patchname, strip,
1982 files, similarity)
1996 files, similarity)
1983 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1997 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1984 similarity)
1998 similarity)
1985
1999
1986 def changedfiles(ui, repo, patchpath, strip=1):
2000 def changedfiles(ui, repo, patchpath, strip=1):
1987 backend = fsbackend(ui, repo.root)
2001 backend = fsbackend(ui, repo.root)
1988 fp = open(patchpath, 'rb')
2002 fp = open(patchpath, 'rb')
1989 try:
2003 try:
1990 changed = set()
2004 changed = set()
1991 for state, values in iterhunks(fp):
2005 for state, values in iterhunks(fp):
1992 if state == 'file':
2006 if state == 'file':
1993 afile, bfile, first_hunk, gp = values
2007 afile, bfile, first_hunk, gp = values
1994 if gp:
2008 if gp:
1995 gp.path = pathtransform(gp.path, strip - 1, '')[1]
2009 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1996 if gp.oldpath:
2010 if gp.oldpath:
1997 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
2011 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1998 else:
2012 else:
1999 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2013 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2000 '')
2014 '')
2001 changed.add(gp.path)
2015 changed.add(gp.path)
2002 if gp.op == 'RENAME':
2016 if gp.op == 'RENAME':
2003 changed.add(gp.oldpath)
2017 changed.add(gp.oldpath)
2004 elif state not in ('hunk', 'git'):
2018 elif state not in ('hunk', 'git'):
2005 raise util.Abort(_('unsupported parser state: %s') % state)
2019 raise util.Abort(_('unsupported parser state: %s') % state)
2006 return changed
2020 return changed
2007 finally:
2021 finally:
2008 fp.close()
2022 fp.close()
2009
2023
2010 class GitDiffRequired(Exception):
2024 class GitDiffRequired(Exception):
2011 pass
2025 pass
2012
2026
2013 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2027 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2014 '''return diffopts with all features supported and parsed'''
2028 '''return diffopts with all features supported and parsed'''
2015 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2029 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2016 git=True, whitespace=True, formatchanging=True)
2030 git=True, whitespace=True, formatchanging=True)
2017
2031
2018 diffopts = diffallopts
2032 diffopts = diffallopts
2019
2033
2020 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2034 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2021 whitespace=False, formatchanging=False):
2035 whitespace=False, formatchanging=False):
2022 '''return diffopts with only opted-in features parsed
2036 '''return diffopts with only opted-in features parsed
2023
2037
2024 Features:
2038 Features:
2025 - git: git-style diffs
2039 - git: git-style diffs
2026 - whitespace: whitespace options like ignoreblanklines and ignorews
2040 - whitespace: whitespace options like ignoreblanklines and ignorews
2027 - formatchanging: options that will likely break or cause correctness issues
2041 - formatchanging: options that will likely break or cause correctness issues
2028 with most diff parsers
2042 with most diff parsers
2029 '''
2043 '''
2030 def get(key, name=None, getter=ui.configbool, forceplain=None):
2044 def get(key, name=None, getter=ui.configbool, forceplain=None):
2031 if opts:
2045 if opts:
2032 v = opts.get(key)
2046 v = opts.get(key)
2033 if v:
2047 if v:
2034 return v
2048 return v
2035 if forceplain is not None and ui.plain():
2049 if forceplain is not None and ui.plain():
2036 return forceplain
2050 return forceplain
2037 return getter(section, name or key, None, untrusted=untrusted)
2051 return getter(section, name or key, None, untrusted=untrusted)
2038
2052
2039 # core options, expected to be understood by every diff parser
2053 # core options, expected to be understood by every diff parser
2040 buildopts = {
2054 buildopts = {
2041 'nodates': get('nodates'),
2055 'nodates': get('nodates'),
2042 'showfunc': get('show_function', 'showfunc'),
2056 'showfunc': get('show_function', 'showfunc'),
2043 'context': get('unified', getter=ui.config),
2057 'context': get('unified', getter=ui.config),
2044 }
2058 }
2045
2059
2046 if git:
2060 if git:
2047 buildopts['git'] = get('git')
2061 buildopts['git'] = get('git')
2048 if whitespace:
2062 if whitespace:
2049 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2063 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2050 buildopts['ignorewsamount'] = get('ignore_space_change',
2064 buildopts['ignorewsamount'] = get('ignore_space_change',
2051 'ignorewsamount')
2065 'ignorewsamount')
2052 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2066 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2053 'ignoreblanklines')
2067 'ignoreblanklines')
2054 if formatchanging:
2068 if formatchanging:
2055 buildopts['text'] = opts and opts.get('text')
2069 buildopts['text'] = opts and opts.get('text')
2056 buildopts['nobinary'] = get('nobinary')
2070 buildopts['nobinary'] = get('nobinary')
2057 buildopts['noprefix'] = get('noprefix', forceplain=False)
2071 buildopts['noprefix'] = get('noprefix', forceplain=False)
2058
2072
2059 return mdiff.diffopts(**buildopts)
2073 return mdiff.diffopts(**buildopts)
2060
2074
2061 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2075 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2062 losedatafn=None, prefix='', relroot=''):
2076 losedatafn=None, prefix='', relroot=''):
2063 '''yields diff of changes to files between two nodes, or node and
2077 '''yields diff of changes to files between two nodes, or node and
2064 working directory.
2078 working directory.
2065
2079
2066 if node1 is None, use first dirstate parent instead.
2080 if node1 is None, use first dirstate parent instead.
2067 if node2 is None, compare node1 with working directory.
2081 if node2 is None, compare node1 with working directory.
2068
2082
2069 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2083 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2070 every time some change cannot be represented with the current
2084 every time some change cannot be represented with the current
2071 patch format. Return False to upgrade to git patch format, True to
2085 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
2086 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
2087 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
2088 to None, patches will always be upgraded to git format when
2075 necessary.
2089 necessary.
2076
2090
2077 prefix is a filename prefix that is prepended to all filenames on
2091 prefix is a filename prefix that is prepended to all filenames on
2078 display (used for subrepos).
2092 display (used for subrepos).
2079
2093
2080 relroot, if not empty, must be normalized with a trailing /. Any match
2094 relroot, if not empty, must be normalized with a trailing /. Any match
2081 patterns that fall outside it will be ignored.'''
2095 patterns that fall outside it will be ignored.'''
2082
2096
2083 if opts is None:
2097 if opts is None:
2084 opts = mdiff.defaultopts
2098 opts = mdiff.defaultopts
2085
2099
2086 if not node1 and not node2:
2100 if not node1 and not node2:
2087 node1 = repo.dirstate.p1()
2101 node1 = repo.dirstate.p1()
2088
2102
2089 def lrugetfilectx():
2103 def lrugetfilectx():
2090 cache = {}
2104 cache = {}
2091 order = util.deque()
2105 order = util.deque()
2092 def getfilectx(f, ctx):
2106 def getfilectx(f, ctx):
2093 fctx = ctx.filectx(f, filelog=cache.get(f))
2107 fctx = ctx.filectx(f, filelog=cache.get(f))
2094 if f not in cache:
2108 if f not in cache:
2095 if len(cache) > 20:
2109 if len(cache) > 20:
2096 del cache[order.popleft()]
2110 del cache[order.popleft()]
2097 cache[f] = fctx.filelog()
2111 cache[f] = fctx.filelog()
2098 else:
2112 else:
2099 order.remove(f)
2113 order.remove(f)
2100 order.append(f)
2114 order.append(f)
2101 return fctx
2115 return fctx
2102 return getfilectx
2116 return getfilectx
2103 getfilectx = lrugetfilectx()
2117 getfilectx = lrugetfilectx()
2104
2118
2105 ctx1 = repo[node1]
2119 ctx1 = repo[node1]
2106 ctx2 = repo[node2]
2120 ctx2 = repo[node2]
2107
2121
2108 relfiltered = False
2122 relfiltered = False
2109 if relroot != '' and match.always():
2123 if relroot != '' and match.always():
2110 # as a special case, create a new matcher with just the relroot
2124 # as a special case, create a new matcher with just the relroot
2111 pats = [relroot]
2125 pats = [relroot]
2112 match = scmutil.match(ctx2, pats, default='path')
2126 match = scmutil.match(ctx2, pats, default='path')
2113 relfiltered = True
2127 relfiltered = True
2114
2128
2115 if not changes:
2129 if not changes:
2116 changes = repo.status(ctx1, ctx2, match=match)
2130 changes = repo.status(ctx1, ctx2, match=match)
2117 modified, added, removed = changes[:3]
2131 modified, added, removed = changes[:3]
2118
2132
2119 if not modified and not added and not removed:
2133 if not modified and not added and not removed:
2120 return []
2134 return []
2121
2135
2122 if repo.ui.debugflag:
2136 if repo.ui.debugflag:
2123 hexfunc = hex
2137 hexfunc = hex
2124 else:
2138 else:
2125 hexfunc = short
2139 hexfunc = short
2126 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2140 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2127
2141
2128 copy = {}
2142 copy = {}
2129 if opts.git or opts.upgrade:
2143 if opts.git or opts.upgrade:
2130 copy = copies.pathcopies(ctx1, ctx2, match=match)
2144 copy = copies.pathcopies(ctx1, ctx2, match=match)
2131
2145
2132 if relroot is not None:
2146 if relroot is not None:
2133 if not relfiltered:
2147 if not relfiltered:
2134 # XXX this would ideally be done in the matcher, but that is
2148 # 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
2149 # generally meant to 'or' patterns, not 'and' them. In this case we
2136 # need to 'and' all the patterns from the matcher with relroot.
2150 # need to 'and' all the patterns from the matcher with relroot.
2137 def filterrel(l):
2151 def filterrel(l):
2138 return [f for f in l if f.startswith(relroot)]
2152 return [f for f in l if f.startswith(relroot)]
2139 modified = filterrel(modified)
2153 modified = filterrel(modified)
2140 added = filterrel(added)
2154 added = filterrel(added)
2141 removed = filterrel(removed)
2155 removed = filterrel(removed)
2142 relfiltered = True
2156 relfiltered = True
2143 # filter out copies where either side isn't inside the relative root
2157 # filter out copies where either side isn't inside the relative root
2144 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2158 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2145 if dst.startswith(relroot)
2159 if dst.startswith(relroot)
2146 and src.startswith(relroot)))
2160 and src.startswith(relroot)))
2147
2161
2148 def difffn(opts, losedata):
2162 def difffn(opts, losedata):
2149 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2163 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2150 copy, getfilectx, opts, losedata, prefix, relroot)
2164 copy, getfilectx, opts, losedata, prefix, relroot)
2151 if opts.upgrade and not opts.git:
2165 if opts.upgrade and not opts.git:
2152 try:
2166 try:
2153 def losedata(fn):
2167 def losedata(fn):
2154 if not losedatafn or not losedatafn(fn=fn):
2168 if not losedatafn or not losedatafn(fn=fn):
2155 raise GitDiffRequired
2169 raise GitDiffRequired
2156 # Buffer the whole output until we are sure it can be generated
2170 # Buffer the whole output until we are sure it can be generated
2157 return list(difffn(opts.copy(git=False), losedata))
2171 return list(difffn(opts.copy(git=False), losedata))
2158 except GitDiffRequired:
2172 except GitDiffRequired:
2159 return difffn(opts.copy(git=True), None)
2173 return difffn(opts.copy(git=True), None)
2160 else:
2174 else:
2161 return difffn(opts, None)
2175 return difffn(opts, None)
2162
2176
2163 def difflabel(func, *args, **kw):
2177 def difflabel(func, *args, **kw):
2164 '''yields 2-tuples of (output, label) based on the output of func()'''
2178 '''yields 2-tuples of (output, label) based on the output of func()'''
2165 headprefixes = [('diff', 'diff.diffline'),
2179 headprefixes = [('diff', 'diff.diffline'),
2166 ('copy', 'diff.extended'),
2180 ('copy', 'diff.extended'),
2167 ('rename', 'diff.extended'),
2181 ('rename', 'diff.extended'),
2168 ('old', 'diff.extended'),
2182 ('old', 'diff.extended'),
2169 ('new', 'diff.extended'),
2183 ('new', 'diff.extended'),
2170 ('deleted', 'diff.extended'),
2184 ('deleted', 'diff.extended'),
2171 ('---', 'diff.file_a'),
2185 ('---', 'diff.file_a'),
2172 ('+++', 'diff.file_b')]
2186 ('+++', 'diff.file_b')]
2173 textprefixes = [('@', 'diff.hunk'),
2187 textprefixes = [('@', 'diff.hunk'),
2174 ('-', 'diff.deleted'),
2188 ('-', 'diff.deleted'),
2175 ('+', 'diff.inserted')]
2189 ('+', 'diff.inserted')]
2176 head = False
2190 head = False
2177 for chunk in func(*args, **kw):
2191 for chunk in func(*args, **kw):
2178 lines = chunk.split('\n')
2192 lines = chunk.split('\n')
2179 for i, line in enumerate(lines):
2193 for i, line in enumerate(lines):
2180 if i != 0:
2194 if i != 0:
2181 yield ('\n', '')
2195 yield ('\n', '')
2182 if head:
2196 if head:
2183 if line.startswith('@'):
2197 if line.startswith('@'):
2184 head = False
2198 head = False
2185 else:
2199 else:
2186 if line and line[0] not in ' +-@\\':
2200 if line and line[0] not in ' +-@\\':
2187 head = True
2201 head = True
2188 stripline = line
2202 stripline = line
2189 diffline = False
2203 diffline = False
2190 if not head and line and line[0] in '+-':
2204 if not head and line and line[0] in '+-':
2191 # highlight tabs and trailing whitespace, but only in
2205 # highlight tabs and trailing whitespace, but only in
2192 # changed lines
2206 # changed lines
2193 stripline = line.rstrip()
2207 stripline = line.rstrip()
2194 diffline = True
2208 diffline = True
2195
2209
2196 prefixes = textprefixes
2210 prefixes = textprefixes
2197 if head:
2211 if head:
2198 prefixes = headprefixes
2212 prefixes = headprefixes
2199 for prefix, label in prefixes:
2213 for prefix, label in prefixes:
2200 if stripline.startswith(prefix):
2214 if stripline.startswith(prefix):
2201 if diffline:
2215 if diffline:
2202 for token in tabsplitter.findall(stripline):
2216 for token in tabsplitter.findall(stripline):
2203 if '\t' == token[0]:
2217 if '\t' == token[0]:
2204 yield (token, 'diff.tab')
2218 yield (token, 'diff.tab')
2205 else:
2219 else:
2206 yield (token, label)
2220 yield (token, label)
2207 else:
2221 else:
2208 yield (stripline, label)
2222 yield (stripline, label)
2209 break
2223 break
2210 else:
2224 else:
2211 yield (line, '')
2225 yield (line, '')
2212 if line != stripline:
2226 if line != stripline:
2213 yield (line[len(stripline):], 'diff.trailingwhitespace')
2227 yield (line[len(stripline):], 'diff.trailingwhitespace')
2214
2228
2215 def diffui(*args, **kw):
2229 def diffui(*args, **kw):
2216 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2230 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2217 return difflabel(diff, *args, **kw)
2231 return difflabel(diff, *args, **kw)
2218
2232
2219 def _filepairs(ctx1, modified, added, removed, copy, opts):
2233 def _filepairs(ctx1, modified, added, removed, copy, opts):
2220 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2234 '''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,
2235 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'
2236 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).'''
2237 or 'rename' (the latter two only if opts.git is set).'''
2224 gone = set()
2238 gone = set()
2225
2239
2226 copyto = dict([(v, k) for k, v in copy.items()])
2240 copyto = dict([(v, k) for k, v in copy.items()])
2227
2241
2228 addedset, removedset = set(added), set(removed)
2242 addedset, removedset = set(added), set(removed)
2229 # Fix up added, since merged-in additions appear as
2243 # Fix up added, since merged-in additions appear as
2230 # modifications during merges
2244 # modifications during merges
2231 for f in modified:
2245 for f in modified:
2232 if f not in ctx1:
2246 if f not in ctx1:
2233 addedset.add(f)
2247 addedset.add(f)
2234
2248
2235 for f in sorted(modified + added + removed):
2249 for f in sorted(modified + added + removed):
2236 copyop = None
2250 copyop = None
2237 f1, f2 = f, f
2251 f1, f2 = f, f
2238 if f in addedset:
2252 if f in addedset:
2239 f1 = None
2253 f1 = None
2240 if f in copy:
2254 if f in copy:
2241 if opts.git:
2255 if opts.git:
2242 f1 = copy[f]
2256 f1 = copy[f]
2243 if f1 in removedset and f1 not in gone:
2257 if f1 in removedset and f1 not in gone:
2244 copyop = 'rename'
2258 copyop = 'rename'
2245 gone.add(f1)
2259 gone.add(f1)
2246 else:
2260 else:
2247 copyop = 'copy'
2261 copyop = 'copy'
2248 elif f in removedset:
2262 elif f in removedset:
2249 f2 = None
2263 f2 = None
2250 if opts.git:
2264 if opts.git:
2251 # have we already reported a copy above?
2265 # have we already reported a copy above?
2252 if (f in copyto and copyto[f] in addedset
2266 if (f in copyto and copyto[f] in addedset
2253 and copy[copyto[f]] == f):
2267 and copy[copyto[f]] == f):
2254 continue
2268 continue
2255 yield f1, f2, copyop
2269 yield f1, f2, copyop
2256
2270
2257 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2271 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2258 copy, getfilectx, opts, losedatafn, prefix, relroot):
2272 copy, getfilectx, opts, losedatafn, prefix, relroot):
2259 '''given input data, generate a diff and yield it in blocks
2273 '''given input data, generate a diff and yield it in blocks
2260
2274
2261 If generating a diff would lose data like flags or binary data and
2275 If generating a diff would lose data like flags or binary data and
2262 losedatafn is not None, it will be called.
2276 losedatafn is not None, it will be called.
2263
2277
2264 relroot is removed and prefix is added to every path in the diff output.
2278 relroot is removed and prefix is added to every path in the diff output.
2265
2279
2266 If relroot is not empty, this function expects every path in modified,
2280 If relroot is not empty, this function expects every path in modified,
2267 added, removed and copy to start with it.'''
2281 added, removed and copy to start with it.'''
2268
2282
2269 def gitindex(text):
2283 def gitindex(text):
2270 if not text:
2284 if not text:
2271 text = ""
2285 text = ""
2272 l = len(text)
2286 l = len(text)
2273 s = util.sha1('blob %d\0' % l)
2287 s = util.sha1('blob %d\0' % l)
2274 s.update(text)
2288 s.update(text)
2275 return s.hexdigest()
2289 return s.hexdigest()
2276
2290
2277 if opts.noprefix:
2291 if opts.noprefix:
2278 aprefix = bprefix = ''
2292 aprefix = bprefix = ''
2279 else:
2293 else:
2280 aprefix = 'a/'
2294 aprefix = 'a/'
2281 bprefix = 'b/'
2295 bprefix = 'b/'
2282
2296
2283 def diffline(f, revs):
2297 def diffline(f, revs):
2284 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2298 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2285 return 'diff %s %s' % (revinfo, f)
2299 return 'diff %s %s' % (revinfo, f)
2286
2300
2287 date1 = util.datestr(ctx1.date())
2301 date1 = util.datestr(ctx1.date())
2288 date2 = util.datestr(ctx2.date())
2302 date2 = util.datestr(ctx2.date())
2289
2303
2290 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2304 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2291
2305
2292 if relroot != '' and (repo.ui.configbool('devel', 'all')
2306 if relroot != '' and (repo.ui.configbool('devel', 'all')
2293 or repo.ui.configbool('devel', 'check-relroot')):
2307 or repo.ui.configbool('devel', 'check-relroot')):
2294 for f in modified + added + removed + copy.keys() + copy.values():
2308 for f in modified + added + removed + copy.keys() + copy.values():
2295 if f is not None and not f.startswith(relroot):
2309 if f is not None and not f.startswith(relroot):
2296 raise AssertionError(
2310 raise AssertionError(
2297 "file %s doesn't start with relroot %s" % (f, relroot))
2311 "file %s doesn't start with relroot %s" % (f, relroot))
2298
2312
2299 for f1, f2, copyop in _filepairs(
2313 for f1, f2, copyop in _filepairs(
2300 ctx1, modified, added, removed, copy, opts):
2314 ctx1, modified, added, removed, copy, opts):
2301 content1 = None
2315 content1 = None
2302 content2 = None
2316 content2 = None
2303 flag1 = None
2317 flag1 = None
2304 flag2 = None
2318 flag2 = None
2305 if f1:
2319 if f1:
2306 content1 = getfilectx(f1, ctx1).data()
2320 content1 = getfilectx(f1, ctx1).data()
2307 if opts.git or losedatafn:
2321 if opts.git or losedatafn:
2308 flag1 = ctx1.flags(f1)
2322 flag1 = ctx1.flags(f1)
2309 if f2:
2323 if f2:
2310 content2 = getfilectx(f2, ctx2).data()
2324 content2 = getfilectx(f2, ctx2).data()
2311 if opts.git or losedatafn:
2325 if opts.git or losedatafn:
2312 flag2 = ctx2.flags(f2)
2326 flag2 = ctx2.flags(f2)
2313 binary = False
2327 binary = False
2314 if opts.git or losedatafn:
2328 if opts.git or losedatafn:
2315 binary = util.binary(content1) or util.binary(content2)
2329 binary = util.binary(content1) or util.binary(content2)
2316
2330
2317 if losedatafn and not opts.git:
2331 if losedatafn and not opts.git:
2318 if (binary or
2332 if (binary or
2319 # copy/rename
2333 # copy/rename
2320 f2 in copy or
2334 f2 in copy or
2321 # empty file creation
2335 # empty file creation
2322 (not f1 and not content2) or
2336 (not f1 and not content2) or
2323 # empty file deletion
2337 # empty file deletion
2324 (not content1 and not f2) or
2338 (not content1 and not f2) or
2325 # create with flags
2339 # create with flags
2326 (not f1 and flag2) or
2340 (not f1 and flag2) or
2327 # change flags
2341 # change flags
2328 (f1 and f2 and flag1 != flag2)):
2342 (f1 and f2 and flag1 != flag2)):
2329 losedatafn(f2 or f1)
2343 losedatafn(f2 or f1)
2330
2344
2331 path1 = f1 or f2
2345 path1 = f1 or f2
2332 path2 = f2 or f1
2346 path2 = f2 or f1
2333 path1 = posixpath.join(prefix, path1[len(relroot):])
2347 path1 = posixpath.join(prefix, path1[len(relroot):])
2334 path2 = posixpath.join(prefix, path2[len(relroot):])
2348 path2 = posixpath.join(prefix, path2[len(relroot):])
2335 header = []
2349 header = []
2336 if opts.git:
2350 if opts.git:
2337 header.append('diff --git %s%s %s%s' %
2351 header.append('diff --git %s%s %s%s' %
2338 (aprefix, path1, bprefix, path2))
2352 (aprefix, path1, bprefix, path2))
2339 if not f1: # added
2353 if not f1: # added
2340 header.append('new file mode %s' % gitmode[flag2])
2354 header.append('new file mode %s' % gitmode[flag2])
2341 elif not f2: # removed
2355 elif not f2: # removed
2342 header.append('deleted file mode %s' % gitmode[flag1])
2356 header.append('deleted file mode %s' % gitmode[flag1])
2343 else: # modified/copied/renamed
2357 else: # modified/copied/renamed
2344 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2358 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2345 if mode1 != mode2:
2359 if mode1 != mode2:
2346 header.append('old mode %s' % mode1)
2360 header.append('old mode %s' % mode1)
2347 header.append('new mode %s' % mode2)
2361 header.append('new mode %s' % mode2)
2348 if copyop is not None:
2362 if copyop is not None:
2349 header.append('%s from %s' % (copyop, path1))
2363 header.append('%s from %s' % (copyop, path1))
2350 header.append('%s to %s' % (copyop, path2))
2364 header.append('%s to %s' % (copyop, path2))
2351 elif revs and not repo.ui.quiet:
2365 elif revs and not repo.ui.quiet:
2352 header.append(diffline(path1, revs))
2366 header.append(diffline(path1, revs))
2353
2367
2354 if binary and opts.git and not opts.nobinary:
2368 if binary and opts.git and not opts.nobinary:
2355 text = mdiff.b85diff(content1, content2)
2369 text = mdiff.b85diff(content1, content2)
2356 if text:
2370 if text:
2357 header.append('index %s..%s' %
2371 header.append('index %s..%s' %
2358 (gitindex(content1), gitindex(content2)))
2372 (gitindex(content1), gitindex(content2)))
2359 else:
2373 else:
2360 text = mdiff.unidiff(content1, date1,
2374 text = mdiff.unidiff(content1, date1,
2361 content2, date2,
2375 content2, date2,
2362 path1, path2, opts=opts)
2376 path1, path2, opts=opts)
2363 if header and (text or len(header) > 1):
2377 if header and (text or len(header) > 1):
2364 yield '\n'.join(header) + '\n'
2378 yield '\n'.join(header) + '\n'
2365 if text:
2379 if text:
2366 yield text
2380 yield text
2367
2381
2368 def diffstatsum(stats):
2382 def diffstatsum(stats):
2369 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2383 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2370 for f, a, r, b in stats:
2384 for f, a, r, b in stats:
2371 maxfile = max(maxfile, encoding.colwidth(f))
2385 maxfile = max(maxfile, encoding.colwidth(f))
2372 maxtotal = max(maxtotal, a + r)
2386 maxtotal = max(maxtotal, a + r)
2373 addtotal += a
2387 addtotal += a
2374 removetotal += r
2388 removetotal += r
2375 binary = binary or b
2389 binary = binary or b
2376
2390
2377 return maxfile, maxtotal, addtotal, removetotal, binary
2391 return maxfile, maxtotal, addtotal, removetotal, binary
2378
2392
2379 def diffstatdata(lines):
2393 def diffstatdata(lines):
2380 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2394 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2381
2395
2382 results = []
2396 results = []
2383 filename, adds, removes, isbinary = None, 0, 0, False
2397 filename, adds, removes, isbinary = None, 0, 0, False
2384
2398
2385 def addresult():
2399 def addresult():
2386 if filename:
2400 if filename:
2387 results.append((filename, adds, removes, isbinary))
2401 results.append((filename, adds, removes, isbinary))
2388
2402
2389 for line in lines:
2403 for line in lines:
2390 if line.startswith('diff'):
2404 if line.startswith('diff'):
2391 addresult()
2405 addresult()
2392 # set numbers to 0 anyway when starting new file
2406 # set numbers to 0 anyway when starting new file
2393 adds, removes, isbinary = 0, 0, False
2407 adds, removes, isbinary = 0, 0, False
2394 if line.startswith('diff --git a/'):
2408 if line.startswith('diff --git a/'):
2395 filename = gitre.search(line).group(2)
2409 filename = gitre.search(line).group(2)
2396 elif line.startswith('diff -r'):
2410 elif line.startswith('diff -r'):
2397 # format: "diff -r ... -r ... filename"
2411 # format: "diff -r ... -r ... filename"
2398 filename = diffre.search(line).group(1)
2412 filename = diffre.search(line).group(1)
2399 elif line.startswith('+') and not line.startswith('+++ '):
2413 elif line.startswith('+') and not line.startswith('+++ '):
2400 adds += 1
2414 adds += 1
2401 elif line.startswith('-') and not line.startswith('--- '):
2415 elif line.startswith('-') and not line.startswith('--- '):
2402 removes += 1
2416 removes += 1
2403 elif (line.startswith('GIT binary patch') or
2417 elif (line.startswith('GIT binary patch') or
2404 line.startswith('Binary file')):
2418 line.startswith('Binary file')):
2405 isbinary = True
2419 isbinary = True
2406 addresult()
2420 addresult()
2407 return results
2421 return results
2408
2422
2409 def diffstat(lines, width=80, git=False):
2423 def diffstat(lines, width=80, git=False):
2410 output = []
2424 output = []
2411 stats = diffstatdata(lines)
2425 stats = diffstatdata(lines)
2412 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2426 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2413
2427
2414 countwidth = len(str(maxtotal))
2428 countwidth = len(str(maxtotal))
2415 if hasbinary and countwidth < 3:
2429 if hasbinary and countwidth < 3:
2416 countwidth = 3
2430 countwidth = 3
2417 graphwidth = width - countwidth - maxname - 6
2431 graphwidth = width - countwidth - maxname - 6
2418 if graphwidth < 10:
2432 if graphwidth < 10:
2419 graphwidth = 10
2433 graphwidth = 10
2420
2434
2421 def scale(i):
2435 def scale(i):
2422 if maxtotal <= graphwidth:
2436 if maxtotal <= graphwidth:
2423 return i
2437 return i
2424 # If diffstat runs out of room it doesn't print anything,
2438 # If diffstat runs out of room it doesn't print anything,
2425 # which isn't very useful, so always print at least one + or -
2439 # which isn't very useful, so always print at least one + or -
2426 # if there were at least some changes.
2440 # if there were at least some changes.
2427 return max(i * graphwidth // maxtotal, int(bool(i)))
2441 return max(i * graphwidth // maxtotal, int(bool(i)))
2428
2442
2429 for filename, adds, removes, isbinary in stats:
2443 for filename, adds, removes, isbinary in stats:
2430 if isbinary:
2444 if isbinary:
2431 count = 'Bin'
2445 count = 'Bin'
2432 else:
2446 else:
2433 count = adds + removes
2447 count = adds + removes
2434 pluses = '+' * scale(adds)
2448 pluses = '+' * scale(adds)
2435 minuses = '-' * scale(removes)
2449 minuses = '-' * scale(removes)
2436 output.append(' %s%s | %*s %s%s\n' %
2450 output.append(' %s%s | %*s %s%s\n' %
2437 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2451 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2438 countwidth, count, pluses, minuses))
2452 countwidth, count, pluses, minuses))
2439
2453
2440 if stats:
2454 if stats:
2441 output.append(_(' %d files changed, %d insertions(+), '
2455 output.append(_(' %d files changed, %d insertions(+), '
2442 '%d deletions(-)\n')
2456 '%d deletions(-)\n')
2443 % (len(stats), totaladds, totalremoves))
2457 % (len(stats), totaladds, totalremoves))
2444
2458
2445 return ''.join(output)
2459 return ''.join(output)
2446
2460
2447 def diffstatui(*args, **kw):
2461 def diffstatui(*args, **kw):
2448 '''like diffstat(), but yields 2-tuples of (output, label) for
2462 '''like diffstat(), but yields 2-tuples of (output, label) for
2449 ui.write()
2463 ui.write()
2450 '''
2464 '''
2451
2465
2452 for line in diffstat(*args, **kw).splitlines():
2466 for line in diffstat(*args, **kw).splitlines():
2453 if line and line[-1] in '+-':
2467 if line and line[-1] in '+-':
2454 name, graph = line.rsplit(' ', 1)
2468 name, graph = line.rsplit(' ', 1)
2455 yield (name + ' ', '')
2469 yield (name + ' ', '')
2456 m = re.search(r'\++', graph)
2470 m = re.search(r'\++', graph)
2457 if m:
2471 if m:
2458 yield (m.group(0), 'diffstat.inserted')
2472 yield (m.group(0), 'diffstat.inserted')
2459 m = re.search(r'-+', graph)
2473 m = re.search(r'-+', graph)
2460 if m:
2474 if m:
2461 yield (m.group(0), 'diffstat.deleted')
2475 yield (m.group(0), 'diffstat.deleted')
2462 else:
2476 else:
2463 yield (line, '')
2477 yield (line, '')
2464 yield ('\n', '')
2478 yield ('\n', '')
@@ -1,1409 +1,1465 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
232 > EOF
233 > EOF
233 diff --git a/plain b/plain
234 diff --git a/plain b/plain
234 new file mode 100644
235 new file mode 100644
235 examine changes to 'plain'? [Ynesfdaq?] y
236 examine changes to 'plain'? [Ynesfdaq?] y
236
237
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
237 $ hg tip -p
251 $ hg tip -p
238 changeset: 7:11fb457c1be4
252 changeset: 7:11fb457c1be4
239 tag: tip
253 tag: tip
240 user: test
254 user: test
241 date: Thu Jan 01 00:00:07 1970 +0000
255 date: Thu Jan 01 00:00:07 1970 +0000
242 summary: plain
256 summary: plain
243
257
244 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
258 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
245 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
259 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
246 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
260 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
247 @@ -0,0 +1,10 @@
261 @@ -0,0 +1,10 @@
248 +1
262 +1
249 +2
263 +2
250 +3
264 +3
251 +4
265 +4
252 +5
266 +5
253 +6
267 +6
254 +7
268 +7
255 +8
269 +8
256 +9
270 +9
257 +10
271 +10
258
272
259 Modify end of plain file with username unset
273 Modify end of plain file with username unset
260
274
261 $ echo 11 >> plain
275 $ echo 11 >> plain
262 $ unset HGUSER
276 $ unset HGUSER
263 $ hg commit -i --config ui.username= -d '8 0' -m end plain
277 $ hg commit -i --config ui.username= -d '8 0' -m end plain
264 abort: no username supplied
278 abort: no username supplied
265 (use "hg config --edit" to set your username)
279 (use "hg config --edit" to set your username)
266 [255]
280 [255]
267
281
268
282
269 Modify end of plain file, also test that diffopts are accounted for
283 Modify end of plain file, also test that diffopts are accounted for
270
284
271 $ HGUSER="test"
285 $ HGUSER="test"
272 $ export HGUSER
286 $ export HGUSER
273 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
287 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
274 > y
288 > y
275 > y
289 > y
276 > EOF
290 > EOF
277 diff --git a/plain b/plain
291 diff --git a/plain b/plain
278 1 hunks, 1 lines changed
292 1 hunks, 1 lines changed
279 examine changes to 'plain'? [Ynesfdaq?] y
293 examine changes to 'plain'? [Ynesfdaq?] y
280
294
281 @@ -8,3 +8,4 @@ 7
295 @@ -8,3 +8,4 @@ 7
282 8
296 8
283 9
297 9
284 10
298 10
285 +11
299 +11
286 record this change to 'plain'? [Ynesfdaq?] y
300 record this change to 'plain'? [Ynesfdaq?] y
287
301
288
302
289 Modify end of plain file, no EOL
303 Modify end of plain file, no EOL
290
304
291 $ hg tip --template '{node}' >> plain
305 $ hg tip --template '{node}' >> plain
292 $ hg commit -i -d '9 0' -m noeol plain <<EOF
306 $ hg commit -i -d '9 0' -m noeol plain <<EOF
293 > y
307 > y
294 > y
308 > y
295 > EOF
309 > EOF
296 diff --git a/plain b/plain
310 diff --git a/plain b/plain
297 1 hunks, 1 lines changed
311 1 hunks, 1 lines changed
298 examine changes to 'plain'? [Ynesfdaq?] y
312 examine changes to 'plain'? [Ynesfdaq?] y
299
313
300 @@ -9,3 +9,4 @@
314 @@ -9,3 +9,4 @@
301 9
315 9
302 10
316 10
303 11
317 11
304 +7264f99c5f5ff3261504828afa4fb4d406c3af54
318 +7264f99c5f5ff3261504828afa4fb4d406c3af54
305 \ No newline at end of file
319 \ No newline at end of file
306 record this change to 'plain'? [Ynesfdaq?] y
320 record this change to 'plain'? [Ynesfdaq?] y
307
321
308
322
309 Modify end of plain file, add EOL
323 Modify end of plain file, add EOL
310
324
311 $ echo >> plain
325 $ echo >> plain
312 $ echo 1 > plain2
326 $ echo 1 > plain2
313 $ hg add plain2
327 $ hg add plain2
314 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
328 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
315 > y
329 > y
316 > y
330 > y
317 > y
331 > y
332 > y
318 > EOF
333 > EOF
319 diff --git a/plain b/plain
334 diff --git a/plain b/plain
320 1 hunks, 1 lines changed
335 1 hunks, 1 lines changed
321 examine changes to 'plain'? [Ynesfdaq?] y
336 examine changes to 'plain'? [Ynesfdaq?] y
322
337
323 @@ -9,4 +9,4 @@
338 @@ -9,4 +9,4 @@
324 9
339 9
325 10
340 10
326 11
341 11
327 -7264f99c5f5ff3261504828afa4fb4d406c3af54
342 -7264f99c5f5ff3261504828afa4fb4d406c3af54
328 \ No newline at end of file
343 \ No newline at end of file
329 +7264f99c5f5ff3261504828afa4fb4d406c3af54
344 +7264f99c5f5ff3261504828afa4fb4d406c3af54
330 record change 1/2 to 'plain'? [Ynesfdaq?] y
345 record change 1/2 to 'plain'? [Ynesfdaq?] y
331
346
332 diff --git a/plain2 b/plain2
347 diff --git a/plain2 b/plain2
333 new file mode 100644
348 new file mode 100644
334 examine changes to 'plain2'? [Ynesfdaq?] y
349 examine changes to 'plain2'? [Ynesfdaq?] y
335
350
351 @@ -0,0 +1,1 @@
352 +1
353 record change 2/2 to 'plain2'? [Ynesfdaq?] y
354
336 Modify beginning, trim end, record both, add another file to test
355 Modify beginning, trim end, record both, add another file to test
337 changes numbering
356 changes numbering
338
357
339 $ rm plain
358 $ rm plain
340 $ for i in 2 2 3 4 5 6 7 8 9 10; do
359 $ for i in 2 2 3 4 5 6 7 8 9 10; do
341 > echo $i >> plain
360 > echo $i >> plain
342 > done
361 > done
343 $ echo 2 >> plain2
362 $ echo 2 >> plain2
344
363
345 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
364 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
346 > y
365 > y
347 > y
366 > y
348 > y
367 > y
349 > y
368 > y
350 > y
369 > y
351 > EOF
370 > EOF
352 diff --git a/plain b/plain
371 diff --git a/plain b/plain
353 2 hunks, 3 lines changed
372 2 hunks, 3 lines changed
354 examine changes to 'plain'? [Ynesfdaq?] y
373 examine changes to 'plain'? [Ynesfdaq?] y
355
374
356 @@ -1,4 +1,4 @@
375 @@ -1,4 +1,4 @@
357 -1
376 -1
358 +2
377 +2
359 2
378 2
360 3
379 3
361 4
380 4
362 record change 1/3 to 'plain'? [Ynesfdaq?] y
381 record change 1/3 to 'plain'? [Ynesfdaq?] y
363
382
364 @@ -8,5 +8,3 @@
383 @@ -8,5 +8,3 @@
365 8
384 8
366 9
385 9
367 10
386 10
368 -11
387 -11
369 -7264f99c5f5ff3261504828afa4fb4d406c3af54
388 -7264f99c5f5ff3261504828afa4fb4d406c3af54
370 record change 2/3 to 'plain'? [Ynesfdaq?] y
389 record change 2/3 to 'plain'? [Ynesfdaq?] y
371
390
372 diff --git a/plain2 b/plain2
391 diff --git a/plain2 b/plain2
373 1 hunks, 1 lines changed
392 1 hunks, 1 lines changed
374 examine changes to 'plain2'? [Ynesfdaq?] y
393 examine changes to 'plain2'? [Ynesfdaq?] y
375
394
376 @@ -1,1 +1,2 @@
395 @@ -1,1 +1,2 @@
377 1
396 1
378 +2
397 +2
379 record change 3/3 to 'plain2'? [Ynesfdaq?] y
398 record change 3/3 to 'plain2'? [Ynesfdaq?] y
380
399
381
400
382 $ hg tip -p
401 $ hg tip -p
383 changeset: 11:21df83db12b8
402 changeset: 11:21df83db12b8
384 tag: tip
403 tag: tip
385 user: test
404 user: test
386 date: Thu Jan 01 00:00:10 1970 +0000
405 date: Thu Jan 01 00:00:10 1970 +0000
387 summary: begin-and-end
406 summary: begin-and-end
388
407
389 diff -r ddb8b281c3ff -r 21df83db12b8 plain
408 diff -r ddb8b281c3ff -r 21df83db12b8 plain
390 --- a/plain Thu Jan 01 00:00:10 1970 +0000
409 --- a/plain Thu Jan 01 00:00:10 1970 +0000
391 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
410 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
392 @@ -1,4 +1,4 @@
411 @@ -1,4 +1,4 @@
393 -1
412 -1
394 +2
413 +2
395 2
414 2
396 3
415 3
397 4
416 4
398 @@ -8,5 +8,3 @@
417 @@ -8,5 +8,3 @@
399 8
418 8
400 9
419 9
401 10
420 10
402 -11
421 -11
403 -7264f99c5f5ff3261504828afa4fb4d406c3af54
422 -7264f99c5f5ff3261504828afa4fb4d406c3af54
404 diff -r ddb8b281c3ff -r 21df83db12b8 plain2
423 diff -r ddb8b281c3ff -r 21df83db12b8 plain2
405 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
424 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
406 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
425 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
407 @@ -1,1 +1,2 @@
426 @@ -1,1 +1,2 @@
408 1
427 1
409 +2
428 +2
410
429
411
430
412 Trim beginning, modify end
431 Trim beginning, modify end
413
432
414 $ rm plain
433 $ rm plain
415 > for i in 4 5 6 7 8 9 10.new; do
434 > for i in 4 5 6 7 8 9 10.new; do
416 > echo $i >> plain
435 > echo $i >> plain
417 > done
436 > done
418
437
419 Record end
438 Record end
420
439
421 $ hg commit -i -d '11 0' -m end-only plain <<EOF
440 $ hg commit -i -d '11 0' -m end-only plain <<EOF
422 > y
441 > y
423 > n
442 > n
424 > y
443 > y
425 > EOF
444 > EOF
426 diff --git a/plain b/plain
445 diff --git a/plain b/plain
427 2 hunks, 4 lines changed
446 2 hunks, 4 lines changed
428 examine changes to 'plain'? [Ynesfdaq?] y
447 examine changes to 'plain'? [Ynesfdaq?] y
429
448
430 @@ -1,9 +1,6 @@
449 @@ -1,9 +1,6 @@
431 -2
450 -2
432 -2
451 -2
433 -3
452 -3
434 4
453 4
435 5
454 5
436 6
455 6
437 7
456 7
438 8
457 8
439 9
458 9
440 record change 1/2 to 'plain'? [Ynesfdaq?] n
459 record change 1/2 to 'plain'? [Ynesfdaq?] n
441
460
442 @@ -4,7 +1,7 @@
461 @@ -4,7 +1,7 @@
443 4
462 4
444 5
463 5
445 6
464 6
446 7
465 7
447 8
466 8
448 9
467 9
449 -10
468 -10
450 +10.new
469 +10.new
451 record change 2/2 to 'plain'? [Ynesfdaq?] y
470 record change 2/2 to 'plain'? [Ynesfdaq?] y
452
471
453
472
454 $ hg tip -p
473 $ hg tip -p
455 changeset: 12:99337501826f
474 changeset: 12:99337501826f
456 tag: tip
475 tag: tip
457 user: test
476 user: test
458 date: Thu Jan 01 00:00:11 1970 +0000
477 date: Thu Jan 01 00:00:11 1970 +0000
459 summary: end-only
478 summary: end-only
460
479
461 diff -r 21df83db12b8 -r 99337501826f plain
480 diff -r 21df83db12b8 -r 99337501826f plain
462 --- a/plain Thu Jan 01 00:00:10 1970 +0000
481 --- a/plain Thu Jan 01 00:00:10 1970 +0000
463 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
482 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
464 @@ -7,4 +7,4 @@
483 @@ -7,4 +7,4 @@
465 7
484 7
466 8
485 8
467 9
486 9
468 -10
487 -10
469 +10.new
488 +10.new
470
489
471
490
472 Record beginning
491 Record beginning
473
492
474 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
493 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
475 > y
494 > y
476 > y
495 > y
477 > EOF
496 > EOF
478 diff --git a/plain b/plain
497 diff --git a/plain b/plain
479 1 hunks, 3 lines changed
498 1 hunks, 3 lines changed
480 examine changes to 'plain'? [Ynesfdaq?] y
499 examine changes to 'plain'? [Ynesfdaq?] y
481
500
482 @@ -1,6 +1,3 @@
501 @@ -1,6 +1,3 @@
483 -2
502 -2
484 -2
503 -2
485 -3
504 -3
486 4
505 4
487 5
506 5
488 6
507 6
489 record this change to 'plain'? [Ynesfdaq?] y
508 record this change to 'plain'? [Ynesfdaq?] y
490
509
491
510
492 $ hg tip -p
511 $ hg tip -p
493 changeset: 13:bbd45465d540
512 changeset: 13:bbd45465d540
494 tag: tip
513 tag: tip
495 user: test
514 user: test
496 date: Thu Jan 01 00:00:12 1970 +0000
515 date: Thu Jan 01 00:00:12 1970 +0000
497 summary: begin-only
516 summary: begin-only
498
517
499 diff -r 99337501826f -r bbd45465d540 plain
518 diff -r 99337501826f -r bbd45465d540 plain
500 --- a/plain Thu Jan 01 00:00:11 1970 +0000
519 --- a/plain Thu Jan 01 00:00:11 1970 +0000
501 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
520 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
502 @@ -1,6 +1,3 @@
521 @@ -1,6 +1,3 @@
503 -2
522 -2
504 -2
523 -2
505 -3
524 -3
506 4
525 4
507 5
526 5
508 6
527 6
509
528
510
529
511 Add to beginning, trim from end
530 Add to beginning, trim from end
512
531
513 $ rm plain
532 $ rm plain
514 $ for i in 1 2 3 4 5 6 7 8 9; do
533 $ for i in 1 2 3 4 5 6 7 8 9; do
515 > echo $i >> plain
534 > echo $i >> plain
516 > done
535 > done
517
536
518 Record end
537 Record end
519
538
520 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
539 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
521 > y
540 > y
522 > n
541 > n
523 > y
542 > y
524 > EOF
543 > EOF
525 diff --git a/plain b/plain
544 diff --git a/plain b/plain
526 2 hunks, 4 lines changed
545 2 hunks, 4 lines changed
527 examine changes to 'plain'? [Ynesfdaq?] y
546 examine changes to 'plain'? [Ynesfdaq?] y
528
547
529 @@ -1,6 +1,9 @@
548 @@ -1,6 +1,9 @@
530 +1
549 +1
531 +2
550 +2
532 +3
551 +3
533 4
552 4
534 5
553 5
535 6
554 6
536 7
555 7
537 8
556 8
538 9
557 9
539 record change 1/2 to 'plain'? [Ynesfdaq?] n
558 record change 1/2 to 'plain'? [Ynesfdaq?] n
540
559
541 @@ -1,7 +4,6 @@
560 @@ -1,7 +4,6 @@
542 4
561 4
543 5
562 5
544 6
563 6
545 7
564 7
546 8
565 8
547 9
566 9
548 -10.new
567 -10.new
549 record change 2/2 to 'plain'? [Ynesfdaq?] y
568 record change 2/2 to 'plain'? [Ynesfdaq?] y
550
569
551
570
552 Add to beginning, middle, end
571 Add to beginning, middle, end
553
572
554 $ rm plain
573 $ rm plain
555 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
574 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
556 > echo $i >> plain
575 > echo $i >> plain
557 > done
576 > done
558
577
559 Record beginning, middle, and test that format-breaking diffopts are ignored
578 Record beginning, middle, and test that format-breaking diffopts are ignored
560
579
561 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
580 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
562 > y
581 > y
563 > y
582 > y
564 > y
583 > y
565 > n
584 > n
566 > EOF
585 > EOF
567 diff --git a/plain b/plain
586 diff --git a/plain b/plain
568 3 hunks, 7 lines changed
587 3 hunks, 7 lines changed
569 examine changes to 'plain'? [Ynesfdaq?] y
588 examine changes to 'plain'? [Ynesfdaq?] y
570
589
571 @@ -1,2 +1,5 @@
590 @@ -1,2 +1,5 @@
572 +1
591 +1
573 +2
592 +2
574 +3
593 +3
575 4
594 4
576 5
595 5
577 record change 1/3 to 'plain'? [Ynesfdaq?] y
596 record change 1/3 to 'plain'? [Ynesfdaq?] y
578
597
579 @@ -1,6 +4,8 @@
598 @@ -1,6 +4,8 @@
580 4
599 4
581 5
600 5
582 +5.new
601 +5.new
583 +5.reallynew
602 +5.reallynew
584 6
603 6
585 7
604 7
586 8
605 8
587 9
606 9
588 record change 2/3 to 'plain'? [Ynesfdaq?] y
607 record change 2/3 to 'plain'? [Ynesfdaq?] y
589
608
590 @@ -3,4 +8,6 @@
609 @@ -3,4 +8,6 @@
591 6
610 6
592 7
611 7
593 8
612 8
594 9
613 9
595 +10
614 +10
596 +11
615 +11
597 record change 3/3 to 'plain'? [Ynesfdaq?] n
616 record change 3/3 to 'plain'? [Ynesfdaq?] n
598
617
599
618
600 $ hg tip -p
619 $ hg tip -p
601 changeset: 15:f34a7937ec33
620 changeset: 15:f34a7937ec33
602 tag: tip
621 tag: tip
603 user: test
622 user: test
604 date: Thu Jan 01 00:00:14 1970 +0000
623 date: Thu Jan 01 00:00:14 1970 +0000
605 summary: middle-only
624 summary: middle-only
606
625
607 diff -r 82c065d0b850 -r f34a7937ec33 plain
626 diff -r 82c065d0b850 -r f34a7937ec33 plain
608 --- a/plain Thu Jan 01 00:00:13 1970 +0000
627 --- a/plain Thu Jan 01 00:00:13 1970 +0000
609 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
628 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
610 @@ -1,5 +1,10 @@
629 @@ -1,5 +1,10 @@
611 +1
630 +1
612 +2
631 +2
613 +3
632 +3
614 4
633 4
615 5
634 5
616 +5.new
635 +5.new
617 +5.reallynew
636 +5.reallynew
618 6
637 6
619 7
638 7
620 8
639 8
621
640
622
641
623 Record end
642 Record end
624
643
625 $ hg commit -i -d '15 0' -m end-only plain <<EOF
644 $ hg commit -i -d '15 0' -m end-only plain <<EOF
626 > y
645 > y
627 > y
646 > y
628 > EOF
647 > EOF
629 diff --git a/plain b/plain
648 diff --git a/plain b/plain
630 1 hunks, 2 lines changed
649 1 hunks, 2 lines changed
631 examine changes to 'plain'? [Ynesfdaq?] y
650 examine changes to 'plain'? [Ynesfdaq?] y
632
651
633 @@ -9,3 +9,5 @@
652 @@ -9,3 +9,5 @@
634 7
653 7
635 8
654 8
636 9
655 9
637 +10
656 +10
638 +11
657 +11
639 record this change to 'plain'? [Ynesfdaq?] y
658 record this change to 'plain'? [Ynesfdaq?] y
640
659
641
660
642 $ hg tip -p
661 $ hg tip -p
643 changeset: 16:f9900b71a04c
662 changeset: 16:f9900b71a04c
644 tag: tip
663 tag: tip
645 user: test
664 user: test
646 date: Thu Jan 01 00:00:15 1970 +0000
665 date: Thu Jan 01 00:00:15 1970 +0000
647 summary: end-only
666 summary: end-only
648
667
649 diff -r f34a7937ec33 -r f9900b71a04c plain
668 diff -r f34a7937ec33 -r f9900b71a04c plain
650 --- a/plain Thu Jan 01 00:00:14 1970 +0000
669 --- a/plain Thu Jan 01 00:00:14 1970 +0000
651 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
670 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
652 @@ -9,3 +9,5 @@
671 @@ -9,3 +9,5 @@
653 7
672 7
654 8
673 8
655 9
674 9
656 +10
675 +10
657 +11
676 +11
658
677
659
678
660 $ mkdir subdir
679 $ mkdir subdir
661 $ cd subdir
680 $ cd subdir
662 $ echo a > a
681 $ echo a > a
663 $ hg ci -d '16 0' -Amsubdir
682 $ hg ci -d '16 0' -Amsubdir
664 adding subdir/a
683 adding subdir/a
665
684
666 $ echo a >> a
685 $ echo a >> a
667 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
686 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
668 > y
687 > y
669 > y
688 > y
670 > EOF
689 > EOF
671 diff --git a/subdir/a b/subdir/a
690 diff --git a/subdir/a b/subdir/a
672 1 hunks, 1 lines changed
691 1 hunks, 1 lines changed
673 examine changes to 'subdir/a'? [Ynesfdaq?] y
692 examine changes to 'subdir/a'? [Ynesfdaq?] y
674
693
675 @@ -1,1 +1,2 @@
694 @@ -1,1 +1,2 @@
676 a
695 a
677 +a
696 +a
678 record this change to 'subdir/a'? [Ynesfdaq?] y
697 record this change to 'subdir/a'? [Ynesfdaq?] y
679
698
680
699
681 $ hg tip -p
700 $ hg tip -p
682 changeset: 18:61be427a9deb
701 changeset: 18:61be427a9deb
683 tag: tip
702 tag: tip
684 user: test
703 user: test
685 date: Thu Jan 01 00:00:16 1970 +0000
704 date: Thu Jan 01 00:00:16 1970 +0000
686 summary: subdir-change
705 summary: subdir-change
687
706
688 diff -r a7ffae4d61cb -r 61be427a9deb subdir/a
707 diff -r a7ffae4d61cb -r 61be427a9deb subdir/a
689 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
708 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
690 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
709 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
691 @@ -1,1 +1,2 @@
710 @@ -1,1 +1,2 @@
692 a
711 a
693 +a
712 +a
694
713
695
714
696 $ echo a > f1
715 $ echo a > f1
697 $ echo b > f2
716 $ echo b > f2
698 $ hg add f1 f2
717 $ hg add f1 f2
699
718
700 $ hg ci -mz -d '17 0'
719 $ hg ci -mz -d '17 0'
701
720
702 $ echo a >> f1
721 $ echo a >> f1
703 $ echo b >> f2
722 $ echo b >> f2
704
723
705 Help, quit
724 Help, quit
706
725
707 $ hg commit -i <<EOF
726 $ hg commit -i <<EOF
708 > ?
727 > ?
709 > q
728 > q
710 > EOF
729 > EOF
711 diff --git a/subdir/f1 b/subdir/f1
730 diff --git a/subdir/f1 b/subdir/f1
712 1 hunks, 1 lines changed
731 1 hunks, 1 lines changed
713 examine changes to 'subdir/f1'? [Ynesfdaq?] ?
732 examine changes to 'subdir/f1'? [Ynesfdaq?] ?
714
733
715 y - yes, record this change
734 y - yes, record this change
716 n - no, skip this change
735 n - no, skip this change
717 e - edit this change manually
736 e - edit this change manually
718 s - skip remaining changes to this file
737 s - skip remaining changes to this file
719 f - record remaining changes to this file
738 f - record remaining changes to this file
720 d - done, skip remaining changes and files
739 d - done, skip remaining changes and files
721 a - record all changes to all remaining files
740 a - record all changes to all remaining files
722 q - quit, recording no changes
741 q - quit, recording no changes
723 ? - ? (display help)
742 ? - ? (display help)
724 examine changes to 'subdir/f1'? [Ynesfdaq?] q
743 examine changes to 'subdir/f1'? [Ynesfdaq?] q
725
744
726 abort: user quit
745 abort: user quit
727 [255]
746 [255]
728
747
729 Skip
748 Skip
730
749
731 $ hg commit -i <<EOF
750 $ hg commit -i <<EOF
732 > s
751 > s
733 > EOF
752 > EOF
734 diff --git a/subdir/f1 b/subdir/f1
753 diff --git a/subdir/f1 b/subdir/f1
735 1 hunks, 1 lines changed
754 1 hunks, 1 lines changed
736 examine changes to 'subdir/f1'? [Ynesfdaq?] s
755 examine changes to 'subdir/f1'? [Ynesfdaq?] s
737
756
738 diff --git a/subdir/f2 b/subdir/f2
757 diff --git a/subdir/f2 b/subdir/f2
739 1 hunks, 1 lines changed
758 1 hunks, 1 lines changed
740 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
759 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
741 [255]
760 [255]
742
761
743 No
762 No
744
763
745 $ hg commit -i <<EOF
764 $ hg commit -i <<EOF
746 > n
765 > n
747 > EOF
766 > EOF
748 diff --git a/subdir/f1 b/subdir/f1
767 diff --git a/subdir/f1 b/subdir/f1
749 1 hunks, 1 lines changed
768 1 hunks, 1 lines changed
750 examine changes to 'subdir/f1'? [Ynesfdaq?] n
769 examine changes to 'subdir/f1'? [Ynesfdaq?] n
751
770
752 diff --git a/subdir/f2 b/subdir/f2
771 diff --git a/subdir/f2 b/subdir/f2
753 1 hunks, 1 lines changed
772 1 hunks, 1 lines changed
754 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
773 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
755 [255]
774 [255]
756
775
757 f, quit
776 f, quit
758
777
759 $ hg commit -i <<EOF
778 $ hg commit -i <<EOF
760 > f
779 > f
761 > q
780 > q
762 > EOF
781 > EOF
763 diff --git a/subdir/f1 b/subdir/f1
782 diff --git a/subdir/f1 b/subdir/f1
764 1 hunks, 1 lines changed
783 1 hunks, 1 lines changed
765 examine changes to 'subdir/f1'? [Ynesfdaq?] f
784 examine changes to 'subdir/f1'? [Ynesfdaq?] f
766
785
767 diff --git a/subdir/f2 b/subdir/f2
786 diff --git a/subdir/f2 b/subdir/f2
768 1 hunks, 1 lines changed
787 1 hunks, 1 lines changed
769 examine changes to 'subdir/f2'? [Ynesfdaq?] q
788 examine changes to 'subdir/f2'? [Ynesfdaq?] q
770
789
771 abort: user quit
790 abort: user quit
772 [255]
791 [255]
773
792
774 s, all
793 s, all
775
794
776 $ hg commit -i -d '18 0' -mx <<EOF
795 $ hg commit -i -d '18 0' -mx <<EOF
777 > s
796 > s
778 > a
797 > a
779 > EOF
798 > EOF
780 diff --git a/subdir/f1 b/subdir/f1
799 diff --git a/subdir/f1 b/subdir/f1
781 1 hunks, 1 lines changed
800 1 hunks, 1 lines changed
782 examine changes to 'subdir/f1'? [Ynesfdaq?] s
801 examine changes to 'subdir/f1'? [Ynesfdaq?] s
783
802
784 diff --git a/subdir/f2 b/subdir/f2
803 diff --git a/subdir/f2 b/subdir/f2
785 1 hunks, 1 lines changed
804 1 hunks, 1 lines changed
786 examine changes to 'subdir/f2'? [Ynesfdaq?] a
805 examine changes to 'subdir/f2'? [Ynesfdaq?] a
787
806
788
807
789 $ hg tip -p
808 $ hg tip -p
790 changeset: 20:b3df3dda369a
809 changeset: 20:b3df3dda369a
791 tag: tip
810 tag: tip
792 user: test
811 user: test
793 date: Thu Jan 01 00:00:18 1970 +0000
812 date: Thu Jan 01 00:00:18 1970 +0000
794 summary: x
813 summary: x
795
814
796 diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2
815 diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2
797 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
816 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
798 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
817 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
799 @@ -1,1 +1,2 @@
818 @@ -1,1 +1,2 @@
800 b
819 b
801 +b
820 +b
802
821
803
822
804 f
823 f
805
824
806 $ hg commit -i -d '19 0' -my <<EOF
825 $ hg commit -i -d '19 0' -my <<EOF
807 > f
826 > f
808 > EOF
827 > EOF
809 diff --git a/subdir/f1 b/subdir/f1
828 diff --git a/subdir/f1 b/subdir/f1
810 1 hunks, 1 lines changed
829 1 hunks, 1 lines changed
811 examine changes to 'subdir/f1'? [Ynesfdaq?] f
830 examine changes to 'subdir/f1'? [Ynesfdaq?] f
812
831
813
832
814 $ hg tip -p
833 $ hg tip -p
815 changeset: 21:38ec577f126b
834 changeset: 21:38ec577f126b
816 tag: tip
835 tag: tip
817 user: test
836 user: test
818 date: Thu Jan 01 00:00:19 1970 +0000
837 date: Thu Jan 01 00:00:19 1970 +0000
819 summary: y
838 summary: y
820
839
821 diff -r b3df3dda369a -r 38ec577f126b subdir/f1
840 diff -r b3df3dda369a -r 38ec577f126b subdir/f1
822 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
841 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
823 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
842 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
824 @@ -1,1 +1,2 @@
843 @@ -1,1 +1,2 @@
825 a
844 a
826 +a
845 +a
827
846
828
847
829 #if execbit
848 #if execbit
830
849
831 Preserve chmod +x
850 Preserve chmod +x
832
851
833 $ chmod +x f1
852 $ chmod +x f1
834 $ echo a >> f1
853 $ echo a >> f1
835 $ hg commit -i -d '20 0' -mz <<EOF
854 $ hg commit -i -d '20 0' -mz <<EOF
836 > y
855 > y
837 > y
856 > y
838 > y
857 > y
839 > EOF
858 > EOF
840 diff --git a/subdir/f1 b/subdir/f1
859 diff --git a/subdir/f1 b/subdir/f1
841 old mode 100644
860 old mode 100644
842 new mode 100755
861 new mode 100755
843 1 hunks, 1 lines changed
862 1 hunks, 1 lines changed
844 examine changes to 'subdir/f1'? [Ynesfdaq?] y
863 examine changes to 'subdir/f1'? [Ynesfdaq?] y
845
864
846 @@ -1,2 +1,3 @@
865 @@ -1,2 +1,3 @@
847 a
866 a
848 a
867 a
849 +a
868 +a
850 record this change to 'subdir/f1'? [Ynesfdaq?] y
869 record this change to 'subdir/f1'? [Ynesfdaq?] y
851
870
852
871
853 $ hg tip --config diff.git=True -p
872 $ hg tip --config diff.git=True -p
854 changeset: 22:3261adceb075
873 changeset: 22:3261adceb075
855 tag: tip
874 tag: tip
856 user: test
875 user: test
857 date: Thu Jan 01 00:00:20 1970 +0000
876 date: Thu Jan 01 00:00:20 1970 +0000
858 summary: z
877 summary: z
859
878
860 diff --git a/subdir/f1 b/subdir/f1
879 diff --git a/subdir/f1 b/subdir/f1
861 old mode 100644
880 old mode 100644
862 new mode 100755
881 new mode 100755
863 --- a/subdir/f1
882 --- a/subdir/f1
864 +++ b/subdir/f1
883 +++ b/subdir/f1
865 @@ -1,2 +1,3 @@
884 @@ -1,2 +1,3 @@
866 a
885 a
867 a
886 a
868 +a
887 +a
869
888
870
889
871 Preserve execute permission on original
890 Preserve execute permission on original
872
891
873 $ echo b >> f1
892 $ echo b >> f1
874 $ hg commit -i -d '21 0' -maa <<EOF
893 $ hg commit -i -d '21 0' -maa <<EOF
875 > y
894 > y
876 > y
895 > y
877 > y
896 > y
878 > EOF
897 > EOF
879 diff --git a/subdir/f1 b/subdir/f1
898 diff --git a/subdir/f1 b/subdir/f1
880 1 hunks, 1 lines changed
899 1 hunks, 1 lines changed
881 examine changes to 'subdir/f1'? [Ynesfdaq?] y
900 examine changes to 'subdir/f1'? [Ynesfdaq?] y
882
901
883 @@ -1,3 +1,4 @@
902 @@ -1,3 +1,4 @@
884 a
903 a
885 a
904 a
886 a
905 a
887 +b
906 +b
888 record this change to 'subdir/f1'? [Ynesfdaq?] y
907 record this change to 'subdir/f1'? [Ynesfdaq?] y
889
908
890
909
891 $ hg tip --config diff.git=True -p
910 $ hg tip --config diff.git=True -p
892 changeset: 23:b429867550db
911 changeset: 23:b429867550db
893 tag: tip
912 tag: tip
894 user: test
913 user: test
895 date: Thu Jan 01 00:00:21 1970 +0000
914 date: Thu Jan 01 00:00:21 1970 +0000
896 summary: aa
915 summary: aa
897
916
898 diff --git a/subdir/f1 b/subdir/f1
917 diff --git a/subdir/f1 b/subdir/f1
899 --- a/subdir/f1
918 --- a/subdir/f1
900 +++ b/subdir/f1
919 +++ b/subdir/f1
901 @@ -1,3 +1,4 @@
920 @@ -1,3 +1,4 @@
902 a
921 a
903 a
922 a
904 a
923 a
905 +b
924 +b
906
925
907
926
908 Preserve chmod -x
927 Preserve chmod -x
909
928
910 $ chmod -x f1
929 $ chmod -x f1
911 $ echo c >> f1
930 $ echo c >> f1
912 $ hg commit -i -d '22 0' -mab <<EOF
931 $ hg commit -i -d '22 0' -mab <<EOF
913 > y
932 > y
914 > y
933 > y
915 > y
934 > y
916 > EOF
935 > EOF
917 diff --git a/subdir/f1 b/subdir/f1
936 diff --git a/subdir/f1 b/subdir/f1
918 old mode 100755
937 old mode 100755
919 new mode 100644
938 new mode 100644
920 1 hunks, 1 lines changed
939 1 hunks, 1 lines changed
921 examine changes to 'subdir/f1'? [Ynesfdaq?] y
940 examine changes to 'subdir/f1'? [Ynesfdaq?] y
922
941
923 @@ -2,3 +2,4 @@
942 @@ -2,3 +2,4 @@
924 a
943 a
925 a
944 a
926 b
945 b
927 +c
946 +c
928 record this change to 'subdir/f1'? [Ynesfdaq?] y
947 record this change to 'subdir/f1'? [Ynesfdaq?] y
929
948
930
949
931 $ hg tip --config diff.git=True -p
950 $ hg tip --config diff.git=True -p
932 changeset: 24:0b082130c20a
951 changeset: 24:0b082130c20a
933 tag: tip
952 tag: tip
934 user: test
953 user: test
935 date: Thu Jan 01 00:00:22 1970 +0000
954 date: Thu Jan 01 00:00:22 1970 +0000
936 summary: ab
955 summary: ab
937
956
938 diff --git a/subdir/f1 b/subdir/f1
957 diff --git a/subdir/f1 b/subdir/f1
939 old mode 100755
958 old mode 100755
940 new mode 100644
959 new mode 100644
941 --- a/subdir/f1
960 --- a/subdir/f1
942 +++ b/subdir/f1
961 +++ b/subdir/f1
943 @@ -2,3 +2,4 @@
962 @@ -2,3 +2,4 @@
944 a
963 a
945 a
964 a
946 b
965 b
947 +c
966 +c
948
967
949
968
950 #else
969 #else
951
970
952 Slightly bogus tests to get almost same repo structure as when x bit is used
971 Slightly bogus tests to get almost same repo structure as when x bit is used
953 - but with different hashes.
972 - but with different hashes.
954
973
955 Mock "Preserve chmod +x"
974 Mock "Preserve chmod +x"
956
975
957 $ echo a >> f1
976 $ echo a >> f1
958 $ hg commit -i -d '20 0' -mz <<EOF
977 $ hg commit -i -d '20 0' -mz <<EOF
959 > y
978 > y
960 > y
979 > y
961 > y
980 > y
962 > EOF
981 > EOF
963 diff --git a/subdir/f1 b/subdir/f1
982 diff --git a/subdir/f1 b/subdir/f1
964 1 hunks, 1 lines changed
983 1 hunks, 1 lines changed
965 examine changes to 'subdir/f1'? [Ynesfdaq?] y
984 examine changes to 'subdir/f1'? [Ynesfdaq?] y
966
985
967 @@ -1,2 +1,3 @@
986 @@ -1,2 +1,3 @@
968 a
987 a
969 a
988 a
970 +a
989 +a
971 record this change to 'subdir/f1'? [Ynesfdaq?] y
990 record this change to 'subdir/f1'? [Ynesfdaq?] y
972
991
973
992
974 $ hg tip --config diff.git=True -p
993 $ hg tip --config diff.git=True -p
975 changeset: 22:0d463bd428f5
994 changeset: 22:0d463bd428f5
976 tag: tip
995 tag: tip
977 user: test
996 user: test
978 date: Thu Jan 01 00:00:20 1970 +0000
997 date: Thu Jan 01 00:00:20 1970 +0000
979 summary: z
998 summary: z
980
999
981 diff --git a/subdir/f1 b/subdir/f1
1000 diff --git a/subdir/f1 b/subdir/f1
982 --- a/subdir/f1
1001 --- a/subdir/f1
983 +++ b/subdir/f1
1002 +++ b/subdir/f1
984 @@ -1,2 +1,3 @@
1003 @@ -1,2 +1,3 @@
985 a
1004 a
986 a
1005 a
987 +a
1006 +a
988
1007
989
1008
990 Mock "Preserve execute permission on original"
1009 Mock "Preserve execute permission on original"
991
1010
992 $ echo b >> f1
1011 $ echo b >> f1
993 $ hg commit -i -d '21 0' -maa <<EOF
1012 $ hg commit -i -d '21 0' -maa <<EOF
994 > y
1013 > y
995 > y
1014 > y
996 > y
1015 > y
997 > EOF
1016 > EOF
998 diff --git a/subdir/f1 b/subdir/f1
1017 diff --git a/subdir/f1 b/subdir/f1
999 1 hunks, 1 lines changed
1018 1 hunks, 1 lines changed
1000 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1019 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1001
1020
1002 @@ -1,3 +1,4 @@
1021 @@ -1,3 +1,4 @@
1003 a
1022 a
1004 a
1023 a
1005 a
1024 a
1006 +b
1025 +b
1007 record this change to 'subdir/f1'? [Ynesfdaq?] y
1026 record this change to 'subdir/f1'? [Ynesfdaq?] y
1008
1027
1009
1028
1010 $ hg tip --config diff.git=True -p
1029 $ hg tip --config diff.git=True -p
1011 changeset: 23:0eab41a3e524
1030 changeset: 23:0eab41a3e524
1012 tag: tip
1031 tag: tip
1013 user: test
1032 user: test
1014 date: Thu Jan 01 00:00:21 1970 +0000
1033 date: Thu Jan 01 00:00:21 1970 +0000
1015 summary: aa
1034 summary: aa
1016
1035
1017 diff --git a/subdir/f1 b/subdir/f1
1036 diff --git a/subdir/f1 b/subdir/f1
1018 --- a/subdir/f1
1037 --- a/subdir/f1
1019 +++ b/subdir/f1
1038 +++ b/subdir/f1
1020 @@ -1,3 +1,4 @@
1039 @@ -1,3 +1,4 @@
1021 a
1040 a
1022 a
1041 a
1023 a
1042 a
1024 +b
1043 +b
1025
1044
1026
1045
1027 Mock "Preserve chmod -x"
1046 Mock "Preserve chmod -x"
1028
1047
1029 $ chmod -x f1
1048 $ chmod -x f1
1030 $ echo c >> f1
1049 $ echo c >> f1
1031 $ hg commit -i -d '22 0' -mab <<EOF
1050 $ hg commit -i -d '22 0' -mab <<EOF
1032 > y
1051 > y
1033 > y
1052 > y
1034 > y
1053 > y
1035 > EOF
1054 > EOF
1036 diff --git a/subdir/f1 b/subdir/f1
1055 diff --git a/subdir/f1 b/subdir/f1
1037 1 hunks, 1 lines changed
1056 1 hunks, 1 lines changed
1038 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1057 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1039
1058
1040 @@ -2,3 +2,4 @@
1059 @@ -2,3 +2,4 @@
1041 a
1060 a
1042 a
1061 a
1043 b
1062 b
1044 +c
1063 +c
1045 record this change to 'subdir/f1'? [Ynesfdaq?] y
1064 record this change to 'subdir/f1'? [Ynesfdaq?] y
1046
1065
1047
1066
1048 $ hg tip --config diff.git=True -p
1067 $ hg tip --config diff.git=True -p
1049 changeset: 24:f4f718f27b7c
1068 changeset: 24:f4f718f27b7c
1050 tag: tip
1069 tag: tip
1051 user: test
1070 user: test
1052 date: Thu Jan 01 00:00:22 1970 +0000
1071 date: Thu Jan 01 00:00:22 1970 +0000
1053 summary: ab
1072 summary: ab
1054
1073
1055 diff --git a/subdir/f1 b/subdir/f1
1074 diff --git a/subdir/f1 b/subdir/f1
1056 --- a/subdir/f1
1075 --- a/subdir/f1
1057 +++ b/subdir/f1
1076 +++ b/subdir/f1
1058 @@ -2,3 +2,4 @@
1077 @@ -2,3 +2,4 @@
1059 a
1078 a
1060 a
1079 a
1061 b
1080 b
1062 +c
1081 +c
1063
1082
1064
1083
1065 #endif
1084 #endif
1066
1085
1067 $ cd ..
1086 $ cd ..
1068
1087
1069
1088
1070 Abort early when a merge is in progress
1089 Abort early when a merge is in progress
1071
1090
1072 $ hg up 4
1091 $ hg up 4
1073 1 files updated, 0 files merged, 6 files removed, 0 files unresolved
1092 1 files updated, 0 files merged, 6 files removed, 0 files unresolved
1074
1093
1075 $ touch iwillmergethat
1094 $ touch iwillmergethat
1076 $ hg add iwillmergethat
1095 $ hg add iwillmergethat
1077
1096
1078 $ hg branch thatbranch
1097 $ hg branch thatbranch
1079 marked working directory as branch thatbranch
1098 marked working directory as branch thatbranch
1080 (branches are permanent and global, did you want a bookmark?)
1099 (branches are permanent and global, did you want a bookmark?)
1081
1100
1082 $ hg ci -m'new head'
1101 $ hg ci -m'new head'
1083
1102
1084 $ hg up default
1103 $ hg up default
1085 6 files updated, 0 files merged, 2 files removed, 0 files unresolved
1104 6 files updated, 0 files merged, 2 files removed, 0 files unresolved
1086
1105
1087 $ hg merge thatbranch
1106 $ hg merge thatbranch
1088 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1107 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1089 (branch merge, don't forget to commit)
1108 (branch merge, don't forget to commit)
1090
1109
1091 $ hg commit -i -m'will abort'
1110 $ hg commit -i -m'will abort'
1092 abort: cannot partially commit a merge (use "hg commit" instead)
1111 abort: cannot partially commit a merge (use "hg commit" instead)
1093 [255]
1112 [255]
1094
1113
1095 $ hg up -C
1114 $ hg up -C
1096 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1115 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1097
1116
1098 Editing patch (and ignoring trailing text)
1117 Editing patch (and ignoring trailing text)
1099
1118
1100 $ cat > editor.sh << '__EOF__'
1119 $ cat > editor.sh << '__EOF__'
1101 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1120 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1102 > trailing\nditto' "$1" > tmp
1121 > trailing\nditto' "$1" > tmp
1103 > mv tmp "$1"
1122 > mv tmp "$1"
1104 > __EOF__
1123 > __EOF__
1105 $ cat > editedfile << '__EOF__'
1124 $ cat > editedfile << '__EOF__'
1106 > This is the first line
1125 > This is the first line
1107 > This is the second line
1126 > This is the second line
1108 > This is the third line
1127 > This is the third line
1109 > __EOF__
1128 > __EOF__
1110 $ hg add editedfile
1129 $ hg add editedfile
1111 $ hg commit -medit-patch-1
1130 $ hg commit -medit-patch-1
1112 $ cat > editedfile << '__EOF__'
1131 $ cat > editedfile << '__EOF__'
1113 > This line has changed
1132 > This line has changed
1114 > This change will be committed
1133 > This change will be committed
1115 > This is the third line
1134 > This is the third line
1116 > __EOF__
1135 > __EOF__
1117 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1136 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1118 > y
1137 > y
1119 > e
1138 > e
1120 > EOF
1139 > EOF
1121 diff --git a/editedfile b/editedfile
1140 diff --git a/editedfile b/editedfile
1122 1 hunks, 2 lines changed
1141 1 hunks, 2 lines changed
1123 examine changes to 'editedfile'? [Ynesfdaq?] y
1142 examine changes to 'editedfile'? [Ynesfdaq?] y
1124
1143
1125 @@ -1,3 +1,3 @@
1144 @@ -1,3 +1,3 @@
1126 -This is the first line
1145 -This is the first line
1127 -This is the second line
1146 -This is the second line
1128 +This line has changed
1147 +This line has changed
1129 +This change will be committed
1148 +This change will be committed
1130 This is the third line
1149 This is the third line
1131 record this change to 'editedfile'? [Ynesfdaq?] e
1150 record this change to 'editedfile'? [Ynesfdaq?] e
1132
1151
1133 $ cat editedfile
1152 $ cat editedfile
1134 This line has changed
1153 This line has changed
1135 This change will be committed
1154 This change will be committed
1136 This is the third line
1155 This is the third line
1137 $ hg cat -r tip editedfile
1156 $ hg cat -r tip editedfile
1138 This is the first line
1157 This is the first line
1139 This change will be committed
1158 This change will be committed
1140 This is the third line
1159 This is the third line
1141 $ hg revert editedfile
1160 $ hg revert editedfile
1142
1161
1143 Trying to edit patch for whole file
1162 Trying to edit patch for whole file
1144
1163
1145 $ echo "This is the fourth line" >> editedfile
1164 $ echo "This is the fourth line" >> editedfile
1146 $ hg commit -i <<EOF
1165 $ hg commit -i <<EOF
1147 > e
1166 > e
1148 > q
1167 > q
1149 > EOF
1168 > EOF
1150 diff --git a/editedfile b/editedfile
1169 diff --git a/editedfile b/editedfile
1151 1 hunks, 1 lines changed
1170 1 hunks, 1 lines changed
1152 examine changes to 'editedfile'? [Ynesfdaq?] e
1171 examine changes to 'editedfile'? [Ynesfdaq?] e
1153
1172
1154 cannot edit patch for whole file
1173 cannot edit patch for whole file
1155 examine changes to 'editedfile'? [Ynesfdaq?] q
1174 examine changes to 'editedfile'? [Ynesfdaq?] q
1156
1175
1157 abort: user quit
1176 abort: user quit
1158 [255]
1177 [255]
1159 $ hg revert editedfile
1178 $ hg revert editedfile
1160
1179
1161 Removing changes from patch
1180 Removing changes from patch
1162
1181
1163 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1182 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1164 $ mv tmp editedfile
1183 $ mv tmp editedfile
1165 $ echo "This line has been added" >> editedfile
1184 $ echo "This line has been added" >> editedfile
1166 $ cat > editor.sh << '__EOF__'
1185 $ cat > editor.sh << '__EOF__'
1167 > sed -e 's/^[-+]/ /' "$1" > tmp
1186 > sed -e 's/^[-+]/ /' "$1" > tmp
1168 > mv tmp "$1"
1187 > mv tmp "$1"
1169 > __EOF__
1188 > __EOF__
1170 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1189 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1171 > y
1190 > y
1172 > e
1191 > e
1173 > EOF
1192 > EOF
1174 diff --git a/editedfile b/editedfile
1193 diff --git a/editedfile b/editedfile
1175 1 hunks, 3 lines changed
1194 1 hunks, 3 lines changed
1176 examine changes to 'editedfile'? [Ynesfdaq?] y
1195 examine changes to 'editedfile'? [Ynesfdaq?] y
1177
1196
1178 @@ -1,3 +1,3 @@
1197 @@ -1,3 +1,3 @@
1179 -This is the first line
1198 -This is the first line
1180 -This change will be committed
1199 -This change will be committed
1181 -This is the third line
1200 -This is the third line
1182 +This change will not be committed
1201 +This change will not be committed
1183 +This is the second line
1202 +This is the second line
1184 +This line has been added
1203 +This line has been added
1185 record this change to 'editedfile'? [Ynesfdaq?] e
1204 record this change to 'editedfile'? [Ynesfdaq?] e
1186
1205
1187 no changes to record
1206 no changes to record
1188 $ cat editedfile
1207 $ cat editedfile
1189 This change will not be committed
1208 This change will not be committed
1190 This is the second line
1209 This is the second line
1191 This line has been added
1210 This line has been added
1192 $ hg cat -r tip editedfile
1211 $ hg cat -r tip editedfile
1193 This is the first line
1212 This is the first line
1194 This change will be committed
1213 This change will be committed
1195 This is the third line
1214 This is the third line
1196 $ hg revert editedfile
1215 $ hg revert editedfile
1197
1216
1198 Invalid patch
1217 Invalid patch
1199
1218
1200 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1219 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1201 $ mv tmp editedfile
1220 $ mv tmp editedfile
1202 $ echo "This line has been added" >> editedfile
1221 $ echo "This line has been added" >> editedfile
1203 $ cat > editor.sh << '__EOF__'
1222 $ cat > editor.sh << '__EOF__'
1204 > sed s/This/That/ "$1" > tmp
1223 > sed s/This/That/ "$1" > tmp
1205 > mv tmp "$1"
1224 > mv tmp "$1"
1206 > __EOF__
1225 > __EOF__
1207 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1226 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1208 > y
1227 > y
1209 > e
1228 > e
1210 > EOF
1229 > EOF
1211 diff --git a/editedfile b/editedfile
1230 diff --git a/editedfile b/editedfile
1212 1 hunks, 3 lines changed
1231 1 hunks, 3 lines changed
1213 examine changes to 'editedfile'? [Ynesfdaq?] y
1232 examine changes to 'editedfile'? [Ynesfdaq?] y
1214
1233
1215 @@ -1,3 +1,3 @@
1234 @@ -1,3 +1,3 @@
1216 -This is the first line
1235 -This is the first line
1217 -This change will be committed
1236 -This change will be committed
1218 -This is the third line
1237 -This is the third line
1219 +This change will not be committed
1238 +This change will not be committed
1220 +This is the second line
1239 +This is the second line
1221 +This line has been added
1240 +This line has been added
1222 record this change to 'editedfile'? [Ynesfdaq?] e
1241 record this change to 'editedfile'? [Ynesfdaq?] e
1223
1242
1224 patching file editedfile
1243 patching file editedfile
1225 Hunk #1 FAILED at 0
1244 Hunk #1 FAILED at 0
1226 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1245 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1227 abort: patch failed to apply
1246 abort: patch failed to apply
1228 [255]
1247 [255]
1229 $ cat editedfile
1248 $ cat editedfile
1230 This change will not be committed
1249 This change will not be committed
1231 This is the second line
1250 This is the second line
1232 This line has been added
1251 This line has been added
1233 $ hg cat -r tip editedfile
1252 $ hg cat -r tip editedfile
1234 This is the first line
1253 This is the first line
1235 This change will be committed
1254 This change will be committed
1236 This is the third line
1255 This is the third line
1237 $ cat editedfile.rej
1256 $ cat editedfile.rej
1238 --- editedfile
1257 --- editedfile
1239 +++ editedfile
1258 +++ editedfile
1240 @@ -1,3 +1,3 @@
1259 @@ -1,3 +1,3 @@
1241 -That is the first line
1260 -That is the first line
1242 -That change will be committed
1261 -That change will be committed
1243 -That is the third line
1262 -That is the third line
1244 +That change will not be committed
1263 +That change will not be committed
1245 +That is the second line
1264 +That is the second line
1246 +That line has been added
1265 +That line has been added
1247
1266
1248 Malformed patch - error handling
1267 Malformed patch - error handling
1249
1268
1250 $ cat > editor.sh << '__EOF__'
1269 $ cat > editor.sh << '__EOF__'
1251 > sed -e '/^@/p' "$1" > tmp
1270 > sed -e '/^@/p' "$1" > tmp
1252 > mv tmp "$1"
1271 > mv tmp "$1"
1253 > __EOF__
1272 > __EOF__
1254 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1273 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1255 > y
1274 > y
1256 > e
1275 > e
1257 > EOF
1276 > EOF
1258 diff --git a/editedfile b/editedfile
1277 diff --git a/editedfile b/editedfile
1259 1 hunks, 3 lines changed
1278 1 hunks, 3 lines changed
1260 examine changes to 'editedfile'? [Ynesfdaq?] y
1279 examine changes to 'editedfile'? [Ynesfdaq?] y
1261
1280
1262 @@ -1,3 +1,3 @@
1281 @@ -1,3 +1,3 @@
1263 -This is the first line
1282 -This is the first line
1264 -This change will be committed
1283 -This change will be committed
1265 -This is the third line
1284 -This is the third line
1266 +This change will not be committed
1285 +This change will not be committed
1267 +This is the second line
1286 +This is the second line
1268 +This line has been added
1287 +This line has been added
1269 record this change to 'editedfile'? [Ynesfdaq?] e
1288 record this change to 'editedfile'? [Ynesfdaq?] e
1270
1289
1271 abort: error parsing patch: unhandled transition: range -> range
1290 abort: error parsing patch: unhandled transition: range -> range
1272 [255]
1291 [255]
1273
1292
1274 random text in random positions is still an error
1293 random text in random positions is still an error
1275
1294
1276 $ cat > editor.sh << '__EOF__'
1295 $ cat > editor.sh << '__EOF__'
1277 > sed -e '/^@/i\
1296 > sed -e '/^@/i\
1278 > other' "$1" > tmp
1297 > other' "$1" > tmp
1279 > mv tmp "$1"
1298 > mv tmp "$1"
1280 > __EOF__
1299 > __EOF__
1281 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1300 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1282 > y
1301 > y
1283 > e
1302 > e
1284 > EOF
1303 > EOF
1285 diff --git a/editedfile b/editedfile
1304 diff --git a/editedfile b/editedfile
1286 1 hunks, 3 lines changed
1305 1 hunks, 3 lines changed
1287 examine changes to 'editedfile'? [Ynesfdaq?] y
1306 examine changes to 'editedfile'? [Ynesfdaq?] y
1288
1307
1289 @@ -1,3 +1,3 @@
1308 @@ -1,3 +1,3 @@
1290 -This is the first line
1309 -This is the first line
1291 -This change will be committed
1310 -This change will be committed
1292 -This is the third line
1311 -This is the third line
1293 +This change will not be committed
1312 +This change will not be committed
1294 +This is the second line
1313 +This is the second line
1295 +This line has been added
1314 +This line has been added
1296 record this change to 'editedfile'? [Ynesfdaq?] e
1315 record this change to 'editedfile'? [Ynesfdaq?] e
1297
1316
1298 abort: error parsing patch: unhandled transition: file -> other
1317 abort: error parsing patch: unhandled transition: file -> other
1299 [255]
1318 [255]
1300
1319
1301 $ hg up -C
1320 $ hg up -C
1302 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1303
1322
1304 With win32text
1323 With win32text
1305
1324
1306 $ echo '[extensions]' >> .hg/hgrc
1325 $ echo '[extensions]' >> .hg/hgrc
1307 $ echo 'win32text = ' >> .hg/hgrc
1326 $ echo 'win32text = ' >> .hg/hgrc
1308 $ echo '[decode]' >> .hg/hgrc
1327 $ echo '[decode]' >> .hg/hgrc
1309 $ echo '** = cleverdecode:' >> .hg/hgrc
1328 $ echo '** = cleverdecode:' >> .hg/hgrc
1310 $ echo '[encode]' >> .hg/hgrc
1329 $ echo '[encode]' >> .hg/hgrc
1311 $ echo '** = cleverencode:' >> .hg/hgrc
1330 $ echo '** = cleverencode:' >> .hg/hgrc
1312 $ echo '[patch]' >> .hg/hgrc
1331 $ echo '[patch]' >> .hg/hgrc
1313 $ echo 'eol = crlf' >> .hg/hgrc
1332 $ echo 'eol = crlf' >> .hg/hgrc
1314
1333
1315 Ignore win32text deprecation warning for now:
1334 Ignore win32text deprecation warning for now:
1316
1335
1317 $ echo '[win32text]' >> .hg/hgrc
1336 $ echo '[win32text]' >> .hg/hgrc
1318 $ echo 'warn = no' >> .hg/hgrc
1337 $ echo 'warn = no' >> .hg/hgrc
1319
1338
1320 $ echo d >> subdir/f1
1339 $ echo d >> subdir/f1
1321 $ hg commit -i -d '24 0' -mw1 <<EOF
1340 $ hg commit -i -d '24 0' -mw1 <<EOF
1322 > y
1341 > y
1323 > y
1342 > y
1324 > EOF
1343 > EOF
1325 diff --git a/subdir/f1 b/subdir/f1
1344 diff --git a/subdir/f1 b/subdir/f1
1326 1 hunks, 1 lines changed
1345 1 hunks, 1 lines changed
1327 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1346 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1328
1347
1329 @@ -3,3 +3,4 @@
1348 @@ -3,3 +3,4 @@
1330 a
1349 a
1331 b
1350 b
1332 c
1351 c
1333 +d
1352 +d
1334 record this change to 'subdir/f1'? [Ynesfdaq?] y
1353 record this change to 'subdir/f1'? [Ynesfdaq?] y
1335
1354
1336
1355
1337 $ hg tip -p
1356 $ hg tip -p
1338 changeset: 28:* (glob)
1357 changeset: 28:* (glob)
1339 tag: tip
1358 tag: tip
1340 user: test
1359 user: test
1341 date: Thu Jan 01 00:00:24 1970 +0000
1360 date: Thu Jan 01 00:00:24 1970 +0000
1342 summary: w1
1361 summary: w1
1343
1362
1344 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1363 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1345 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1364 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1346 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1365 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1347 @@ -3,3 +3,4 @@
1366 @@ -3,3 +3,4 @@
1348 a
1367 a
1349 b
1368 b
1350 c
1369 c
1351 +d
1370 +d
1352
1371
1353
1372
1354
1373
1355 Test --user when ui.username not set
1374 Test --user when ui.username not set
1356 $ unset HGUSER
1375 $ unset HGUSER
1357 $ echo e >> subdir/f1
1376 $ echo e >> subdir/f1
1358 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1377 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1359 > y
1378 > y
1360 > y
1379 > y
1361 > EOF
1380 > EOF
1362 diff --git a/subdir/f1 b/subdir/f1
1381 diff --git a/subdir/f1 b/subdir/f1
1363 1 hunks, 1 lines changed
1382 1 hunks, 1 lines changed
1364 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1383 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1365
1384
1366 @@ -4,3 +4,4 @@
1385 @@ -4,3 +4,4 @@
1367 b
1386 b
1368 c
1387 c
1369 d
1388 d
1370 +e
1389 +e
1371 record this change to 'subdir/f1'? [Ynesfdaq?] y
1390 record this change to 'subdir/f1'? [Ynesfdaq?] y
1372
1391
1373 $ hg log --template '{author}\n' -l 1
1392 $ hg log --template '{author}\n' -l 1
1374 xyz
1393 xyz
1375 $ HGUSER="test"
1394 $ HGUSER="test"
1376 $ export HGUSER
1395 $ export HGUSER
1377
1396
1378
1397
1379 Moving files
1398 Moving files
1380
1399
1381 $ hg update -C .
1400 $ hg update -C .
1382 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1401 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1383 $ hg mv plain plain3
1402 $ hg mv plain plain3
1384 $ echo somechange >> plain3
1403 $ echo somechange >> plain3
1385 $ hg commit -i -d '23 0' -mmoving_files << EOF
1404 $ hg commit -i -d '23 0' -mmoving_files << EOF
1386 > y
1405 > y
1387 > y
1406 > y
1388 > EOF
1407 > EOF
1389 diff --git a/plain b/plain3
1408 diff --git a/plain b/plain3
1390 rename from plain
1409 rename from plain
1391 rename to plain3
1410 rename to plain3
1392 1 hunks, 1 lines changed
1411 1 hunks, 1 lines changed
1393 examine changes to 'plain' and 'plain3'? [Ynesfdaq?] y
1412 examine changes to 'plain' and 'plain3'? [Ynesfdaq?] y
1394
1413
1395 @@ -11,3 +11,4 @@
1414 @@ -11,3 +11,4 @@
1396 9
1415 9
1397 10
1416 10
1398 11
1417 11
1399 +somechange
1418 +somechange
1400 record this change to 'plain3'? [Ynesfdaq?] y
1419 record this change to 'plain3'? [Ynesfdaq?] y
1401
1420
1402 $ hg tip
1421 $ hg tip
1403 changeset: 30:542e1f362a22
1422 changeset: 30:542e1f362a22
1404 tag: tip
1423 tag: tip
1405 user: test
1424 user: test
1406 date: Thu Jan 01 00:00:23 1970 +0000
1425 date: Thu Jan 01 00:00:23 1970 +0000
1407 summary: moving_files
1426 summary: moving_files
1408
1427
1428 Editing patch of newly added file
1429
1430 $ hg update -C .
1431 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1432 $ cat > editor.sh << '__EOF__'
1433 > cat "$1" | sed "s/first/very/g" > tt
1434 > mv tt "$1"
1435 > __EOF__
1436 $ cat > newfile << '__EOF__'
1437 > This is the first line
1438 > This is the second line
1439 > This is the third line
1440 > __EOF__
1441 $ hg add newfile
1442 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1443 > y
1444 > e
1445 > EOF
1446 diff --git a/newfile b/newfile
1447 new file mode 100644
1448 examine changes to 'newfile'? [Ynesfdaq?] y
1449
1450 @@ -0,0 +1,3 @@
1451 +This is the first line
1452 +This is the second line
1453 +This is the third line
1454 record this change to 'newfile'? [Ynesfdaq?] e
1455
1456 $ hg cat -r tip newfile
1457 This is the very line
1458 This is the second line
1459 This is the third line
1460
1461 $ cat newfile
1462 This is the first line
1463 This is the second line
1464 This is the third line
1409 $ cd ..
1465 $ cd ..
@@ -1,1327 +1,1339 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
482 committing files:
488 committing files:
483 r
489 r
484 committing manifest
490 committing manifest
485 committing changelog
491 committing changelog
486 committed changeset 3:82a2f715724d
492 committed changeset 3:82a2f715724d
487 overwriting r expanding keywords
493 overwriting r expanding keywords
488 $ hg status r
494 $ hg status r
489 $ hg --verbose rollback
495 $ hg --verbose rollback
490 repository tip rolled back to revision 2 (undo commit)
496 repository tip rolled back to revision 2 (undo commit)
491 working directory now based on revision 2
497 working directory now based on revision 2
492 overwriting r shrinking keywords
498 overwriting r shrinking keywords
493 $ hg forget r
499 $ hg forget r
494 $ rm msg r
500 $ rm msg r
495 $ hg update -C
501 $ hg update -C
496 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
502 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
497
503
498 record added keyword ignored file
504 record added keyword ignored file
499
505
500 $ echo '$Id$' > i
506 $ echo '$Id$' > i
501 $ hg add i
507 $ hg add i
502 $ hg --verbose record -d '13 1' -m recignored<<EOF
508 $ hg --verbose record -d '13 1' -m recignored<<EOF
503 > y
509 > y
504 > y
510 > y
505 > EOF
511 > EOF
506 diff --git a/i b/i
512 diff --git a/i b/i
507 new file mode 100644
513 new file mode 100644
508 examine changes to 'i'? [Ynesfdaq?] y
514 examine changes to 'i'? [Ynesfdaq?] y
509
515
516 @@ -0,0 +1,1 @@
517 +$Id$
518 record this change to 'i'? [Ynesfdaq?] y
519
520 resolving manifests
521 patching file i
510 committing files:
522 committing files:
511 i
523 i
512 committing manifest
524 committing manifest
513 committing changelog
525 committing changelog
514 committed changeset 3:9f40ceb5a072
526 committed changeset 3:9f40ceb5a072
515 $ cat i
527 $ cat i
516 $Id$
528 $Id$
517 $ hg -q rollback
529 $ hg -q rollback
518 $ hg forget i
530 $ hg forget i
519 $ rm i
531 $ rm i
520
532
521 amend
533 amend
522
534
523 $ echo amend >> a
535 $ echo amend >> a
524 $ echo amend >> b
536 $ echo amend >> b
525 $ hg -q commit -d '14 1' -m 'prepare amend'
537 $ hg -q commit -d '14 1' -m 'prepare amend'
526
538
527 $ hg --debug commit --amend -d '15 1' -m 'amend without changes' | grep keywords
539 $ hg --debug commit --amend -d '15 1' -m 'amend without changes' | grep keywords
528 overwriting a expanding keywords
540 overwriting a expanding keywords
529 $ hg -q id
541 $ hg -q id
530 67d8c481a6be
542 67d8c481a6be
531 $ head -1 a
543 $ head -1 a
532 expand $Id: a,v 67d8c481a6be 1970/01/01 00:00:15 test $
544 expand $Id: a,v 67d8c481a6be 1970/01/01 00:00:15 test $
533
545
534 $ hg -q strip --no-backup tip
546 $ hg -q strip --no-backup tip
535
547
536 Test patch queue repo
548 Test patch queue repo
537
549
538 $ hg init --mq
550 $ hg init --mq
539 $ hg qimport -r tip -n mqtest.diff
551 $ hg qimport -r tip -n mqtest.diff
540 $ hg commit --mq -m mqtest
552 $ hg commit --mq -m mqtest
541
553
542 Keywords should not be expanded in patch
554 Keywords should not be expanded in patch
543
555
544 $ cat .hg/patches/mqtest.diff
556 $ cat .hg/patches/mqtest.diff
545 # HG changeset patch
557 # HG changeset patch
546 # User User Name <user@example.com>
558 # User User Name <user@example.com>
547 # Date 1 0
559 # Date 1 0
548 # Thu Jan 01 00:00:01 1970 +0000
560 # Thu Jan 01 00:00:01 1970 +0000
549 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
561 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
550 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
562 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
551 cndiff
563 cndiff
552
564
553 diff -r ef63ca68695b -r 40a904bbbe4c c
565 diff -r ef63ca68695b -r 40a904bbbe4c c
554 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
566 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
555 +++ b/c Thu Jan 01 00:00:01 1970 +0000
567 +++ b/c Thu Jan 01 00:00:01 1970 +0000
556 @@ -0,0 +1,2 @@
568 @@ -0,0 +1,2 @@
557 +$Id$
569 +$Id$
558 +tests for different changenodes
570 +tests for different changenodes
559
571
560 $ hg qpop
572 $ hg qpop
561 popping mqtest.diff
573 popping mqtest.diff
562 patch queue now empty
574 patch queue now empty
563
575
564 qgoto, implying qpush, should expand
576 qgoto, implying qpush, should expand
565
577
566 $ hg qgoto mqtest.diff
578 $ hg qgoto mqtest.diff
567 applying mqtest.diff
579 applying mqtest.diff
568 now at: mqtest.diff
580 now at: mqtest.diff
569 $ cat c
581 $ cat c
570 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
582 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
571 tests for different changenodes
583 tests for different changenodes
572 $ hg cat c
584 $ hg cat c
573 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
585 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
574 tests for different changenodes
586 tests for different changenodes
575
587
576 Keywords should not be expanded in filelog
588 Keywords should not be expanded in filelog
577
589
578 $ hg --config 'extensions.keyword=!' cat c
590 $ hg --config 'extensions.keyword=!' cat c
579 $Id$
591 $Id$
580 tests for different changenodes
592 tests for different changenodes
581
593
582 qpop and move on
594 qpop and move on
583
595
584 $ hg qpop
596 $ hg qpop
585 popping mqtest.diff
597 popping mqtest.diff
586 patch queue now empty
598 patch queue now empty
587
599
588 Copy and show added kwfiles
600 Copy and show added kwfiles
589
601
590 $ hg cp a c
602 $ hg cp a c
591 $ hg kwfiles
603 $ hg kwfiles
592 a
604 a
593 c
605 c
594
606
595 Commit and show expansion in original and copy
607 Commit and show expansion in original and copy
596
608
597 $ hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
609 $ hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
598 committing files:
610 committing files:
599 c
611 c
600 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
612 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
601 committing manifest
613 committing manifest
602 committing changelog
614 committing changelog
603 overwriting c expanding keywords
615 overwriting c expanding keywords
604 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
616 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
605 $ cat a c
617 $ cat a c
606 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
618 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
607 do not process $Id:
619 do not process $Id:
608 xxx $
620 xxx $
609 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
621 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
610 do not process $Id:
622 do not process $Id:
611 xxx $
623 xxx $
612
624
613 Touch copied c and check its status
625 Touch copied c and check its status
614
626
615 $ touch c
627 $ touch c
616 $ hg status
628 $ hg status
617
629
618 Copy kwfile to keyword ignored file unexpanding keywords
630 Copy kwfile to keyword ignored file unexpanding keywords
619
631
620 $ hg --verbose copy a i
632 $ hg --verbose copy a i
621 copying a to i
633 copying a to i
622 overwriting i shrinking keywords
634 overwriting i shrinking keywords
623 $ head -n 1 i
635 $ head -n 1 i
624 expand $Id$
636 expand $Id$
625 $ hg forget i
637 $ hg forget i
626 $ rm i
638 $ rm i
627
639
628 Copy ignored file to ignored file: no overwriting
640 Copy ignored file to ignored file: no overwriting
629
641
630 $ hg --verbose copy b i
642 $ hg --verbose copy b i
631 copying b to i
643 copying b to i
632 $ hg forget i
644 $ hg forget i
633 $ rm i
645 $ rm i
634
646
635 cp symlink file; hg cp -A symlink file (part1)
647 cp symlink file; hg cp -A symlink file (part1)
636 - copied symlink points to kwfile: overwrite
648 - copied symlink points to kwfile: overwrite
637
649
638 #if symlink
650 #if symlink
639 $ cp sym i
651 $ cp sym i
640 $ ls -l i
652 $ ls -l i
641 -rw-r--r--* (glob)
653 -rw-r--r--* (glob)
642 $ head -1 i
654 $ head -1 i
643 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
655 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
644 $ hg copy --after --verbose sym i
656 $ hg copy --after --verbose sym i
645 copying sym to i
657 copying sym to i
646 overwriting i shrinking keywords
658 overwriting i shrinking keywords
647 $ head -1 i
659 $ head -1 i
648 expand $Id$
660 expand $Id$
649 $ hg forget i
661 $ hg forget i
650 $ rm i
662 $ rm i
651 #endif
663 #endif
652
664
653 Test different options of hg kwfiles
665 Test different options of hg kwfiles
654
666
655 $ hg kwfiles
667 $ hg kwfiles
656 a
668 a
657 c
669 c
658 $ hg -v kwfiles --ignore
670 $ hg -v kwfiles --ignore
659 I b
671 I b
660 I sym
672 I sym
661 $ hg kwfiles --all
673 $ hg kwfiles --all
662 K a
674 K a
663 K c
675 K c
664 I b
676 I b
665 I sym
677 I sym
666
678
667 Diff specific revision
679 Diff specific revision
668
680
669 $ hg diff --rev 1
681 $ hg diff --rev 1
670 diff -r ef63ca68695b c
682 diff -r ef63ca68695b c
671 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
683 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
672 +++ b/c * (glob)
684 +++ b/c * (glob)
673 @@ -0,0 +1,3 @@
685 @@ -0,0 +1,3 @@
674 +expand $Id$
686 +expand $Id$
675 +do not process $Id:
687 +do not process $Id:
676 +xxx $
688 +xxx $
677
689
678 Status after rollback:
690 Status after rollback:
679
691
680 $ hg rollback
692 $ hg rollback
681 repository tip rolled back to revision 1 (undo commit)
693 repository tip rolled back to revision 1 (undo commit)
682 working directory now based on revision 1
694 working directory now based on revision 1
683 $ hg status
695 $ hg status
684 A c
696 A c
685 $ hg update --clean
697 $ hg update --clean
686 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
698 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
687
699
688 #if symlink
700 #if symlink
689
701
690 cp symlink file; hg cp -A symlink file (part2)
702 cp symlink file; hg cp -A symlink file (part2)
691 - copied symlink points to kw ignored file: do not overwrite
703 - copied symlink points to kw ignored file: do not overwrite
692
704
693 $ cat a > i
705 $ cat a > i
694 $ ln -s i symignored
706 $ ln -s i symignored
695 $ hg commit -Am 'fake expansion in ignored and symlink' i symignored
707 $ hg commit -Am 'fake expansion in ignored and symlink' i symignored
696 $ cp symignored x
708 $ cp symignored x
697 $ hg copy --after --verbose symignored x
709 $ hg copy --after --verbose symignored x
698 copying symignored to x
710 copying symignored to x
699 $ head -n 1 x
711 $ head -n 1 x
700 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
712 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
701 $ hg forget x
713 $ hg forget x
702 $ rm x
714 $ rm x
703
715
704 $ hg rollback
716 $ hg rollback
705 repository tip rolled back to revision 1 (undo commit)
717 repository tip rolled back to revision 1 (undo commit)
706 working directory now based on revision 1
718 working directory now based on revision 1
707 $ hg update --clean
719 $ hg update --clean
708 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
720 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
709 $ rm i symignored
721 $ rm i symignored
710
722
711 #endif
723 #endif
712
724
713 Custom keywordmaps as argument to kwdemo
725 Custom keywordmaps as argument to kwdemo
714
726
715 $ hg --quiet kwdemo "Xinfo = {author}: {desc}"
727 $ hg --quiet kwdemo "Xinfo = {author}: {desc}"
716 [extensions]
728 [extensions]
717 keyword =
729 keyword =
718 [keyword]
730 [keyword]
719 ** =
731 ** =
720 b = ignore
732 b = ignore
721 demo.txt =
733 demo.txt =
722 i = ignore
734 i = ignore
723 [keywordset]
735 [keywordset]
724 svn = False
736 svn = False
725 [keywordmaps]
737 [keywordmaps]
726 Xinfo = {author}: {desc}
738 Xinfo = {author}: {desc}
727 $Xinfo: test: hg keyword configuration and expansion example $
739 $Xinfo: test: hg keyword configuration and expansion example $
728
740
729 Configure custom keywordmaps
741 Configure custom keywordmaps
730
742
731 $ cat <<EOF >>$HGRCPATH
743 $ cat <<EOF >>$HGRCPATH
732 > [keywordmaps]
744 > [keywordmaps]
733 > Id = {file} {node|short} {date|rfc822date} {author|user}
745 > Id = {file} {node|short} {date|rfc822date} {author|user}
734 > Xinfo = {author}: {desc}
746 > Xinfo = {author}: {desc}
735 > EOF
747 > EOF
736
748
737 Cat and hg cat files before custom expansion
749 Cat and hg cat files before custom expansion
738
750
739 $ cat a b
751 $ cat a b
740 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
752 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
741 do not process $Id:
753 do not process $Id:
742 xxx $
754 xxx $
743 ignore $Id$
755 ignore $Id$
744 $ hg cat sym a b && echo
756 $ hg cat sym a b && echo
745 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
757 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
746 do not process $Id:
758 do not process $Id:
747 xxx $
759 xxx $
748 ignore $Id$
760 ignore $Id$
749 a
761 a
750
762
751 Write custom keyword and prepare multi-line commit message
763 Write custom keyword and prepare multi-line commit message
752
764
753 $ echo '$Xinfo$' >> a
765 $ echo '$Xinfo$' >> a
754 $ cat <<EOF >> log
766 $ cat <<EOF >> log
755 > firstline
767 > firstline
756 > secondline
768 > secondline
757 > EOF
769 > EOF
758
770
759 Interrupted commit should not change state
771 Interrupted commit should not change state
760
772
761 $ hg commit
773 $ hg commit
762 abort: empty commit message
774 abort: empty commit message
763 [255]
775 [255]
764 $ hg status
776 $ hg status
765 M a
777 M a
766 ? c
778 ? c
767 ? log
779 ? log
768
780
769 Commit with multi-line message and custom expansion
781 Commit with multi-line message and custom expansion
770
782
771 $ hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
783 $ hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
772 committing files:
784 committing files:
773 a
785 a
774 committing manifest
786 committing manifest
775 committing changelog
787 committing changelog
776 overwriting a expanding keywords
788 overwriting a expanding keywords
777 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
789 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
778 $ rm log
790 $ rm log
779
791
780 Stat, verify and show custom expansion (firstline)
792 Stat, verify and show custom expansion (firstline)
781
793
782 $ hg status
794 $ hg status
783 ? c
795 ? c
784 $ hg verify
796 $ hg verify
785 checking changesets
797 checking changesets
786 checking manifests
798 checking manifests
787 crosschecking files in changesets and manifests
799 crosschecking files in changesets and manifests
788 checking files
800 checking files
789 3 files, 3 changesets, 4 total revisions
801 3 files, 3 changesets, 4 total revisions
790 $ cat a b
802 $ cat a b
791 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
803 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
792 do not process $Id:
804 do not process $Id:
793 xxx $
805 xxx $
794 $Xinfo: User Name <user@example.com>: firstline $
806 $Xinfo: User Name <user@example.com>: firstline $
795 ignore $Id$
807 ignore $Id$
796 $ hg cat sym a b && echo
808 $ hg cat sym a b && echo
797 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
809 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
798 do not process $Id:
810 do not process $Id:
799 xxx $
811 xxx $
800 $Xinfo: User Name <user@example.com>: firstline $
812 $Xinfo: User Name <user@example.com>: firstline $
801 ignore $Id$
813 ignore $Id$
802 a
814 a
803
815
804 annotate
816 annotate
805
817
806 $ hg annotate a
818 $ hg annotate a
807 1: expand $Id$
819 1: expand $Id$
808 1: do not process $Id:
820 1: do not process $Id:
809 1: xxx $
821 1: xxx $
810 2: $Xinfo$
822 2: $Xinfo$
811
823
812 remove with status checks
824 remove with status checks
813
825
814 $ hg debugrebuildstate
826 $ hg debugrebuildstate
815 $ hg remove a
827 $ hg remove a
816 $ hg --debug commit -m rma
828 $ hg --debug commit -m rma
817 committing files:
829 committing files:
818 committing manifest
830 committing manifest
819 committing changelog
831 committing changelog
820 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
832 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
821 $ hg status
833 $ hg status
822 ? c
834 ? c
823
835
824 Rollback, revert, and check expansion
836 Rollback, revert, and check expansion
825
837
826 $ hg rollback
838 $ hg rollback
827 repository tip rolled back to revision 2 (undo commit)
839 repository tip rolled back to revision 2 (undo commit)
828 working directory now based on revision 2
840 working directory now based on revision 2
829 $ hg status
841 $ hg status
830 R a
842 R a
831 ? c
843 ? c
832 $ hg revert --no-backup --rev tip a
844 $ hg revert --no-backup --rev tip a
833 $ cat a
845 $ cat a
834 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
846 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
835 do not process $Id:
847 do not process $Id:
836 xxx $
848 xxx $
837 $Xinfo: User Name <user@example.com>: firstline $
849 $Xinfo: User Name <user@example.com>: firstline $
838
850
839 Clone to test global and local configurations
851 Clone to test global and local configurations
840
852
841 $ cd ..
853 $ cd ..
842
854
843 Expansion in destination with global configuration
855 Expansion in destination with global configuration
844
856
845 $ hg --quiet clone Test globalconf
857 $ hg --quiet clone Test globalconf
846 $ cat globalconf/a
858 $ cat globalconf/a
847 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
859 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
848 do not process $Id:
860 do not process $Id:
849 xxx $
861 xxx $
850 $Xinfo: User Name <user@example.com>: firstline $
862 $Xinfo: User Name <user@example.com>: firstline $
851
863
852 No expansion in destination with local configuration in origin only
864 No expansion in destination with local configuration in origin only
853
865
854 $ hg --quiet --config 'keyword.**=ignore' clone Test localconf
866 $ hg --quiet --config 'keyword.**=ignore' clone Test localconf
855 $ cat localconf/a
867 $ cat localconf/a
856 expand $Id$
868 expand $Id$
857 do not process $Id:
869 do not process $Id:
858 xxx $
870 xxx $
859 $Xinfo$
871 $Xinfo$
860
872
861 Clone to test incoming
873 Clone to test incoming
862
874
863 $ hg clone -r1 Test Test-a
875 $ hg clone -r1 Test Test-a
864 adding changesets
876 adding changesets
865 adding manifests
877 adding manifests
866 adding file changes
878 adding file changes
867 added 2 changesets with 3 changes to 3 files
879 added 2 changesets with 3 changes to 3 files
868 updating to branch default
880 updating to branch default
869 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
881 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
870 $ cd Test-a
882 $ cd Test-a
871 $ cat <<EOF >> .hg/hgrc
883 $ cat <<EOF >> .hg/hgrc
872 > [paths]
884 > [paths]
873 > default = ../Test
885 > default = ../Test
874 > EOF
886 > EOF
875 $ hg incoming
887 $ hg incoming
876 comparing with $TESTTMP/Test (glob)
888 comparing with $TESTTMP/Test (glob)
877 searching for changes
889 searching for changes
878 changeset: 2:bb948857c743
890 changeset: 2:bb948857c743
879 tag: tip
891 tag: tip
880 user: User Name <user@example.com>
892 user: User Name <user@example.com>
881 date: Thu Jan 01 00:00:02 1970 +0000
893 date: Thu Jan 01 00:00:02 1970 +0000
882 summary: firstline
894 summary: firstline
883
895
884 Imported patch should not be rejected
896 Imported patch should not be rejected
885
897
886 >>> import re
898 >>> import re
887 >>> text = re.sub(r'(Id.*)', r'\1 rejecttest', open('a').read())
899 >>> text = re.sub(r'(Id.*)', r'\1 rejecttest', open('a').read())
888 >>> open('a', 'wb').write(text)
900 >>> open('a', 'wb').write(text)
889 $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
901 $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
890 committing files:
902 committing files:
891 a
903 a
892 committing manifest
904 committing manifest
893 committing changelog
905 committing changelog
894 overwriting a expanding keywords
906 overwriting a expanding keywords
895 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
907 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
896 $ hg export -o ../rejecttest.diff tip
908 $ hg export -o ../rejecttest.diff tip
897 $ cd ../Test
909 $ cd ../Test
898 $ hg import ../rejecttest.diff
910 $ hg import ../rejecttest.diff
899 applying ../rejecttest.diff
911 applying ../rejecttest.diff
900 $ cat a b
912 $ cat a b
901 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
913 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
902 do not process $Id: rejecttest
914 do not process $Id: rejecttest
903 xxx $
915 xxx $
904 $Xinfo: User Name <user@example.com>: rejects? $
916 $Xinfo: User Name <user@example.com>: rejects? $
905 ignore $Id$
917 ignore $Id$
906
918
907 $ hg rollback
919 $ hg rollback
908 repository tip rolled back to revision 2 (undo import)
920 repository tip rolled back to revision 2 (undo import)
909 working directory now based on revision 2
921 working directory now based on revision 2
910 $ hg update --clean
922 $ hg update --clean
911 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
923 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
912
924
913 kwexpand/kwshrink on selected files
925 kwexpand/kwshrink on selected files
914
926
915 $ mkdir x
927 $ mkdir x
916 $ hg copy a x/a
928 $ hg copy a x/a
917 $ hg --verbose kwshrink a
929 $ hg --verbose kwshrink a
918 overwriting a shrinking keywords
930 overwriting a shrinking keywords
919 - sleep required for dirstate.normal() check
931 - sleep required for dirstate.normal() check
920 $ sleep 1
932 $ sleep 1
921 $ hg status a
933 $ hg status a
922 $ hg --verbose kwexpand a
934 $ hg --verbose kwexpand a
923 overwriting a expanding keywords
935 overwriting a expanding keywords
924 $ hg status a
936 $ hg status a
925
937
926 kwexpand x/a should abort
938 kwexpand x/a should abort
927
939
928 $ hg --verbose kwexpand x/a
940 $ hg --verbose kwexpand x/a
929 abort: outstanding uncommitted changes
941 abort: outstanding uncommitted changes
930 [255]
942 [255]
931 $ cd x
943 $ cd x
932 $ hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
944 $ hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
933 committing files:
945 committing files:
934 x/a
946 x/a
935 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
947 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
936 committing manifest
948 committing manifest
937 committing changelog
949 committing changelog
938 overwriting x/a expanding keywords
950 overwriting x/a expanding keywords
939 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
951 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
940 $ cat a
952 $ cat a
941 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
953 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
942 do not process $Id:
954 do not process $Id:
943 xxx $
955 xxx $
944 $Xinfo: User Name <user@example.com>: xa $
956 $Xinfo: User Name <user@example.com>: xa $
945
957
946 kwshrink a inside directory x
958 kwshrink a inside directory x
947
959
948 $ hg --verbose kwshrink a
960 $ hg --verbose kwshrink a
949 overwriting x/a shrinking keywords
961 overwriting x/a shrinking keywords
950 $ cat a
962 $ cat a
951 expand $Id$
963 expand $Id$
952 do not process $Id:
964 do not process $Id:
953 xxx $
965 xxx $
954 $Xinfo$
966 $Xinfo$
955 $ cd ..
967 $ cd ..
956
968
957 kwexpand nonexistent
969 kwexpand nonexistent
958
970
959 $ hg kwexpand nonexistent
971 $ hg kwexpand nonexistent
960 nonexistent:* (glob)
972 nonexistent:* (glob)
961
973
962
974
963 #if serve
975 #if serve
964 hg serve
976 hg serve
965 - expand with hgweb file
977 - expand with hgweb file
966 - no expansion with hgweb annotate/changeset/filediff
978 - no expansion with hgweb annotate/changeset/filediff
967 - check errors
979 - check errors
968
980
969 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
981 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
970 $ cat hg.pid >> $DAEMON_PIDS
982 $ cat hg.pid >> $DAEMON_PIDS
971 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/a/?style=raw'
983 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/a/?style=raw'
972 200 Script output follows
984 200 Script output follows
973
985
974 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
986 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
975 do not process $Id:
987 do not process $Id:
976 xxx $
988 xxx $
977 $Xinfo: User Name <user@example.com>: firstline $
989 $Xinfo: User Name <user@example.com>: firstline $
978 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/a/?style=raw'
990 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/a/?style=raw'
979 200 Script output follows
991 200 Script output follows
980
992
981
993
982 user@1: expand $Id$
994 user@1: expand $Id$
983 user@1: do not process $Id:
995 user@1: do not process $Id:
984 user@1: xxx $
996 user@1: xxx $
985 user@2: $Xinfo$
997 user@2: $Xinfo$
986
998
987
999
988
1000
989
1001
990 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip/?style=raw'
1002 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip/?style=raw'
991 200 Script output follows
1003 200 Script output follows
992
1004
993
1005
994 # HG changeset patch
1006 # HG changeset patch
995 # User User Name <user@example.com>
1007 # User User Name <user@example.com>
996 # Date 3 0
1008 # Date 3 0
997 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
1009 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
998 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
1010 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
999 xa
1011 xa
1000
1012
1001 diff -r bb948857c743 -r b4560182a3f9 x/a
1013 diff -r bb948857c743 -r b4560182a3f9 x/a
1002 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1014 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1003 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
1015 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
1004 @@ -0,0 +1,4 @@
1016 @@ -0,0 +1,4 @@
1005 +expand $Id$
1017 +expand $Id$
1006 +do not process $Id:
1018 +do not process $Id:
1007 +xxx $
1019 +xxx $
1008 +$Xinfo$
1020 +$Xinfo$
1009
1021
1010 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/bb948857c743/a?style=raw'
1022 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/bb948857c743/a?style=raw'
1011 200 Script output follows
1023 200 Script output follows
1012
1024
1013
1025
1014 diff -r ef63ca68695b -r bb948857c743 a
1026 diff -r ef63ca68695b -r bb948857c743 a
1015 --- a/a Thu Jan 01 00:00:00 1970 +0000
1027 --- a/a Thu Jan 01 00:00:00 1970 +0000
1016 +++ b/a Thu Jan 01 00:00:02 1970 +0000
1028 +++ b/a Thu Jan 01 00:00:02 1970 +0000
1017 @@ -1,3 +1,4 @@
1029 @@ -1,3 +1,4 @@
1018 expand $Id$
1030 expand $Id$
1019 do not process $Id:
1031 do not process $Id:
1020 xxx $
1032 xxx $
1021 +$Xinfo$
1033 +$Xinfo$
1022
1034
1023
1035
1024
1036
1025
1037
1026 $ cat errors.log
1038 $ cat errors.log
1027 #endif
1039 #endif
1028
1040
1029 Prepare merge and resolve tests
1041 Prepare merge and resolve tests
1030
1042
1031 $ echo '$Id$' > m
1043 $ echo '$Id$' > m
1032 $ hg add m
1044 $ hg add m
1033 $ hg commit -m 4kw
1045 $ hg commit -m 4kw
1034 $ echo foo >> m
1046 $ echo foo >> m
1035 $ hg commit -m 5foo
1047 $ hg commit -m 5foo
1036
1048
1037 simplemerge
1049 simplemerge
1038
1050
1039 $ hg update 4
1051 $ hg update 4
1040 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1052 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1041 $ echo foo >> m
1053 $ echo foo >> m
1042 $ hg commit -m 6foo
1054 $ hg commit -m 6foo
1043 created new head
1055 created new head
1044 $ hg merge
1056 $ hg merge
1045 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1057 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1046 (branch merge, don't forget to commit)
1058 (branch merge, don't forget to commit)
1047 $ hg commit -m simplemerge
1059 $ hg commit -m simplemerge
1048 $ cat m
1060 $ cat m
1049 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
1061 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
1050 foo
1062 foo
1051
1063
1052 conflict: keyword should stay outside conflict zone
1064 conflict: keyword should stay outside conflict zone
1053
1065
1054 $ hg update 4
1066 $ hg update 4
1055 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1067 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1056 $ echo bar >> m
1068 $ echo bar >> m
1057 $ hg commit -m 8bar
1069 $ hg commit -m 8bar
1058 created new head
1070 created new head
1059 $ hg merge
1071 $ hg merge
1060 merging m
1072 merging m
1061 warning: conflicts during merge.
1073 warning: conflicts during merge.
1062 merging m incomplete! (edit conflicts, then use 'hg resolve --mark')
1074 merging m incomplete! (edit conflicts, then use 'hg resolve --mark')
1063 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1075 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1064 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1076 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1065 [1]
1077 [1]
1066 $ cat m
1078 $ cat m
1067 $Id$
1079 $Id$
1068 <<<<<<< local: 88a80c8d172e - test: 8bar
1080 <<<<<<< local: 88a80c8d172e - test: 8bar
1069 bar
1081 bar
1070 =======
1082 =======
1071 foo
1083 foo
1072 >>>>>>> other: 85d2d2d732a5 - test: simplemerge
1084 >>>>>>> other: 85d2d2d732a5 - test: simplemerge
1073
1085
1074 resolve to local, m must contain hash of last change (local parent)
1086 resolve to local, m must contain hash of last change (local parent)
1075
1087
1076 $ hg resolve -t internal:local -a
1088 $ hg resolve -t internal:local -a
1077 (no more unresolved files)
1089 (no more unresolved files)
1078 $ hg commit -m localresolve
1090 $ hg commit -m localresolve
1079 $ cat m
1091 $ cat m
1080 $Id: m 88a80c8d172e Thu, 01 Jan 1970 00:00:00 +0000 test $
1092 $Id: m 88a80c8d172e Thu, 01 Jan 1970 00:00:00 +0000 test $
1081 bar
1093 bar
1082
1094
1083 Test restricted mode with transplant -b
1095 Test restricted mode with transplant -b
1084
1096
1085 $ hg update 6
1097 $ hg update 6
1086 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1098 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1087 $ hg branch foo
1099 $ hg branch foo
1088 marked working directory as branch foo
1100 marked working directory as branch foo
1089 (branches are permanent and global, did you want a bookmark?)
1101 (branches are permanent and global, did you want a bookmark?)
1090 $ mv a a.bak
1102 $ mv a a.bak
1091 $ echo foobranch > a
1103 $ echo foobranch > a
1092 $ cat a.bak >> a
1104 $ cat a.bak >> a
1093 $ rm a.bak
1105 $ rm a.bak
1094 $ hg commit -m 9foobranch
1106 $ hg commit -m 9foobranch
1095 $ hg update default
1107 $ hg update default
1096 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1108 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1097 $ hg -y transplant -b foo tip
1109 $ hg -y transplant -b foo tip
1098 applying 4aa30d025d50
1110 applying 4aa30d025d50
1099 4aa30d025d50 transplanted to e00abbf63521
1111 4aa30d025d50 transplanted to e00abbf63521
1100
1112
1101 Expansion in changeset but not in file
1113 Expansion in changeset but not in file
1102
1114
1103 $ hg tip -p
1115 $ hg tip -p
1104 changeset: 11:e00abbf63521
1116 changeset: 11:e00abbf63521
1105 tag: tip
1117 tag: tip
1106 parent: 9:800511b3a22d
1118 parent: 9:800511b3a22d
1107 user: test
1119 user: test
1108 date: Thu Jan 01 00:00:00 1970 +0000
1120 date: Thu Jan 01 00:00:00 1970 +0000
1109 summary: 9foobranch
1121 summary: 9foobranch
1110
1122
1111 diff -r 800511b3a22d -r e00abbf63521 a
1123 diff -r 800511b3a22d -r e00abbf63521 a
1112 --- a/a Thu Jan 01 00:00:00 1970 +0000
1124 --- a/a Thu Jan 01 00:00:00 1970 +0000
1113 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1125 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1114 @@ -1,3 +1,4 @@
1126 @@ -1,3 +1,4 @@
1115 +foobranch
1127 +foobranch
1116 expand $Id$
1128 expand $Id$
1117 do not process $Id:
1129 do not process $Id:
1118 xxx $
1130 xxx $
1119
1131
1120 $ head -n 2 a
1132 $ head -n 2 a
1121 foobranch
1133 foobranch
1122 expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $
1134 expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $
1123
1135
1124 Turn off expansion
1136 Turn off expansion
1125
1137
1126 $ hg -q rollback
1138 $ hg -q rollback
1127 $ hg -q update -C
1139 $ hg -q update -C
1128
1140
1129 kwshrink with unknown file u
1141 kwshrink with unknown file u
1130
1142
1131 $ cp a u
1143 $ cp a u
1132 $ hg --verbose kwshrink
1144 $ hg --verbose kwshrink
1133 overwriting a shrinking keywords
1145 overwriting a shrinking keywords
1134 overwriting m shrinking keywords
1146 overwriting m shrinking keywords
1135 overwriting x/a shrinking keywords
1147 overwriting x/a shrinking keywords
1136
1148
1137 Keywords shrunk in working directory, but not yet disabled
1149 Keywords shrunk in working directory, but not yet disabled
1138 - cat shows unexpanded keywords
1150 - cat shows unexpanded keywords
1139 - hg cat shows expanded keywords
1151 - hg cat shows expanded keywords
1140
1152
1141 $ cat a b
1153 $ cat a b
1142 expand $Id$
1154 expand $Id$
1143 do not process $Id:
1155 do not process $Id:
1144 xxx $
1156 xxx $
1145 $Xinfo$
1157 $Xinfo$
1146 ignore $Id$
1158 ignore $Id$
1147 $ hg cat sym a b && echo
1159 $ hg cat sym a b && echo
1148 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
1160 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
1149 do not process $Id:
1161 do not process $Id:
1150 xxx $
1162 xxx $
1151 $Xinfo: User Name <user@example.com>: firstline $
1163 $Xinfo: User Name <user@example.com>: firstline $
1152 ignore $Id$
1164 ignore $Id$
1153 a
1165 a
1154
1166
1155 Now disable keyword expansion
1167 Now disable keyword expansion
1156
1168
1157 $ cp $HGRCPATH $HGRCPATH.backup
1169 $ cp $HGRCPATH $HGRCPATH.backup
1158 $ rm "$HGRCPATH"
1170 $ rm "$HGRCPATH"
1159 $ cat a b
1171 $ cat a b
1160 expand $Id$
1172 expand $Id$
1161 do not process $Id:
1173 do not process $Id:
1162 xxx $
1174 xxx $
1163 $Xinfo$
1175 $Xinfo$
1164 ignore $Id$
1176 ignore $Id$
1165 $ hg cat sym a b && echo
1177 $ hg cat sym a b && echo
1166 expand $Id$
1178 expand $Id$
1167 do not process $Id:
1179 do not process $Id:
1168 xxx $
1180 xxx $
1169 $Xinfo$
1181 $Xinfo$
1170 ignore $Id$
1182 ignore $Id$
1171 a
1183 a
1172
1184
1173 enable keyword expansion again
1185 enable keyword expansion again
1174
1186
1175 $ cat $HGRCPATH.backup >> $HGRCPATH
1187 $ cat $HGRCPATH.backup >> $HGRCPATH
1176
1188
1177 Test restricted mode with unshelve
1189 Test restricted mode with unshelve
1178
1190
1179 $ cat <<EOF >> $HGRCPATH
1191 $ cat <<EOF >> $HGRCPATH
1180 > [extensions]
1192 > [extensions]
1181 > shelve =
1193 > shelve =
1182 > EOF
1194 > EOF
1183
1195
1184 $ echo xxxx >> a
1196 $ echo xxxx >> a
1185 $ hg diff
1197 $ hg diff
1186 diff -r 800511b3a22d a
1198 diff -r 800511b3a22d a
1187 --- a/a Thu Jan 01 00:00:00 1970 +0000
1199 --- a/a Thu Jan 01 00:00:00 1970 +0000
1188 +++ b/a * (glob)
1200 +++ b/a * (glob)
1189 @@ -2,3 +2,4 @@
1201 @@ -2,3 +2,4 @@
1190 do not process $Id:
1202 do not process $Id:
1191 xxx $
1203 xxx $
1192 $Xinfo$
1204 $Xinfo$
1193 +xxxx
1205 +xxxx
1194 $ hg shelve -q --name tmp
1206 $ hg shelve -q --name tmp
1195 $ hg shelve --list --patch
1207 $ hg shelve --list --patch
1196 tmp (*) changes to 'localresolve' (glob)
1208 tmp (*) changes to 'localresolve' (glob)
1197
1209
1198 diff --git a/a b/a
1210 diff --git a/a b/a
1199 --- a/a
1211 --- a/a
1200 +++ b/a
1212 +++ b/a
1201 @@ -2,3 +2,4 @@
1213 @@ -2,3 +2,4 @@
1202 do not process $Id:
1214 do not process $Id:
1203 xxx $
1215 xxx $
1204 $Xinfo$
1216 $Xinfo$
1205 +xxxx
1217 +xxxx
1206
1218
1207 $ hg update -q -C 10
1219 $ hg update -q -C 10
1208 $ hg unshelve -q tmp
1220 $ hg unshelve -q tmp
1209 $ hg diff
1221 $ hg diff
1210 diff -r 4aa30d025d50 a
1222 diff -r 4aa30d025d50 a
1211 --- a/a Thu Jan 01 00:00:00 1970 +0000
1223 --- a/a Thu Jan 01 00:00:00 1970 +0000
1212 +++ b/a * (glob)
1224 +++ b/a * (glob)
1213 @@ -3,3 +3,4 @@
1225 @@ -3,3 +3,4 @@
1214 do not process $Id:
1226 do not process $Id:
1215 xxx $
1227 xxx $
1216 $Xinfo$
1228 $Xinfo$
1217 +xxxx
1229 +xxxx
1218
1230
1219 Test restricted mode with rebase
1231 Test restricted mode with rebase
1220
1232
1221 $ cat <<EOF >> $HGRCPATH
1233 $ cat <<EOF >> $HGRCPATH
1222 > [extensions]
1234 > [extensions]
1223 > rebase =
1235 > rebase =
1224 > EOF
1236 > EOF
1225
1237
1226 $ hg update -q -C 9
1238 $ hg update -q -C 9
1227
1239
1228 $ echo xxxx >> a
1240 $ echo xxxx >> a
1229 $ hg commit -m '#11'
1241 $ hg commit -m '#11'
1230 $ hg diff -c 11
1242 $ hg diff -c 11
1231 diff -r 800511b3a22d -r b07670694489 a
1243 diff -r 800511b3a22d -r b07670694489 a
1232 --- a/a Thu Jan 01 00:00:00 1970 +0000
1244 --- a/a Thu Jan 01 00:00:00 1970 +0000
1233 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1245 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1234 @@ -2,3 +2,4 @@
1246 @@ -2,3 +2,4 @@
1235 do not process $Id:
1247 do not process $Id:
1236 xxx $
1248 xxx $
1237 $Xinfo$
1249 $Xinfo$
1238 +xxxx
1250 +xxxx
1239
1251
1240 $ hg diff -c 10
1252 $ hg diff -c 10
1241 diff -r 27d48ee14f67 -r 4aa30d025d50 a
1253 diff -r 27d48ee14f67 -r 4aa30d025d50 a
1242 --- a/a Thu Jan 01 00:00:00 1970 +0000
1254 --- a/a Thu Jan 01 00:00:00 1970 +0000
1243 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1255 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1244 @@ -1,3 +1,4 @@
1256 @@ -1,3 +1,4 @@
1245 +foobranch
1257 +foobranch
1246 expand $Id$
1258 expand $Id$
1247 do not process $Id:
1259 do not process $Id:
1248 xxx $
1260 xxx $
1249
1261
1250 $ hg rebase -q -s 10 -d 11 --keep
1262 $ hg rebase -q -s 10 -d 11 --keep
1251 $ hg diff -r 9 -r 12 a
1263 $ hg diff -r 9 -r 12 a
1252 diff -r 800511b3a22d -r 1939b927726c a
1264 diff -r 800511b3a22d -r 1939b927726c a
1253 --- a/a Thu Jan 01 00:00:00 1970 +0000
1265 --- a/a Thu Jan 01 00:00:00 1970 +0000
1254 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1266 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1255 @@ -1,4 +1,6 @@
1267 @@ -1,4 +1,6 @@
1256 +foobranch
1268 +foobranch
1257 expand $Id$
1269 expand $Id$
1258 do not process $Id:
1270 do not process $Id:
1259 xxx $
1271 xxx $
1260 $Xinfo$
1272 $Xinfo$
1261 +xxxx
1273 +xxxx
1262
1274
1263 Test restricted mode with graft
1275 Test restricted mode with graft
1264
1276
1265 $ hg graft -q 10
1277 $ hg graft -q 10
1266 $ hg diff -r 9 -r 13 a
1278 $ hg diff -r 9 -r 13 a
1267 diff -r 800511b3a22d -r 01a68de1003a a
1279 diff -r 800511b3a22d -r 01a68de1003a a
1268 --- a/a Thu Jan 01 00:00:00 1970 +0000
1280 --- a/a Thu Jan 01 00:00:00 1970 +0000
1269 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1281 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1270 @@ -1,4 +1,6 @@
1282 @@ -1,4 +1,6 @@
1271 +foobranch
1283 +foobranch
1272 expand $Id$
1284 expand $Id$
1273 do not process $Id:
1285 do not process $Id:
1274 xxx $
1286 xxx $
1275 $Xinfo$
1287 $Xinfo$
1276 +xxxx
1288 +xxxx
1277
1289
1278 Test restricted mode with backout
1290 Test restricted mode with backout
1279
1291
1280 $ hg backout -q 11
1292 $ hg backout -q 11
1281 $ hg diff a
1293 $ hg diff a
1282 diff -r 01a68de1003a a
1294 diff -r 01a68de1003a a
1283 --- a/a Thu Jan 01 00:00:00 1970 +0000
1295 --- a/a Thu Jan 01 00:00:00 1970 +0000
1284 +++ b/a * (glob)
1296 +++ b/a * (glob)
1285 @@ -3,4 +3,3 @@
1297 @@ -3,4 +3,3 @@
1286 do not process $Id:
1298 do not process $Id:
1287 xxx $
1299 xxx $
1288 $Xinfo$
1300 $Xinfo$
1289 -xxxx
1301 -xxxx
1290
1302
1291 Test restricted mode with histedit
1303 Test restricted mode with histedit
1292
1304
1293 $ cat <<EOF >> $HGRCPATH
1305 $ cat <<EOF >> $HGRCPATH
1294 > [extensions]
1306 > [extensions]
1295 > histedit =
1307 > histedit =
1296 > EOF
1308 > EOF
1297
1309
1298 $ hg commit -m 'backout #11'
1310 $ hg commit -m 'backout #11'
1299 $ hg histedit -q --command - 13 <<EOF
1311 $ hg histedit -q --command - 13 <<EOF
1300 > pick 49f5f2d940c3 14 backout #11
1312 > pick 49f5f2d940c3 14 backout #11
1301 > pick 01a68de1003a 13 9foobranch
1313 > pick 01a68de1003a 13 9foobranch
1302 > EOF
1314 > EOF
1303
1315
1304 Test restricted mode with fetch (with merge)
1316 Test restricted mode with fetch (with merge)
1305
1317
1306 $ cat <<EOF >> $HGRCPATH
1318 $ cat <<EOF >> $HGRCPATH
1307 > [extensions]
1319 > [extensions]
1308 > fetch =
1320 > fetch =
1309 > EOF
1321 > EOF
1310
1322
1311 $ hg clone -q -r 9 . ../fetch-merge
1323 $ hg clone -q -r 9 . ../fetch-merge
1312 $ cd ../fetch-merge
1324 $ cd ../fetch-merge
1313 $ hg -R ../Test export 10 | hg import -q -
1325 $ hg -R ../Test export 10 | hg import -q -
1314 $ hg fetch -q -r 11
1326 $ hg fetch -q -r 11
1315 $ hg diff -r 9 a
1327 $ hg diff -r 9 a
1316 diff -r 800511b3a22d a
1328 diff -r 800511b3a22d a
1317 --- a/a Thu Jan 01 00:00:00 1970 +0000
1329 --- a/a Thu Jan 01 00:00:00 1970 +0000
1318 +++ b/a * (glob)
1330 +++ b/a * (glob)
1319 @@ -1,4 +1,6 @@
1331 @@ -1,4 +1,6 @@
1320 +foobranch
1332 +foobranch
1321 expand $Id$
1333 expand $Id$
1322 do not process $Id:
1334 do not process $Id:
1323 xxx $
1335 xxx $
1324 $Xinfo$
1336 $Xinfo$
1325 +xxxx
1337 +xxxx
1326
1338
1327 $ cd ..
1339 $ cd ..
@@ -1,610 +1,620 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
298 abort: uncommitted changes in subrepository 'sub'
303 abort: uncommitted changes in subrepository 'sub'
299 [255]
304 [255]
300 % update substate when adding .hgsub w/clean updated subrepo
305 % update substate when adding .hgsub w/clean updated subrepo
301 A .hgsub
306 A .hgsub
302 % qrecord --config ui.interactive=1 -m0 0.diff
307 % qrecord --config ui.interactive=1 -m0 0.diff
303 diff --git a/.hgsub b/.hgsub
308 diff --git a/.hgsub b/.hgsub
304 new file mode 100644
309 new file mode 100644
305 examine changes to '.hgsub'? [Ynesfdaq?] y
310 examine changes to '.hgsub'? [Ynesfdaq?] y
306
311
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
307 path sub
317 path sub
308 source sub
318 source sub
309 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
319 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
310 $ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
320 $ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
311 > y
321 > y
312 > y
322 > y
313 > EOF
323 > EOF
314 adding a
324 adding a
315 % abort when modifying .hgsub w/dirty subrepo
325 % abort when modifying .hgsub w/dirty subrepo
316 M .hgsub
326 M .hgsub
317 A sub2/a
327 A sub2/a
318 % qrecord --config ui.interactive=1 -m1 1.diff
328 % qrecord --config ui.interactive=1 -m1 1.diff
319 diff --git a/.hgsub b/.hgsub
329 diff --git a/.hgsub b/.hgsub
320 1 hunks, 1 lines changed
330 1 hunks, 1 lines changed
321 examine changes to '.hgsub'? [Ynesfdaq?] y
331 examine changes to '.hgsub'? [Ynesfdaq?] y
322
332
323 @@ -1,1 +1,2 @@
333 @@ -1,1 +1,2 @@
324 sub = sub
334 sub = sub
325 +sub2 = sub2
335 +sub2 = sub2
326 record this change to '.hgsub'? [Ynesfdaq?] y
336 record this change to '.hgsub'? [Ynesfdaq?] y
327
337
328 abort: uncommitted changes in subrepository 'sub2'
338 abort: uncommitted changes in subrepository 'sub2'
329 [255]
339 [255]
330 % update substate when modifying .hgsub w/clean updated subrepo
340 % update substate when modifying .hgsub w/clean updated subrepo
331 M .hgsub
341 M .hgsub
332 % qrecord --config ui.interactive=1 -m1 1.diff
342 % qrecord --config ui.interactive=1 -m1 1.diff
333 diff --git a/.hgsub b/.hgsub
343 diff --git a/.hgsub b/.hgsub
334 1 hunks, 1 lines changed
344 1 hunks, 1 lines changed
335 examine changes to '.hgsub'? [Ynesfdaq?] y
345 examine changes to '.hgsub'? [Ynesfdaq?] y
336
346
337 @@ -1,1 +1,2 @@
347 @@ -1,1 +1,2 @@
338 sub = sub
348 sub = sub
339 +sub2 = sub2
349 +sub2 = sub2
340 record this change to '.hgsub'? [Ynesfdaq?] y
350 record this change to '.hgsub'? [Ynesfdaq?] y
341
351
342 path sub
352 path sub
343 source sub
353 source sub
344 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
354 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
345 path sub2
355 path sub2
346 source sub2
356 source sub2
347 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
357 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
348
358
349 $ hg qpop -qa
359 $ hg qpop -qa
350 patch queue now empty
360 patch queue now empty
351 $ testrm1 qrecord --config ui.interactive=1 -m2 2.diff <<EOF
361 $ testrm1 qrecord --config ui.interactive=1 -m2 2.diff <<EOF
352 > y
362 > y
353 > y
363 > y
354 > EOF
364 > EOF
355 adding a
365 adding a
356 % update substate when removing .hgsub w/dirty subrepo
366 % update substate when removing .hgsub w/dirty subrepo
357 M sub3/a
367 M sub3/a
358 R .hgsub
368 R .hgsub
359 % qrecord --config ui.interactive=1 -m2 2.diff
369 % qrecord --config ui.interactive=1 -m2 2.diff
360 diff --git a/.hgsub b/.hgsub
370 diff --git a/.hgsub b/.hgsub
361 deleted file mode 100644
371 deleted file mode 100644
362 examine changes to '.hgsub'? [Ynesfdaq?] y
372 examine changes to '.hgsub'? [Ynesfdaq?] y
363
373
364 % debugsub should be empty
374 % debugsub should be empty
365
375
366 $ hg qpop -qa
376 $ hg qpop -qa
367 patch queue now empty
377 patch queue now empty
368 $ testrm2 qrecord --config ui.interactive=1 -m3 3.diff <<EOF
378 $ testrm2 qrecord --config ui.interactive=1 -m3 3.diff <<EOF
369 > y
379 > y
370 > y
380 > y
371 > EOF
381 > EOF
372 adding a
382 adding a
373 % update substate when removing .hgsub w/clean updated subrepo
383 % update substate when removing .hgsub w/clean updated subrepo
374 R .hgsub
384 R .hgsub
375 % qrecord --config ui.interactive=1 -m3 3.diff
385 % qrecord --config ui.interactive=1 -m3 3.diff
376 diff --git a/.hgsub b/.hgsub
386 diff --git a/.hgsub b/.hgsub
377 deleted file mode 100644
387 deleted file mode 100644
378 examine changes to '.hgsub'? [Ynesfdaq?] y
388 examine changes to '.hgsub'? [Ynesfdaq?] y
379
389
380 % debugsub should be empty
390 % debugsub should be empty
381
391
382 $ cd ..
392 $ cd ..
383
393
384
394
385 correctly handle subrepos with patch queues
395 correctly handle subrepos with patch queues
386 $ mkrepo repo-subrepo-with-queue
396 $ mkrepo repo-subrepo-with-queue
387 $ mksubrepo sub
397 $ mksubrepo sub
388 adding a
398 adding a
389 $ hg -R sub qnew sub0.diff
399 $ hg -R sub qnew sub0.diff
390 $ echo sub = sub >> .hgsub
400 $ echo sub = sub >> .hgsub
391 $ hg add .hgsub
401 $ hg add .hgsub
392 $ hg qnew 0.diff
402 $ hg qnew 0.diff
393
403
394 $ cd ..
404 $ cd ..
395
405
396 check whether MQ operations can import updated .hgsubstate correctly
406 check whether MQ operations can import updated .hgsubstate correctly
397 both into 'revision' and 'patch file under .hg/patches':
407 both into 'revision' and 'patch file under .hg/patches':
398
408
399 $ hg init importing-hgsubstate
409 $ hg init importing-hgsubstate
400 $ cd importing-hgsubstate
410 $ cd importing-hgsubstate
401
411
402 $ echo a > a
412 $ echo a > a
403 $ hg commit -u test -d '0 0' -Am '#0 in parent'
413 $ hg commit -u test -d '0 0' -Am '#0 in parent'
404 adding a
414 adding a
405 $ hg init sub
415 $ hg init sub
406 $ echo sa > sub/sa
416 $ echo sa > sub/sa
407 $ hg -R sub commit -u test -d '0 0' -Am '#0 in sub'
417 $ hg -R sub commit -u test -d '0 0' -Am '#0 in sub'
408 adding sa
418 adding sa
409 $ echo 'sub = sub' > .hgsub
419 $ echo 'sub = sub' > .hgsub
410 $ touch .hgsubstate
420 $ touch .hgsubstate
411 $ hg add .hgsub .hgsubstate
421 $ hg add .hgsub .hgsubstate
412
422
413 $ hg qnew -u test -d '0 0' import-at-qnew
423 $ hg qnew -u test -d '0 0' import-at-qnew
414 $ hg -R sub parents --template '{node} sub\n'
424 $ hg -R sub parents --template '{node} sub\n'
415 b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
425 b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
416 $ cat .hgsubstate
426 $ cat .hgsubstate
417 b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
427 b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
418 $ hg diff -c tip
428 $ hg diff -c tip
419 diff -r f499373e340c -r f69e96d86e75 .hgsub
429 diff -r f499373e340c -r f69e96d86e75 .hgsub
420 --- /dev/null
430 --- /dev/null
421 +++ b/.hgsub
431 +++ b/.hgsub
422 @@ -0,0 +1,1 @@
432 @@ -0,0 +1,1 @@
423 +sub = sub
433 +sub = sub
424 diff -r f499373e340c -r f69e96d86e75 .hgsubstate
434 diff -r f499373e340c -r f69e96d86e75 .hgsubstate
425 --- /dev/null
435 --- /dev/null
426 +++ b/.hgsubstate
436 +++ b/.hgsubstate
427 @@ -0,0 +1,1 @@
437 @@ -0,0 +1,1 @@
428 +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
438 +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
429 $ cat .hg/patches/import-at-qnew
439 $ cat .hg/patches/import-at-qnew
430 # HG changeset patch
440 # HG changeset patch
431 # User test
441 # User test
432 # Date 0 0
442 # Date 0 0
433 # Parent f499373e340cdca5d01dee904aeb42dd2a325e71
443 # Parent f499373e340cdca5d01dee904aeb42dd2a325e71
434
444
435 diff -r f499373e340c -r f69e96d86e75 .hgsub
445 diff -r f499373e340c -r f69e96d86e75 .hgsub
436 --- /dev/null
446 --- /dev/null
437 +++ b/.hgsub
447 +++ b/.hgsub
438 @@ -0,0 +1,1 @@
448 @@ -0,0 +1,1 @@
439 +sub = sub
449 +sub = sub
440 diff -r f499373e340c -r f69e96d86e75 .hgsubstate
450 diff -r f499373e340c -r f69e96d86e75 .hgsubstate
441 --- /dev/null
451 --- /dev/null
442 +++ b/.hgsubstate
452 +++ b/.hgsubstate
443 @@ -0,0 +1,1 @@
453 @@ -0,0 +1,1 @@
444 +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
454 +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
445 $ hg parents --template '{node}\n'
455 $ hg parents --template '{node}\n'
446 f69e96d86e75a6d4fd88285dc9697acb23951041
456 f69e96d86e75a6d4fd88285dc9697acb23951041
447 $ hg parents --template '{files}\n'
457 $ hg parents --template '{files}\n'
448 .hgsub .hgsubstate
458 .hgsub .hgsubstate
449
459
450 check also whether qnew not including ".hgsubstate" explicitly causes
460 check also whether qnew not including ".hgsubstate" explicitly causes
451 as same result (in node hash) as one including it.
461 as same result (in node hash) as one including it.
452
462
453 $ hg qpop -a -q
463 $ hg qpop -a -q
454 patch queue now empty
464 patch queue now empty
455 $ hg qdelete import-at-qnew
465 $ hg qdelete import-at-qnew
456 $ echo 'sub = sub' > .hgsub
466 $ echo 'sub = sub' > .hgsub
457 $ hg add .hgsub
467 $ hg add .hgsub
458 $ rm -f .hgsubstate
468 $ rm -f .hgsubstate
459 $ hg qnew -u test -d '0 0' import-at-qnew
469 $ hg qnew -u test -d '0 0' import-at-qnew
460 $ hg parents --template '{node}\n'
470 $ hg parents --template '{node}\n'
461 f69e96d86e75a6d4fd88285dc9697acb23951041
471 f69e96d86e75a6d4fd88285dc9697acb23951041
462 $ hg parents --template '{files}\n'
472 $ hg parents --template '{files}\n'
463 .hgsub .hgsubstate
473 .hgsub .hgsubstate
464
474
465 check whether qrefresh imports updated .hgsubstate correctly
475 check whether qrefresh imports updated .hgsubstate correctly
466
476
467 $ hg qpop
477 $ hg qpop
468 popping import-at-qnew
478 popping import-at-qnew
469 patch queue now empty
479 patch queue now empty
470 $ hg qpush
480 $ hg qpush
471 applying import-at-qnew
481 applying import-at-qnew
472 now at: import-at-qnew
482 now at: import-at-qnew
473 $ hg parents --template '{files}\n'
483 $ hg parents --template '{files}\n'
474 .hgsub .hgsubstate
484 .hgsub .hgsubstate
475
485
476 $ hg qnew import-at-qrefresh
486 $ hg qnew import-at-qrefresh
477 $ echo sb > sub/sb
487 $ echo sb > sub/sb
478 $ hg -R sub commit -u test -d '0 0' -Am '#1 in sub'
488 $ hg -R sub commit -u test -d '0 0' -Am '#1 in sub'
479 adding sb
489 adding sb
480 $ hg qrefresh -u test -d '0 0'
490 $ hg qrefresh -u test -d '0 0'
481 $ hg -R sub parents --template '{node} sub\n'
491 $ hg -R sub parents --template '{node} sub\n'
482 88ac1bef5ed43b689d1d200b59886b675dec474b sub
492 88ac1bef5ed43b689d1d200b59886b675dec474b sub
483 $ cat .hgsubstate
493 $ cat .hgsubstate
484 88ac1bef5ed43b689d1d200b59886b675dec474b sub
494 88ac1bef5ed43b689d1d200b59886b675dec474b sub
485 $ hg diff -c tip
495 $ hg diff -c tip
486 diff -r 05b056bb9c8c -r d987bec230f4 .hgsubstate
496 diff -r 05b056bb9c8c -r d987bec230f4 .hgsubstate
487 --- a/.hgsubstate
497 --- a/.hgsubstate
488 +++ b/.hgsubstate
498 +++ b/.hgsubstate
489 @@ -1,1 +1,1 @@
499 @@ -1,1 +1,1 @@
490 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
500 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
491 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
501 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
492 $ cat .hg/patches/import-at-qrefresh
502 $ cat .hg/patches/import-at-qrefresh
493 # HG changeset patch
503 # HG changeset patch
494 # User test
504 # User test
495 # Date 0 0
505 # Date 0 0
496 # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
506 # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
497
507
498 diff -r 05b056bb9c8c .hgsubstate
508 diff -r 05b056bb9c8c .hgsubstate
499 --- a/.hgsubstate
509 --- a/.hgsubstate
500 +++ b/.hgsubstate
510 +++ b/.hgsubstate
501 @@ -1,1 +1,1 @@
511 @@ -1,1 +1,1 @@
502 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
512 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
503 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
513 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
504 $ hg parents --template '{files}\n'
514 $ hg parents --template '{files}\n'
505 .hgsubstate
515 .hgsubstate
506
516
507 $ hg qrefresh -u test -d '0 0'
517 $ hg qrefresh -u test -d '0 0'
508 $ cat .hgsubstate
518 $ cat .hgsubstate
509 88ac1bef5ed43b689d1d200b59886b675dec474b sub
519 88ac1bef5ed43b689d1d200b59886b675dec474b sub
510 $ hg diff -c tip
520 $ hg diff -c tip
511 diff -r 05b056bb9c8c -r d987bec230f4 .hgsubstate
521 diff -r 05b056bb9c8c -r d987bec230f4 .hgsubstate
512 --- a/.hgsubstate
522 --- a/.hgsubstate
513 +++ b/.hgsubstate
523 +++ b/.hgsubstate
514 @@ -1,1 +1,1 @@
524 @@ -1,1 +1,1 @@
515 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
525 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
516 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
526 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
517 $ cat .hg/patches/import-at-qrefresh
527 $ cat .hg/patches/import-at-qrefresh
518 # HG changeset patch
528 # HG changeset patch
519 # User test
529 # User test
520 # Date 0 0
530 # Date 0 0
521 # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
531 # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
522
532
523 diff -r 05b056bb9c8c .hgsubstate
533 diff -r 05b056bb9c8c .hgsubstate
524 --- a/.hgsubstate
534 --- a/.hgsubstate
525 +++ b/.hgsubstate
535 +++ b/.hgsubstate
526 @@ -1,1 +1,1 @@
536 @@ -1,1 +1,1 @@
527 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
537 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
528 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
538 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
529 $ hg parents --template '{files}\n'
539 $ hg parents --template '{files}\n'
530 .hgsubstate
540 .hgsubstate
531
541
532 $ hg update -C tip
542 $ hg update -C tip
533 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
543 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
534 $ hg qpop -a
544 $ hg qpop -a
535 popping import-at-qrefresh
545 popping import-at-qrefresh
536 popping import-at-qnew
546 popping import-at-qnew
537 patch queue now empty
547 patch queue now empty
538
548
539 $ hg -R sub update -C 0
549 $ hg -R sub update -C 0
540 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
550 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
541 $ echo 'sub = sub' > .hgsub
551 $ echo 'sub = sub' > .hgsub
542 $ hg commit -Am '#1 in parent'
552 $ hg commit -Am '#1 in parent'
543 adding .hgsub
553 adding .hgsub
544 $ hg -R sub update -C 1
554 $ hg -R sub update -C 1
545 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
555 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
546 $ hg commit -Am '#2 in parent (but will be rolled back soon)'
556 $ hg commit -Am '#2 in parent (but will be rolled back soon)'
547 $ hg rollback
557 $ hg rollback
548 repository tip rolled back to revision 1 (undo commit)
558 repository tip rolled back to revision 1 (undo commit)
549 working directory now based on revision 1
559 working directory now based on revision 1
550 $ hg status
560 $ hg status
551 M .hgsubstate
561 M .hgsubstate
552 $ hg qnew -u test -d '0 0' checkstate-at-qnew
562 $ hg qnew -u test -d '0 0' checkstate-at-qnew
553 $ hg -R sub parents --template '{node} sub\n'
563 $ hg -R sub parents --template '{node} sub\n'
554 88ac1bef5ed43b689d1d200b59886b675dec474b sub
564 88ac1bef5ed43b689d1d200b59886b675dec474b sub
555 $ cat .hgsubstate
565 $ cat .hgsubstate
556 88ac1bef5ed43b689d1d200b59886b675dec474b sub
566 88ac1bef5ed43b689d1d200b59886b675dec474b sub
557 $ hg diff -c tip
567 $ hg diff -c tip
558 diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
568 diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
559 --- a/.hgsubstate
569 --- a/.hgsubstate
560 +++ b/.hgsubstate
570 +++ b/.hgsubstate
561 @@ -1,1 +1,1 @@
571 @@ -1,1 +1,1 @@
562 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
572 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
563 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
573 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
564 $ cat .hg/patches/checkstate-at-qnew
574 $ cat .hg/patches/checkstate-at-qnew
565 # HG changeset patch
575 # HG changeset patch
566 # User test
576 # User test
567 # Date 0 0
577 # Date 0 0
568 # Parent 4d91eb2fa1d1b22ec513347b9cd06f6b49d470fa
578 # Parent 4d91eb2fa1d1b22ec513347b9cd06f6b49d470fa
569
579
570 diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
580 diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
571 --- a/.hgsubstate
581 --- a/.hgsubstate
572 +++ b/.hgsubstate
582 +++ b/.hgsubstate
573 @@ -1,1 +1,1 @@
583 @@ -1,1 +1,1 @@
574 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
584 -b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
575 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
585 +88ac1bef5ed43b689d1d200b59886b675dec474b sub
576 $ hg parents --template '{files}\n'
586 $ hg parents --template '{files}\n'
577 .hgsubstate
587 .hgsubstate
578
588
579 check whether qrefresh not including ".hgsubstate" explicitly causes
589 check whether qrefresh not including ".hgsubstate" explicitly causes
580 as same result (in node hash) as one including it.
590 as same result (in node hash) as one including it.
581
591
582 $ hg update -C -q 0
592 $ hg update -C -q 0
583 $ hg qpop -a -q
593 $ hg qpop -a -q
584 patch queue now empty
594 patch queue now empty
585 $ hg qnew -u test -d '0 0' add-hgsub-at-qrefresh
595 $ hg qnew -u test -d '0 0' add-hgsub-at-qrefresh
586 $ echo 'sub = sub' > .hgsub
596 $ echo 'sub = sub' > .hgsub
587 $ echo > .hgsubstate
597 $ echo > .hgsubstate
588 $ hg add .hgsub .hgsubstate
598 $ hg add .hgsub .hgsubstate
589 $ hg qrefresh -u test -d '0 0'
599 $ hg qrefresh -u test -d '0 0'
590 $ hg parents --template '{node}\n'
600 $ hg parents --template '{node}\n'
591 7c48c35501aae6770ed9c2517014628615821a8e
601 7c48c35501aae6770ed9c2517014628615821a8e
592 $ hg parents --template '{files}\n'
602 $ hg parents --template '{files}\n'
593 .hgsub .hgsubstate
603 .hgsub .hgsubstate
594
604
595 $ hg qpop -a -q
605 $ hg qpop -a -q
596 patch queue now empty
606 patch queue now empty
597 $ hg qdelete add-hgsub-at-qrefresh
607 $ hg qdelete add-hgsub-at-qrefresh
598 $ hg qnew -u test -d '0 0' add-hgsub-at-qrefresh
608 $ hg qnew -u test -d '0 0' add-hgsub-at-qrefresh
599 $ echo 'sub = sub' > .hgsub
609 $ echo 'sub = sub' > .hgsub
600 $ hg add .hgsub
610 $ hg add .hgsub
601 $ rm -f .hgsubstate
611 $ rm -f .hgsubstate
602 $ hg qrefresh -u test -d '0 0'
612 $ hg qrefresh -u test -d '0 0'
603 $ hg parents --template '{node}\n'
613 $ hg parents --template '{node}\n'
604 7c48c35501aae6770ed9c2517014628615821a8e
614 7c48c35501aae6770ed9c2517014628615821a8e
605 $ hg parents --template '{files}\n'
615 $ hg parents --template '{files}\n'
606 .hgsub .hgsubstate
616 .hgsub .hgsubstate
607
617
608 $ cd ..
618 $ cd ..
609
619
610 $ cd ..
620 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now