##// END OF EJS Templates
remove: queue warnings until after status messages (issue5140) (API)...
timeless -
r28607:a88959ae default
parent child Browse files
Show More
@@ -1,3489 +1,3500 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import cStringIO
10 import cStringIO
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15 import tempfile
15 import tempfile
16
16
17 from .i18n import _
17 from .i18n import _
18 from .node import (
18 from .node import (
19 bin,
19 bin,
20 hex,
20 hex,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 short,
23 short,
24 )
24 )
25
25
26 from . import (
26 from . import (
27 bookmarks,
27 bookmarks,
28 changelog,
28 changelog,
29 copies,
29 copies,
30 crecord as crecordmod,
30 crecord as crecordmod,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 graphmod,
34 graphmod,
35 lock as lockmod,
35 lock as lockmod,
36 match as matchmod,
36 match as matchmod,
37 obsolete,
37 obsolete,
38 patch,
38 patch,
39 pathutil,
39 pathutil,
40 phases,
40 phases,
41 repair,
41 repair,
42 revlog,
42 revlog,
43 revset,
43 revset,
44 scmutil,
44 scmutil,
45 templatekw,
45 templatekw,
46 templater,
46 templater,
47 util,
47 util,
48 )
48 )
49
49
50 def ishunk(x):
50 def ishunk(x):
51 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
51 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
52 return isinstance(x, hunkclasses)
52 return isinstance(x, hunkclasses)
53
53
54 def newandmodified(chunks, originalchunks):
54 def newandmodified(chunks, originalchunks):
55 newlyaddedandmodifiedfiles = set()
55 newlyaddedandmodifiedfiles = set()
56 for chunk in chunks:
56 for chunk in chunks:
57 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
57 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
58 originalchunks:
58 originalchunks:
59 newlyaddedandmodifiedfiles.add(chunk.header.filename())
59 newlyaddedandmodifiedfiles.add(chunk.header.filename())
60 return newlyaddedandmodifiedfiles
60 return newlyaddedandmodifiedfiles
61
61
62 def parsealiases(cmd):
62 def parsealiases(cmd):
63 return cmd.lstrip("^").split("|")
63 return cmd.lstrip("^").split("|")
64
64
65 def setupwrapcolorwrite(ui):
65 def setupwrapcolorwrite(ui):
66 # wrap ui.write so diff output can be labeled/colorized
66 # wrap ui.write so diff output can be labeled/colorized
67 def wrapwrite(orig, *args, **kw):
67 def wrapwrite(orig, *args, **kw):
68 label = kw.pop('label', '')
68 label = kw.pop('label', '')
69 for chunk, l in patch.difflabel(lambda: args):
69 for chunk, l in patch.difflabel(lambda: args):
70 orig(chunk, label=label + l)
70 orig(chunk, label=label + l)
71
71
72 oldwrite = ui.write
72 oldwrite = ui.write
73 def wrap(*args, **kwargs):
73 def wrap(*args, **kwargs):
74 return wrapwrite(oldwrite, *args, **kwargs)
74 return wrapwrite(oldwrite, *args, **kwargs)
75 setattr(ui, 'write', wrap)
75 setattr(ui, 'write', wrap)
76 return oldwrite
76 return oldwrite
77
77
78 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
78 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
79 if usecurses:
79 if usecurses:
80 if testfile:
80 if testfile:
81 recordfn = crecordmod.testdecorator(testfile,
81 recordfn = crecordmod.testdecorator(testfile,
82 crecordmod.testchunkselector)
82 crecordmod.testchunkselector)
83 else:
83 else:
84 recordfn = crecordmod.chunkselector
84 recordfn = crecordmod.chunkselector
85
85
86 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
86 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
87
87
88 else:
88 else:
89 return patch.filterpatch(ui, originalhunks, operation)
89 return patch.filterpatch(ui, originalhunks, operation)
90
90
91 def recordfilter(ui, originalhunks, operation=None):
91 def recordfilter(ui, originalhunks, operation=None):
92 """ Prompts the user to filter the originalhunks and return a list of
92 """ Prompts the user to filter the originalhunks and return a list of
93 selected hunks.
93 selected hunks.
94 *operation* is used for ui purposes to indicate the user
94 *operation* is used for ui purposes to indicate the user
95 what kind of filtering they are doing: reverting, committing, shelving, etc.
95 what kind of filtering they are doing: reverting, committing, shelving, etc.
96 *operation* has to be a translated string.
96 *operation* has to be a translated string.
97 """
97 """
98 usecurses = crecordmod.checkcurses(ui)
98 usecurses = crecordmod.checkcurses(ui)
99 testfile = ui.config('experimental', 'crecordtest', None)
99 testfile = ui.config('experimental', 'crecordtest', None)
100 oldwrite = setupwrapcolorwrite(ui)
100 oldwrite = setupwrapcolorwrite(ui)
101 try:
101 try:
102 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
102 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
103 testfile, operation)
103 testfile, operation)
104 finally:
104 finally:
105 ui.write = oldwrite
105 ui.write = oldwrite
106 return newchunks, newopts
106 return newchunks, newopts
107
107
108 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
108 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
109 filterfn, *pats, **opts):
109 filterfn, *pats, **opts):
110 from . import merge as mergemod
110 from . import merge as mergemod
111 if not ui.interactive():
111 if not ui.interactive():
112 if cmdsuggest:
112 if cmdsuggest:
113 msg = _('running non-interactively, use %s instead') % cmdsuggest
113 msg = _('running non-interactively, use %s instead') % cmdsuggest
114 else:
114 else:
115 msg = _('running non-interactively')
115 msg = _('running non-interactively')
116 raise error.Abort(msg)
116 raise error.Abort(msg)
117
117
118 # make sure username is set before going interactive
118 # make sure username is set before going interactive
119 if not opts.get('user'):
119 if not opts.get('user'):
120 ui.username() # raise exception, username not provided
120 ui.username() # raise exception, username not provided
121
121
122 def recordfunc(ui, repo, message, match, opts):
122 def recordfunc(ui, repo, message, match, opts):
123 """This is generic record driver.
123 """This is generic record driver.
124
124
125 Its job is to interactively filter local changes, and
125 Its job is to interactively filter local changes, and
126 accordingly prepare working directory into a state in which the
126 accordingly prepare working directory into a state in which the
127 job can be delegated to a non-interactive commit command such as
127 job can be delegated to a non-interactive commit command such as
128 'commit' or 'qrefresh'.
128 'commit' or 'qrefresh'.
129
129
130 After the actual job is done by non-interactive command, the
130 After the actual job is done by non-interactive command, the
131 working directory is restored to its original state.
131 working directory is restored to its original state.
132
132
133 In the end we'll record interesting changes, and everything else
133 In the end we'll record interesting changes, and everything else
134 will be left in place, so the user can continue working.
134 will be left in place, so the user can continue working.
135 """
135 """
136
136
137 checkunfinished(repo, commit=True)
137 checkunfinished(repo, commit=True)
138 merge = len(repo[None].parents()) > 1
138 merge = len(repo[None].parents()) > 1
139 if merge:
139 if merge:
140 raise error.Abort(_('cannot partially commit a merge '
140 raise error.Abort(_('cannot partially commit a merge '
141 '(use "hg commit" instead)'))
141 '(use "hg commit" instead)'))
142
142
143 status = repo.status(match=match)
143 status = repo.status(match=match)
144 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
144 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
145 diffopts.nodates = True
145 diffopts.nodates = True
146 diffopts.git = True
146 diffopts.git = True
147 diffopts.showfunc = True
147 diffopts.showfunc = True
148 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
148 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
149 originalchunks = patch.parsepatch(originaldiff)
149 originalchunks = patch.parsepatch(originaldiff)
150
150
151 # 1. filter patch, since we are intending to apply subset of it
151 # 1. filter patch, since we are intending to apply subset of it
152 try:
152 try:
153 chunks, newopts = filterfn(ui, originalchunks)
153 chunks, newopts = filterfn(ui, originalchunks)
154 except patch.PatchError as err:
154 except patch.PatchError as err:
155 raise error.Abort(_('error parsing patch: %s') % err)
155 raise error.Abort(_('error parsing patch: %s') % err)
156 opts.update(newopts)
156 opts.update(newopts)
157
157
158 # We need to keep a backup of files that have been newly added and
158 # We need to keep a backup of files that have been newly added and
159 # modified during the recording process because there is a previous
159 # modified during the recording process because there is a previous
160 # version without the edit in the workdir
160 # version without the edit in the workdir
161 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
161 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
162 contenders = set()
162 contenders = set()
163 for h in chunks:
163 for h in chunks:
164 try:
164 try:
165 contenders.update(set(h.files()))
165 contenders.update(set(h.files()))
166 except AttributeError:
166 except AttributeError:
167 pass
167 pass
168
168
169 changed = status.modified + status.added + status.removed
169 changed = status.modified + status.added + status.removed
170 newfiles = [f for f in changed if f in contenders]
170 newfiles = [f for f in changed if f in contenders]
171 if not newfiles:
171 if not newfiles:
172 ui.status(_('no changes to record\n'))
172 ui.status(_('no changes to record\n'))
173 return 0
173 return 0
174
174
175 modified = set(status.modified)
175 modified = set(status.modified)
176
176
177 # 2. backup changed files, so we can restore them in the end
177 # 2. backup changed files, so we can restore them in the end
178
178
179 if backupall:
179 if backupall:
180 tobackup = changed
180 tobackup = changed
181 else:
181 else:
182 tobackup = [f for f in newfiles if f in modified or f in \
182 tobackup = [f for f in newfiles if f in modified or f in \
183 newlyaddedandmodifiedfiles]
183 newlyaddedandmodifiedfiles]
184 backups = {}
184 backups = {}
185 if tobackup:
185 if tobackup:
186 backupdir = repo.join('record-backups')
186 backupdir = repo.join('record-backups')
187 try:
187 try:
188 os.mkdir(backupdir)
188 os.mkdir(backupdir)
189 except OSError as err:
189 except OSError as err:
190 if err.errno != errno.EEXIST:
190 if err.errno != errno.EEXIST:
191 raise
191 raise
192 try:
192 try:
193 # backup continues
193 # backup continues
194 for f in tobackup:
194 for f in tobackup:
195 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
195 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
196 dir=backupdir)
196 dir=backupdir)
197 os.close(fd)
197 os.close(fd)
198 ui.debug('backup %r as %r\n' % (f, tmpname))
198 ui.debug('backup %r as %r\n' % (f, tmpname))
199 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
199 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
200 backups[f] = tmpname
200 backups[f] = tmpname
201
201
202 fp = cStringIO.StringIO()
202 fp = cStringIO.StringIO()
203 for c in chunks:
203 for c in chunks:
204 fname = c.filename()
204 fname = c.filename()
205 if fname in backups:
205 if fname in backups:
206 c.write(fp)
206 c.write(fp)
207 dopatch = fp.tell()
207 dopatch = fp.tell()
208 fp.seek(0)
208 fp.seek(0)
209
209
210 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
210 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
211 # 3a. apply filtered patch to clean repo (clean)
211 # 3a. apply filtered patch to clean repo (clean)
212 if backups:
212 if backups:
213 # Equivalent to hg.revert
213 # Equivalent to hg.revert
214 m = scmutil.matchfiles(repo, backups.keys())
214 m = scmutil.matchfiles(repo, backups.keys())
215 mergemod.update(repo, repo.dirstate.p1(),
215 mergemod.update(repo, repo.dirstate.p1(),
216 False, True, matcher=m)
216 False, True, matcher=m)
217
217
218 # 3b. (apply)
218 # 3b. (apply)
219 if dopatch:
219 if dopatch:
220 try:
220 try:
221 ui.debug('applying patch\n')
221 ui.debug('applying patch\n')
222 ui.debug(fp.getvalue())
222 ui.debug(fp.getvalue())
223 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
223 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
224 except patch.PatchError as err:
224 except patch.PatchError as err:
225 raise error.Abort(str(err))
225 raise error.Abort(str(err))
226 del fp
226 del fp
227
227
228 # 4. We prepared working directory according to filtered
228 # 4. We prepared working directory according to filtered
229 # patch. Now is the time to delegate the job to
229 # patch. Now is the time to delegate the job to
230 # commit/qrefresh or the like!
230 # commit/qrefresh or the like!
231
231
232 # Make all of the pathnames absolute.
232 # Make all of the pathnames absolute.
233 newfiles = [repo.wjoin(nf) for nf in newfiles]
233 newfiles = [repo.wjoin(nf) for nf in newfiles]
234 return commitfunc(ui, repo, *newfiles, **opts)
234 return commitfunc(ui, repo, *newfiles, **opts)
235 finally:
235 finally:
236 # 5. finally restore backed-up files
236 # 5. finally restore backed-up files
237 try:
237 try:
238 dirstate = repo.dirstate
238 dirstate = repo.dirstate
239 for realname, tmpname in backups.iteritems():
239 for realname, tmpname in backups.iteritems():
240 ui.debug('restoring %r to %r\n' % (tmpname, realname))
240 ui.debug('restoring %r to %r\n' % (tmpname, realname))
241
241
242 if dirstate[realname] == 'n':
242 if dirstate[realname] == 'n':
243 # without normallookup, restoring timestamp
243 # without normallookup, restoring timestamp
244 # may cause partially committed files
244 # may cause partially committed files
245 # to be treated as unmodified
245 # to be treated as unmodified
246 dirstate.normallookup(realname)
246 dirstate.normallookup(realname)
247
247
248 # copystat=True here and above are a hack to trick any
248 # copystat=True here and above are a hack to trick any
249 # editors that have f open that we haven't modified them.
249 # editors that have f open that we haven't modified them.
250 #
250 #
251 # Also note that this racy as an editor could notice the
251 # Also note that this racy as an editor could notice the
252 # file's mtime before we've finished writing it.
252 # file's mtime before we've finished writing it.
253 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
253 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
254 os.unlink(tmpname)
254 os.unlink(tmpname)
255 if tobackup:
255 if tobackup:
256 os.rmdir(backupdir)
256 os.rmdir(backupdir)
257 except OSError:
257 except OSError:
258 pass
258 pass
259
259
260 def recordinwlock(ui, repo, message, match, opts):
260 def recordinwlock(ui, repo, message, match, opts):
261 with repo.wlock():
261 with repo.wlock():
262 return recordfunc(ui, repo, message, match, opts)
262 return recordfunc(ui, repo, message, match, opts)
263
263
264 return commit(ui, repo, recordinwlock, pats, opts)
264 return commit(ui, repo, recordinwlock, pats, opts)
265
265
266 def findpossible(cmd, table, strict=False):
266 def findpossible(cmd, table, strict=False):
267 """
267 """
268 Return cmd -> (aliases, command table entry)
268 Return cmd -> (aliases, command table entry)
269 for each matching command.
269 for each matching command.
270 Return debug commands (or their aliases) only if no normal command matches.
270 Return debug commands (or their aliases) only if no normal command matches.
271 """
271 """
272 choice = {}
272 choice = {}
273 debugchoice = {}
273 debugchoice = {}
274
274
275 if cmd in table:
275 if cmd in table:
276 # short-circuit exact matches, "log" alias beats "^log|history"
276 # short-circuit exact matches, "log" alias beats "^log|history"
277 keys = [cmd]
277 keys = [cmd]
278 else:
278 else:
279 keys = table.keys()
279 keys = table.keys()
280
280
281 allcmds = []
281 allcmds = []
282 for e in keys:
282 for e in keys:
283 aliases = parsealiases(e)
283 aliases = parsealiases(e)
284 allcmds.extend(aliases)
284 allcmds.extend(aliases)
285 found = None
285 found = None
286 if cmd in aliases:
286 if cmd in aliases:
287 found = cmd
287 found = cmd
288 elif not strict:
288 elif not strict:
289 for a in aliases:
289 for a in aliases:
290 if a.startswith(cmd):
290 if a.startswith(cmd):
291 found = a
291 found = a
292 break
292 break
293 if found is not None:
293 if found is not None:
294 if aliases[0].startswith("debug") or found.startswith("debug"):
294 if aliases[0].startswith("debug") or found.startswith("debug"):
295 debugchoice[found] = (aliases, table[e])
295 debugchoice[found] = (aliases, table[e])
296 else:
296 else:
297 choice[found] = (aliases, table[e])
297 choice[found] = (aliases, table[e])
298
298
299 if not choice and debugchoice:
299 if not choice and debugchoice:
300 choice = debugchoice
300 choice = debugchoice
301
301
302 return choice, allcmds
302 return choice, allcmds
303
303
304 def findcmd(cmd, table, strict=True):
304 def findcmd(cmd, table, strict=True):
305 """Return (aliases, command table entry) for command string."""
305 """Return (aliases, command table entry) for command string."""
306 choice, allcmds = findpossible(cmd, table, strict)
306 choice, allcmds = findpossible(cmd, table, strict)
307
307
308 if cmd in choice:
308 if cmd in choice:
309 return choice[cmd]
309 return choice[cmd]
310
310
311 if len(choice) > 1:
311 if len(choice) > 1:
312 clist = choice.keys()
312 clist = choice.keys()
313 clist.sort()
313 clist.sort()
314 raise error.AmbiguousCommand(cmd, clist)
314 raise error.AmbiguousCommand(cmd, clist)
315
315
316 if choice:
316 if choice:
317 return choice.values()[0]
317 return choice.values()[0]
318
318
319 raise error.UnknownCommand(cmd, allcmds)
319 raise error.UnknownCommand(cmd, allcmds)
320
320
321 def findrepo(p):
321 def findrepo(p):
322 while not os.path.isdir(os.path.join(p, ".hg")):
322 while not os.path.isdir(os.path.join(p, ".hg")):
323 oldp, p = p, os.path.dirname(p)
323 oldp, p = p, os.path.dirname(p)
324 if p == oldp:
324 if p == oldp:
325 return None
325 return None
326
326
327 return p
327 return p
328
328
329 def bailifchanged(repo, merge=True):
329 def bailifchanged(repo, merge=True):
330 if merge and repo.dirstate.p2() != nullid:
330 if merge and repo.dirstate.p2() != nullid:
331 raise error.Abort(_('outstanding uncommitted merge'))
331 raise error.Abort(_('outstanding uncommitted merge'))
332 modified, added, removed, deleted = repo.status()[:4]
332 modified, added, removed, deleted = repo.status()[:4]
333 if modified or added or removed or deleted:
333 if modified or added or removed or deleted:
334 raise error.Abort(_('uncommitted changes'))
334 raise error.Abort(_('uncommitted changes'))
335 ctx = repo[None]
335 ctx = repo[None]
336 for s in sorted(ctx.substate):
336 for s in sorted(ctx.substate):
337 ctx.sub(s).bailifchanged()
337 ctx.sub(s).bailifchanged()
338
338
339 def logmessage(ui, opts):
339 def logmessage(ui, opts):
340 """ get the log message according to -m and -l option """
340 """ get the log message according to -m and -l option """
341 message = opts.get('message')
341 message = opts.get('message')
342 logfile = opts.get('logfile')
342 logfile = opts.get('logfile')
343
343
344 if message and logfile:
344 if message and logfile:
345 raise error.Abort(_('options --message and --logfile are mutually '
345 raise error.Abort(_('options --message and --logfile are mutually '
346 'exclusive'))
346 'exclusive'))
347 if not message and logfile:
347 if not message and logfile:
348 try:
348 try:
349 if logfile == '-':
349 if logfile == '-':
350 message = ui.fin.read()
350 message = ui.fin.read()
351 else:
351 else:
352 message = '\n'.join(util.readfile(logfile).splitlines())
352 message = '\n'.join(util.readfile(logfile).splitlines())
353 except IOError as inst:
353 except IOError as inst:
354 raise error.Abort(_("can't read commit message '%s': %s") %
354 raise error.Abort(_("can't read commit message '%s': %s") %
355 (logfile, inst.strerror))
355 (logfile, inst.strerror))
356 return message
356 return message
357
357
358 def mergeeditform(ctxorbool, baseformname):
358 def mergeeditform(ctxorbool, baseformname):
359 """return appropriate editform name (referencing a committemplate)
359 """return appropriate editform name (referencing a committemplate)
360
360
361 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
361 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
362 merging is committed.
362 merging is committed.
363
363
364 This returns baseformname with '.merge' appended if it is a merge,
364 This returns baseformname with '.merge' appended if it is a merge,
365 otherwise '.normal' is appended.
365 otherwise '.normal' is appended.
366 """
366 """
367 if isinstance(ctxorbool, bool):
367 if isinstance(ctxorbool, bool):
368 if ctxorbool:
368 if ctxorbool:
369 return baseformname + ".merge"
369 return baseformname + ".merge"
370 elif 1 < len(ctxorbool.parents()):
370 elif 1 < len(ctxorbool.parents()):
371 return baseformname + ".merge"
371 return baseformname + ".merge"
372
372
373 return baseformname + ".normal"
373 return baseformname + ".normal"
374
374
375 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
375 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
376 editform='', **opts):
376 editform='', **opts):
377 """get appropriate commit message editor according to '--edit' option
377 """get appropriate commit message editor according to '--edit' option
378
378
379 'finishdesc' is a function to be called with edited commit message
379 'finishdesc' is a function to be called with edited commit message
380 (= 'description' of the new changeset) just after editing, but
380 (= 'description' of the new changeset) just after editing, but
381 before checking empty-ness. It should return actual text to be
381 before checking empty-ness. It should return actual text to be
382 stored into history. This allows to change description before
382 stored into history. This allows to change description before
383 storing.
383 storing.
384
384
385 'extramsg' is a extra message to be shown in the editor instead of
385 'extramsg' is a extra message to be shown in the editor instead of
386 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
386 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
387 is automatically added.
387 is automatically added.
388
388
389 'editform' is a dot-separated list of names, to distinguish
389 'editform' is a dot-separated list of names, to distinguish
390 the purpose of commit text editing.
390 the purpose of commit text editing.
391
391
392 'getcommiteditor' returns 'commitforceeditor' regardless of
392 'getcommiteditor' returns 'commitforceeditor' regardless of
393 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
393 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
394 they are specific for usage in MQ.
394 they are specific for usage in MQ.
395 """
395 """
396 if edit or finishdesc or extramsg:
396 if edit or finishdesc or extramsg:
397 return lambda r, c, s: commitforceeditor(r, c, s,
397 return lambda r, c, s: commitforceeditor(r, c, s,
398 finishdesc=finishdesc,
398 finishdesc=finishdesc,
399 extramsg=extramsg,
399 extramsg=extramsg,
400 editform=editform)
400 editform=editform)
401 elif editform:
401 elif editform:
402 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
402 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
403 else:
403 else:
404 return commiteditor
404 return commiteditor
405
405
406 def loglimit(opts):
406 def loglimit(opts):
407 """get the log limit according to option -l/--limit"""
407 """get the log limit according to option -l/--limit"""
408 limit = opts.get('limit')
408 limit = opts.get('limit')
409 if limit:
409 if limit:
410 try:
410 try:
411 limit = int(limit)
411 limit = int(limit)
412 except ValueError:
412 except ValueError:
413 raise error.Abort(_('limit must be a positive integer'))
413 raise error.Abort(_('limit must be a positive integer'))
414 if limit <= 0:
414 if limit <= 0:
415 raise error.Abort(_('limit must be positive'))
415 raise error.Abort(_('limit must be positive'))
416 else:
416 else:
417 limit = None
417 limit = None
418 return limit
418 return limit
419
419
420 def makefilename(repo, pat, node, desc=None,
420 def makefilename(repo, pat, node, desc=None,
421 total=None, seqno=None, revwidth=None, pathname=None):
421 total=None, seqno=None, revwidth=None, pathname=None):
422 node_expander = {
422 node_expander = {
423 'H': lambda: hex(node),
423 'H': lambda: hex(node),
424 'R': lambda: str(repo.changelog.rev(node)),
424 'R': lambda: str(repo.changelog.rev(node)),
425 'h': lambda: short(node),
425 'h': lambda: short(node),
426 'm': lambda: re.sub('[^\w]', '_', str(desc))
426 'm': lambda: re.sub('[^\w]', '_', str(desc))
427 }
427 }
428 expander = {
428 expander = {
429 '%': lambda: '%',
429 '%': lambda: '%',
430 'b': lambda: os.path.basename(repo.root),
430 'b': lambda: os.path.basename(repo.root),
431 }
431 }
432
432
433 try:
433 try:
434 if node:
434 if node:
435 expander.update(node_expander)
435 expander.update(node_expander)
436 if node:
436 if node:
437 expander['r'] = (lambda:
437 expander['r'] = (lambda:
438 str(repo.changelog.rev(node)).zfill(revwidth or 0))
438 str(repo.changelog.rev(node)).zfill(revwidth or 0))
439 if total is not None:
439 if total is not None:
440 expander['N'] = lambda: str(total)
440 expander['N'] = lambda: str(total)
441 if seqno is not None:
441 if seqno is not None:
442 expander['n'] = lambda: str(seqno)
442 expander['n'] = lambda: str(seqno)
443 if total is not None and seqno is not None:
443 if total is not None and seqno is not None:
444 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
444 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
445 if pathname is not None:
445 if pathname is not None:
446 expander['s'] = lambda: os.path.basename(pathname)
446 expander['s'] = lambda: os.path.basename(pathname)
447 expander['d'] = lambda: os.path.dirname(pathname) or '.'
447 expander['d'] = lambda: os.path.dirname(pathname) or '.'
448 expander['p'] = lambda: pathname
448 expander['p'] = lambda: pathname
449
449
450 newname = []
450 newname = []
451 patlen = len(pat)
451 patlen = len(pat)
452 i = 0
452 i = 0
453 while i < patlen:
453 while i < patlen:
454 c = pat[i]
454 c = pat[i]
455 if c == '%':
455 if c == '%':
456 i += 1
456 i += 1
457 c = pat[i]
457 c = pat[i]
458 c = expander[c]()
458 c = expander[c]()
459 newname.append(c)
459 newname.append(c)
460 i += 1
460 i += 1
461 return ''.join(newname)
461 return ''.join(newname)
462 except KeyError as inst:
462 except KeyError as inst:
463 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
463 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
464 inst.args[0])
464 inst.args[0])
465
465
466 class _unclosablefile(object):
466 class _unclosablefile(object):
467 def __init__(self, fp):
467 def __init__(self, fp):
468 self._fp = fp
468 self._fp = fp
469
469
470 def close(self):
470 def close(self):
471 pass
471 pass
472
472
473 def __iter__(self):
473 def __iter__(self):
474 return iter(self._fp)
474 return iter(self._fp)
475
475
476 def __getattr__(self, attr):
476 def __getattr__(self, attr):
477 return getattr(self._fp, attr)
477 return getattr(self._fp, attr)
478
478
479 def makefileobj(repo, pat, node=None, desc=None, total=None,
479 def makefileobj(repo, pat, node=None, desc=None, total=None,
480 seqno=None, revwidth=None, mode='wb', modemap=None,
480 seqno=None, revwidth=None, mode='wb', modemap=None,
481 pathname=None):
481 pathname=None):
482
482
483 writable = mode not in ('r', 'rb')
483 writable = mode not in ('r', 'rb')
484
484
485 if not pat or pat == '-':
485 if not pat or pat == '-':
486 if writable:
486 if writable:
487 fp = repo.ui.fout
487 fp = repo.ui.fout
488 else:
488 else:
489 fp = repo.ui.fin
489 fp = repo.ui.fin
490 return _unclosablefile(fp)
490 return _unclosablefile(fp)
491 if util.safehasattr(pat, 'write') and writable:
491 if util.safehasattr(pat, 'write') and writable:
492 return pat
492 return pat
493 if util.safehasattr(pat, 'read') and 'r' in mode:
493 if util.safehasattr(pat, 'read') and 'r' in mode:
494 return pat
494 return pat
495 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
495 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
496 if modemap is not None:
496 if modemap is not None:
497 mode = modemap.get(fn, mode)
497 mode = modemap.get(fn, mode)
498 if mode == 'wb':
498 if mode == 'wb':
499 modemap[fn] = 'ab'
499 modemap[fn] = 'ab'
500 return open(fn, mode)
500 return open(fn, mode)
501
501
502 def openrevlog(repo, cmd, file_, opts):
502 def openrevlog(repo, cmd, file_, opts):
503 """opens the changelog, manifest, a filelog or a given revlog"""
503 """opens the changelog, manifest, a filelog or a given revlog"""
504 cl = opts['changelog']
504 cl = opts['changelog']
505 mf = opts['manifest']
505 mf = opts['manifest']
506 dir = opts['dir']
506 dir = opts['dir']
507 msg = None
507 msg = None
508 if cl and mf:
508 if cl and mf:
509 msg = _('cannot specify --changelog and --manifest at the same time')
509 msg = _('cannot specify --changelog and --manifest at the same time')
510 elif cl and dir:
510 elif cl and dir:
511 msg = _('cannot specify --changelog and --dir at the same time')
511 msg = _('cannot specify --changelog and --dir at the same time')
512 elif cl or mf:
512 elif cl or mf:
513 if file_:
513 if file_:
514 msg = _('cannot specify filename with --changelog or --manifest')
514 msg = _('cannot specify filename with --changelog or --manifest')
515 elif not repo:
515 elif not repo:
516 msg = _('cannot specify --changelog or --manifest or --dir '
516 msg = _('cannot specify --changelog or --manifest or --dir '
517 'without a repository')
517 'without a repository')
518 if msg:
518 if msg:
519 raise error.Abort(msg)
519 raise error.Abort(msg)
520
520
521 r = None
521 r = None
522 if repo:
522 if repo:
523 if cl:
523 if cl:
524 r = repo.unfiltered().changelog
524 r = repo.unfiltered().changelog
525 elif dir:
525 elif dir:
526 if 'treemanifest' not in repo.requirements:
526 if 'treemanifest' not in repo.requirements:
527 raise error.Abort(_("--dir can only be used on repos with "
527 raise error.Abort(_("--dir can only be used on repos with "
528 "treemanifest enabled"))
528 "treemanifest enabled"))
529 dirlog = repo.dirlog(file_)
529 dirlog = repo.dirlog(file_)
530 if len(dirlog):
530 if len(dirlog):
531 r = dirlog
531 r = dirlog
532 elif mf:
532 elif mf:
533 r = repo.manifest
533 r = repo.manifest
534 elif file_:
534 elif file_:
535 filelog = repo.file(file_)
535 filelog = repo.file(file_)
536 if len(filelog):
536 if len(filelog):
537 r = filelog
537 r = filelog
538 if not r:
538 if not r:
539 if not file_:
539 if not file_:
540 raise error.CommandError(cmd, _('invalid arguments'))
540 raise error.CommandError(cmd, _('invalid arguments'))
541 if not os.path.isfile(file_):
541 if not os.path.isfile(file_):
542 raise error.Abort(_("revlog '%s' not found") % file_)
542 raise error.Abort(_("revlog '%s' not found") % file_)
543 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
543 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
544 file_[:-2] + ".i")
544 file_[:-2] + ".i")
545 return r
545 return r
546
546
547 def copy(ui, repo, pats, opts, rename=False):
547 def copy(ui, repo, pats, opts, rename=False):
548 # called with the repo lock held
548 # called with the repo lock held
549 #
549 #
550 # hgsep => pathname that uses "/" to separate directories
550 # hgsep => pathname that uses "/" to separate directories
551 # ossep => pathname that uses os.sep to separate directories
551 # ossep => pathname that uses os.sep to separate directories
552 cwd = repo.getcwd()
552 cwd = repo.getcwd()
553 targets = {}
553 targets = {}
554 after = opts.get("after")
554 after = opts.get("after")
555 dryrun = opts.get("dry_run")
555 dryrun = opts.get("dry_run")
556 wctx = repo[None]
556 wctx = repo[None]
557
557
558 def walkpat(pat):
558 def walkpat(pat):
559 srcs = []
559 srcs = []
560 if after:
560 if after:
561 badstates = '?'
561 badstates = '?'
562 else:
562 else:
563 badstates = '?r'
563 badstates = '?r'
564 m = scmutil.match(repo[None], [pat], opts, globbed=True)
564 m = scmutil.match(repo[None], [pat], opts, globbed=True)
565 for abs in repo.walk(m):
565 for abs in repo.walk(m):
566 state = repo.dirstate[abs]
566 state = repo.dirstate[abs]
567 rel = m.rel(abs)
567 rel = m.rel(abs)
568 exact = m.exact(abs)
568 exact = m.exact(abs)
569 if state in badstates:
569 if state in badstates:
570 if exact and state == '?':
570 if exact and state == '?':
571 ui.warn(_('%s: not copying - file is not managed\n') % rel)
571 ui.warn(_('%s: not copying - file is not managed\n') % rel)
572 if exact and state == 'r':
572 if exact and state == 'r':
573 ui.warn(_('%s: not copying - file has been marked for'
573 ui.warn(_('%s: not copying - file has been marked for'
574 ' remove\n') % rel)
574 ' remove\n') % rel)
575 continue
575 continue
576 # abs: hgsep
576 # abs: hgsep
577 # rel: ossep
577 # rel: ossep
578 srcs.append((abs, rel, exact))
578 srcs.append((abs, rel, exact))
579 return srcs
579 return srcs
580
580
581 # abssrc: hgsep
581 # abssrc: hgsep
582 # relsrc: ossep
582 # relsrc: ossep
583 # otarget: ossep
583 # otarget: ossep
584 def copyfile(abssrc, relsrc, otarget, exact):
584 def copyfile(abssrc, relsrc, otarget, exact):
585 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
585 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
586 if '/' in abstarget:
586 if '/' in abstarget:
587 # We cannot normalize abstarget itself, this would prevent
587 # We cannot normalize abstarget itself, this would prevent
588 # case only renames, like a => A.
588 # case only renames, like a => A.
589 abspath, absname = abstarget.rsplit('/', 1)
589 abspath, absname = abstarget.rsplit('/', 1)
590 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
590 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
591 reltarget = repo.pathto(abstarget, cwd)
591 reltarget = repo.pathto(abstarget, cwd)
592 target = repo.wjoin(abstarget)
592 target = repo.wjoin(abstarget)
593 src = repo.wjoin(abssrc)
593 src = repo.wjoin(abssrc)
594 state = repo.dirstate[abstarget]
594 state = repo.dirstate[abstarget]
595
595
596 scmutil.checkportable(ui, abstarget)
596 scmutil.checkportable(ui, abstarget)
597
597
598 # check for collisions
598 # check for collisions
599 prevsrc = targets.get(abstarget)
599 prevsrc = targets.get(abstarget)
600 if prevsrc is not None:
600 if prevsrc is not None:
601 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
601 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
602 (reltarget, repo.pathto(abssrc, cwd),
602 (reltarget, repo.pathto(abssrc, cwd),
603 repo.pathto(prevsrc, cwd)))
603 repo.pathto(prevsrc, cwd)))
604 return
604 return
605
605
606 # check for overwrites
606 # check for overwrites
607 exists = os.path.lexists(target)
607 exists = os.path.lexists(target)
608 samefile = False
608 samefile = False
609 if exists and abssrc != abstarget:
609 if exists and abssrc != abstarget:
610 if (repo.dirstate.normalize(abssrc) ==
610 if (repo.dirstate.normalize(abssrc) ==
611 repo.dirstate.normalize(abstarget)):
611 repo.dirstate.normalize(abstarget)):
612 if not rename:
612 if not rename:
613 ui.warn(_("%s: can't copy - same file\n") % reltarget)
613 ui.warn(_("%s: can't copy - same file\n") % reltarget)
614 return
614 return
615 exists = False
615 exists = False
616 samefile = True
616 samefile = True
617
617
618 if not after and exists or after and state in 'mn':
618 if not after and exists or after and state in 'mn':
619 if not opts['force']:
619 if not opts['force']:
620 ui.warn(_('%s: not overwriting - file exists\n') %
620 ui.warn(_('%s: not overwriting - file exists\n') %
621 reltarget)
621 reltarget)
622 return
622 return
623
623
624 if after:
624 if after:
625 if not exists:
625 if not exists:
626 if rename:
626 if rename:
627 ui.warn(_('%s: not recording move - %s does not exist\n') %
627 ui.warn(_('%s: not recording move - %s does not exist\n') %
628 (relsrc, reltarget))
628 (relsrc, reltarget))
629 else:
629 else:
630 ui.warn(_('%s: not recording copy - %s does not exist\n') %
630 ui.warn(_('%s: not recording copy - %s does not exist\n') %
631 (relsrc, reltarget))
631 (relsrc, reltarget))
632 return
632 return
633 elif not dryrun:
633 elif not dryrun:
634 try:
634 try:
635 if exists:
635 if exists:
636 os.unlink(target)
636 os.unlink(target)
637 targetdir = os.path.dirname(target) or '.'
637 targetdir = os.path.dirname(target) or '.'
638 if not os.path.isdir(targetdir):
638 if not os.path.isdir(targetdir):
639 os.makedirs(targetdir)
639 os.makedirs(targetdir)
640 if samefile:
640 if samefile:
641 tmp = target + "~hgrename"
641 tmp = target + "~hgrename"
642 os.rename(src, tmp)
642 os.rename(src, tmp)
643 os.rename(tmp, target)
643 os.rename(tmp, target)
644 else:
644 else:
645 util.copyfile(src, target)
645 util.copyfile(src, target)
646 srcexists = True
646 srcexists = True
647 except IOError as inst:
647 except IOError as inst:
648 if inst.errno == errno.ENOENT:
648 if inst.errno == errno.ENOENT:
649 ui.warn(_('%s: deleted in working directory\n') % relsrc)
649 ui.warn(_('%s: deleted in working directory\n') % relsrc)
650 srcexists = False
650 srcexists = False
651 else:
651 else:
652 ui.warn(_('%s: cannot copy - %s\n') %
652 ui.warn(_('%s: cannot copy - %s\n') %
653 (relsrc, inst.strerror))
653 (relsrc, inst.strerror))
654 return True # report a failure
654 return True # report a failure
655
655
656 if ui.verbose or not exact:
656 if ui.verbose or not exact:
657 if rename:
657 if rename:
658 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
658 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
659 else:
659 else:
660 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
660 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
661
661
662 targets[abstarget] = abssrc
662 targets[abstarget] = abssrc
663
663
664 # fix up dirstate
664 # fix up dirstate
665 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
665 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
666 dryrun=dryrun, cwd=cwd)
666 dryrun=dryrun, cwd=cwd)
667 if rename and not dryrun:
667 if rename and not dryrun:
668 if not after and srcexists and not samefile:
668 if not after and srcexists and not samefile:
669 util.unlinkpath(repo.wjoin(abssrc))
669 util.unlinkpath(repo.wjoin(abssrc))
670 wctx.forget([abssrc])
670 wctx.forget([abssrc])
671
671
672 # pat: ossep
672 # pat: ossep
673 # dest ossep
673 # dest ossep
674 # srcs: list of (hgsep, hgsep, ossep, bool)
674 # srcs: list of (hgsep, hgsep, ossep, bool)
675 # return: function that takes hgsep and returns ossep
675 # return: function that takes hgsep and returns ossep
676 def targetpathfn(pat, dest, srcs):
676 def targetpathfn(pat, dest, srcs):
677 if os.path.isdir(pat):
677 if os.path.isdir(pat):
678 abspfx = pathutil.canonpath(repo.root, cwd, pat)
678 abspfx = pathutil.canonpath(repo.root, cwd, pat)
679 abspfx = util.localpath(abspfx)
679 abspfx = util.localpath(abspfx)
680 if destdirexists:
680 if destdirexists:
681 striplen = len(os.path.split(abspfx)[0])
681 striplen = len(os.path.split(abspfx)[0])
682 else:
682 else:
683 striplen = len(abspfx)
683 striplen = len(abspfx)
684 if striplen:
684 if striplen:
685 striplen += len(os.sep)
685 striplen += len(os.sep)
686 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
686 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
687 elif destdirexists:
687 elif destdirexists:
688 res = lambda p: os.path.join(dest,
688 res = lambda p: os.path.join(dest,
689 os.path.basename(util.localpath(p)))
689 os.path.basename(util.localpath(p)))
690 else:
690 else:
691 res = lambda p: dest
691 res = lambda p: dest
692 return res
692 return res
693
693
694 # pat: ossep
694 # pat: ossep
695 # dest ossep
695 # dest ossep
696 # srcs: list of (hgsep, hgsep, ossep, bool)
696 # srcs: list of (hgsep, hgsep, ossep, bool)
697 # return: function that takes hgsep and returns ossep
697 # return: function that takes hgsep and returns ossep
698 def targetpathafterfn(pat, dest, srcs):
698 def targetpathafterfn(pat, dest, srcs):
699 if matchmod.patkind(pat):
699 if matchmod.patkind(pat):
700 # a mercurial pattern
700 # a mercurial pattern
701 res = lambda p: os.path.join(dest,
701 res = lambda p: os.path.join(dest,
702 os.path.basename(util.localpath(p)))
702 os.path.basename(util.localpath(p)))
703 else:
703 else:
704 abspfx = pathutil.canonpath(repo.root, cwd, pat)
704 abspfx = pathutil.canonpath(repo.root, cwd, pat)
705 if len(abspfx) < len(srcs[0][0]):
705 if len(abspfx) < len(srcs[0][0]):
706 # A directory. Either the target path contains the last
706 # A directory. Either the target path contains the last
707 # component of the source path or it does not.
707 # component of the source path or it does not.
708 def evalpath(striplen):
708 def evalpath(striplen):
709 score = 0
709 score = 0
710 for s in srcs:
710 for s in srcs:
711 t = os.path.join(dest, util.localpath(s[0])[striplen:])
711 t = os.path.join(dest, util.localpath(s[0])[striplen:])
712 if os.path.lexists(t):
712 if os.path.lexists(t):
713 score += 1
713 score += 1
714 return score
714 return score
715
715
716 abspfx = util.localpath(abspfx)
716 abspfx = util.localpath(abspfx)
717 striplen = len(abspfx)
717 striplen = len(abspfx)
718 if striplen:
718 if striplen:
719 striplen += len(os.sep)
719 striplen += len(os.sep)
720 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
720 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
721 score = evalpath(striplen)
721 score = evalpath(striplen)
722 striplen1 = len(os.path.split(abspfx)[0])
722 striplen1 = len(os.path.split(abspfx)[0])
723 if striplen1:
723 if striplen1:
724 striplen1 += len(os.sep)
724 striplen1 += len(os.sep)
725 if evalpath(striplen1) > score:
725 if evalpath(striplen1) > score:
726 striplen = striplen1
726 striplen = striplen1
727 res = lambda p: os.path.join(dest,
727 res = lambda p: os.path.join(dest,
728 util.localpath(p)[striplen:])
728 util.localpath(p)[striplen:])
729 else:
729 else:
730 # a file
730 # a file
731 if destdirexists:
731 if destdirexists:
732 res = lambda p: os.path.join(dest,
732 res = lambda p: os.path.join(dest,
733 os.path.basename(util.localpath(p)))
733 os.path.basename(util.localpath(p)))
734 else:
734 else:
735 res = lambda p: dest
735 res = lambda p: dest
736 return res
736 return res
737
737
738 pats = scmutil.expandpats(pats)
738 pats = scmutil.expandpats(pats)
739 if not pats:
739 if not pats:
740 raise error.Abort(_('no source or destination specified'))
740 raise error.Abort(_('no source or destination specified'))
741 if len(pats) == 1:
741 if len(pats) == 1:
742 raise error.Abort(_('no destination specified'))
742 raise error.Abort(_('no destination specified'))
743 dest = pats.pop()
743 dest = pats.pop()
744 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
744 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
745 if not destdirexists:
745 if not destdirexists:
746 if len(pats) > 1 or matchmod.patkind(pats[0]):
746 if len(pats) > 1 or matchmod.patkind(pats[0]):
747 raise error.Abort(_('with multiple sources, destination must be an '
747 raise error.Abort(_('with multiple sources, destination must be an '
748 'existing directory'))
748 'existing directory'))
749 if util.endswithsep(dest):
749 if util.endswithsep(dest):
750 raise error.Abort(_('destination %s is not a directory') % dest)
750 raise error.Abort(_('destination %s is not a directory') % dest)
751
751
752 tfn = targetpathfn
752 tfn = targetpathfn
753 if after:
753 if after:
754 tfn = targetpathafterfn
754 tfn = targetpathafterfn
755 copylist = []
755 copylist = []
756 for pat in pats:
756 for pat in pats:
757 srcs = walkpat(pat)
757 srcs = walkpat(pat)
758 if not srcs:
758 if not srcs:
759 continue
759 continue
760 copylist.append((tfn(pat, dest, srcs), srcs))
760 copylist.append((tfn(pat, dest, srcs), srcs))
761 if not copylist:
761 if not copylist:
762 raise error.Abort(_('no files to copy'))
762 raise error.Abort(_('no files to copy'))
763
763
764 errors = 0
764 errors = 0
765 for targetpath, srcs in copylist:
765 for targetpath, srcs in copylist:
766 for abssrc, relsrc, exact in srcs:
766 for abssrc, relsrc, exact in srcs:
767 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
767 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
768 errors += 1
768 errors += 1
769
769
770 if errors:
770 if errors:
771 ui.warn(_('(consider using --after)\n'))
771 ui.warn(_('(consider using --after)\n'))
772
772
773 return errors != 0
773 return errors != 0
774
774
775 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
775 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
776 runargs=None, appendpid=False):
776 runargs=None, appendpid=False):
777 '''Run a command as a service.'''
777 '''Run a command as a service.'''
778
778
779 def writepid(pid):
779 def writepid(pid):
780 if opts['pid_file']:
780 if opts['pid_file']:
781 if appendpid:
781 if appendpid:
782 mode = 'a'
782 mode = 'a'
783 else:
783 else:
784 mode = 'w'
784 mode = 'w'
785 fp = open(opts['pid_file'], mode)
785 fp = open(opts['pid_file'], mode)
786 fp.write(str(pid) + '\n')
786 fp.write(str(pid) + '\n')
787 fp.close()
787 fp.close()
788
788
789 if opts['daemon'] and not opts['daemon_postexec']:
789 if opts['daemon'] and not opts['daemon_postexec']:
790 # Signal child process startup with file removal
790 # Signal child process startup with file removal
791 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
791 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
792 os.close(lockfd)
792 os.close(lockfd)
793 try:
793 try:
794 if not runargs:
794 if not runargs:
795 runargs = util.hgcmd() + sys.argv[1:]
795 runargs = util.hgcmd() + sys.argv[1:]
796 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
796 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
797 # Don't pass --cwd to the child process, because we've already
797 # Don't pass --cwd to the child process, because we've already
798 # changed directory.
798 # changed directory.
799 for i in xrange(1, len(runargs)):
799 for i in xrange(1, len(runargs)):
800 if runargs[i].startswith('--cwd='):
800 if runargs[i].startswith('--cwd='):
801 del runargs[i]
801 del runargs[i]
802 break
802 break
803 elif runargs[i].startswith('--cwd'):
803 elif runargs[i].startswith('--cwd'):
804 del runargs[i:i + 2]
804 del runargs[i:i + 2]
805 break
805 break
806 def condfn():
806 def condfn():
807 return not os.path.exists(lockpath)
807 return not os.path.exists(lockpath)
808 pid = util.rundetached(runargs, condfn)
808 pid = util.rundetached(runargs, condfn)
809 if pid < 0:
809 if pid < 0:
810 raise error.Abort(_('child process failed to start'))
810 raise error.Abort(_('child process failed to start'))
811 writepid(pid)
811 writepid(pid)
812 finally:
812 finally:
813 try:
813 try:
814 os.unlink(lockpath)
814 os.unlink(lockpath)
815 except OSError as e:
815 except OSError as e:
816 if e.errno != errno.ENOENT:
816 if e.errno != errno.ENOENT:
817 raise
817 raise
818 if parentfn:
818 if parentfn:
819 return parentfn(pid)
819 return parentfn(pid)
820 else:
820 else:
821 return
821 return
822
822
823 if initfn:
823 if initfn:
824 initfn()
824 initfn()
825
825
826 if not opts['daemon']:
826 if not opts['daemon']:
827 writepid(util.getpid())
827 writepid(util.getpid())
828
828
829 if opts['daemon_postexec']:
829 if opts['daemon_postexec']:
830 try:
830 try:
831 os.setsid()
831 os.setsid()
832 except AttributeError:
832 except AttributeError:
833 pass
833 pass
834 for inst in opts['daemon_postexec']:
834 for inst in opts['daemon_postexec']:
835 if inst.startswith('unlink:'):
835 if inst.startswith('unlink:'):
836 lockpath = inst[7:]
836 lockpath = inst[7:]
837 os.unlink(lockpath)
837 os.unlink(lockpath)
838 elif inst.startswith('chdir:'):
838 elif inst.startswith('chdir:'):
839 os.chdir(inst[6:])
839 os.chdir(inst[6:])
840 elif inst != 'none':
840 elif inst != 'none':
841 raise error.Abort(_('invalid value for --daemon-postexec: %s')
841 raise error.Abort(_('invalid value for --daemon-postexec: %s')
842 % inst)
842 % inst)
843 util.hidewindow()
843 util.hidewindow()
844 sys.stdout.flush()
844 sys.stdout.flush()
845 sys.stderr.flush()
845 sys.stderr.flush()
846
846
847 nullfd = os.open(os.devnull, os.O_RDWR)
847 nullfd = os.open(os.devnull, os.O_RDWR)
848 logfilefd = nullfd
848 logfilefd = nullfd
849 if logfile:
849 if logfile:
850 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
850 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
851 os.dup2(nullfd, 0)
851 os.dup2(nullfd, 0)
852 os.dup2(logfilefd, 1)
852 os.dup2(logfilefd, 1)
853 os.dup2(logfilefd, 2)
853 os.dup2(logfilefd, 2)
854 if nullfd not in (0, 1, 2):
854 if nullfd not in (0, 1, 2):
855 os.close(nullfd)
855 os.close(nullfd)
856 if logfile and logfilefd not in (0, 1, 2):
856 if logfile and logfilefd not in (0, 1, 2):
857 os.close(logfilefd)
857 os.close(logfilefd)
858
858
859 if runfn:
859 if runfn:
860 return runfn()
860 return runfn()
861
861
862 ## facility to let extension process additional data into an import patch
862 ## facility to let extension process additional data into an import patch
863 # list of identifier to be executed in order
863 # list of identifier to be executed in order
864 extrapreimport = [] # run before commit
864 extrapreimport = [] # run before commit
865 extrapostimport = [] # run after commit
865 extrapostimport = [] # run after commit
866 # mapping from identifier to actual import function
866 # mapping from identifier to actual import function
867 #
867 #
868 # 'preimport' are run before the commit is made and are provided the following
868 # 'preimport' are run before the commit is made and are provided the following
869 # arguments:
869 # arguments:
870 # - repo: the localrepository instance,
870 # - repo: the localrepository instance,
871 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
871 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
872 # - extra: the future extra dictionary of the changeset, please mutate it,
872 # - extra: the future extra dictionary of the changeset, please mutate it,
873 # - opts: the import options.
873 # - opts: the import options.
874 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
874 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
875 # mutation of in memory commit and more. Feel free to rework the code to get
875 # mutation of in memory commit and more. Feel free to rework the code to get
876 # there.
876 # there.
877 extrapreimportmap = {}
877 extrapreimportmap = {}
878 # 'postimport' are run after the commit is made and are provided the following
878 # 'postimport' are run after the commit is made and are provided the following
879 # argument:
879 # argument:
880 # - ctx: the changectx created by import.
880 # - ctx: the changectx created by import.
881 extrapostimportmap = {}
881 extrapostimportmap = {}
882
882
883 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
883 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
884 """Utility function used by commands.import to import a single patch
884 """Utility function used by commands.import to import a single patch
885
885
886 This function is explicitly defined here to help the evolve extension to
886 This function is explicitly defined here to help the evolve extension to
887 wrap this part of the import logic.
887 wrap this part of the import logic.
888
888
889 The API is currently a bit ugly because it a simple code translation from
889 The API is currently a bit ugly because it a simple code translation from
890 the import command. Feel free to make it better.
890 the import command. Feel free to make it better.
891
891
892 :hunk: a patch (as a binary string)
892 :hunk: a patch (as a binary string)
893 :parents: nodes that will be parent of the created commit
893 :parents: nodes that will be parent of the created commit
894 :opts: the full dict of option passed to the import command
894 :opts: the full dict of option passed to the import command
895 :msgs: list to save commit message to.
895 :msgs: list to save commit message to.
896 (used in case we need to save it when failing)
896 (used in case we need to save it when failing)
897 :updatefunc: a function that update a repo to a given node
897 :updatefunc: a function that update a repo to a given node
898 updatefunc(<repo>, <node>)
898 updatefunc(<repo>, <node>)
899 """
899 """
900 # avoid cycle context -> subrepo -> cmdutil
900 # avoid cycle context -> subrepo -> cmdutil
901 from . import context
901 from . import context
902 extractdata = patch.extract(ui, hunk)
902 extractdata = patch.extract(ui, hunk)
903 tmpname = extractdata.get('filename')
903 tmpname = extractdata.get('filename')
904 message = extractdata.get('message')
904 message = extractdata.get('message')
905 user = opts.get('user') or extractdata.get('user')
905 user = opts.get('user') or extractdata.get('user')
906 date = opts.get('date') or extractdata.get('date')
906 date = opts.get('date') or extractdata.get('date')
907 branch = extractdata.get('branch')
907 branch = extractdata.get('branch')
908 nodeid = extractdata.get('nodeid')
908 nodeid = extractdata.get('nodeid')
909 p1 = extractdata.get('p1')
909 p1 = extractdata.get('p1')
910 p2 = extractdata.get('p2')
910 p2 = extractdata.get('p2')
911
911
912 nocommit = opts.get('no_commit')
912 nocommit = opts.get('no_commit')
913 importbranch = opts.get('import_branch')
913 importbranch = opts.get('import_branch')
914 update = not opts.get('bypass')
914 update = not opts.get('bypass')
915 strip = opts["strip"]
915 strip = opts["strip"]
916 prefix = opts["prefix"]
916 prefix = opts["prefix"]
917 sim = float(opts.get('similarity') or 0)
917 sim = float(opts.get('similarity') or 0)
918 if not tmpname:
918 if not tmpname:
919 return (None, None, False)
919 return (None, None, False)
920
920
921 rejects = False
921 rejects = False
922
922
923 try:
923 try:
924 cmdline_message = logmessage(ui, opts)
924 cmdline_message = logmessage(ui, opts)
925 if cmdline_message:
925 if cmdline_message:
926 # pickup the cmdline msg
926 # pickup the cmdline msg
927 message = cmdline_message
927 message = cmdline_message
928 elif message:
928 elif message:
929 # pickup the patch msg
929 # pickup the patch msg
930 message = message.strip()
930 message = message.strip()
931 else:
931 else:
932 # launch the editor
932 # launch the editor
933 message = None
933 message = None
934 ui.debug('message:\n%s\n' % message)
934 ui.debug('message:\n%s\n' % message)
935
935
936 if len(parents) == 1:
936 if len(parents) == 1:
937 parents.append(repo[nullid])
937 parents.append(repo[nullid])
938 if opts.get('exact'):
938 if opts.get('exact'):
939 if not nodeid or not p1:
939 if not nodeid or not p1:
940 raise error.Abort(_('not a Mercurial patch'))
940 raise error.Abort(_('not a Mercurial patch'))
941 p1 = repo[p1]
941 p1 = repo[p1]
942 p2 = repo[p2 or nullid]
942 p2 = repo[p2 or nullid]
943 elif p2:
943 elif p2:
944 try:
944 try:
945 p1 = repo[p1]
945 p1 = repo[p1]
946 p2 = repo[p2]
946 p2 = repo[p2]
947 # Without any options, consider p2 only if the
947 # Without any options, consider p2 only if the
948 # patch is being applied on top of the recorded
948 # patch is being applied on top of the recorded
949 # first parent.
949 # first parent.
950 if p1 != parents[0]:
950 if p1 != parents[0]:
951 p1 = parents[0]
951 p1 = parents[0]
952 p2 = repo[nullid]
952 p2 = repo[nullid]
953 except error.RepoError:
953 except error.RepoError:
954 p1, p2 = parents
954 p1, p2 = parents
955 if p2.node() == nullid:
955 if p2.node() == nullid:
956 ui.warn(_("warning: import the patch as a normal revision\n"
956 ui.warn(_("warning: import the patch as a normal revision\n"
957 "(use --exact to import the patch as a merge)\n"))
957 "(use --exact to import the patch as a merge)\n"))
958 else:
958 else:
959 p1, p2 = parents
959 p1, p2 = parents
960
960
961 n = None
961 n = None
962 if update:
962 if update:
963 if p1 != parents[0]:
963 if p1 != parents[0]:
964 updatefunc(repo, p1.node())
964 updatefunc(repo, p1.node())
965 if p2 != parents[1]:
965 if p2 != parents[1]:
966 repo.setparents(p1.node(), p2.node())
966 repo.setparents(p1.node(), p2.node())
967
967
968 if opts.get('exact') or importbranch:
968 if opts.get('exact') or importbranch:
969 repo.dirstate.setbranch(branch or 'default')
969 repo.dirstate.setbranch(branch or 'default')
970
970
971 partial = opts.get('partial', False)
971 partial = opts.get('partial', False)
972 files = set()
972 files = set()
973 try:
973 try:
974 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
974 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
975 files=files, eolmode=None, similarity=sim / 100.0)
975 files=files, eolmode=None, similarity=sim / 100.0)
976 except patch.PatchError as e:
976 except patch.PatchError as e:
977 if not partial:
977 if not partial:
978 raise error.Abort(str(e))
978 raise error.Abort(str(e))
979 if partial:
979 if partial:
980 rejects = True
980 rejects = True
981
981
982 files = list(files)
982 files = list(files)
983 if nocommit:
983 if nocommit:
984 if message:
984 if message:
985 msgs.append(message)
985 msgs.append(message)
986 else:
986 else:
987 if opts.get('exact') or p2:
987 if opts.get('exact') or p2:
988 # If you got here, you either use --force and know what
988 # If you got here, you either use --force and know what
989 # you are doing or used --exact or a merge patch while
989 # you are doing or used --exact or a merge patch while
990 # being updated to its first parent.
990 # being updated to its first parent.
991 m = None
991 m = None
992 else:
992 else:
993 m = scmutil.matchfiles(repo, files or [])
993 m = scmutil.matchfiles(repo, files or [])
994 editform = mergeeditform(repo[None], 'import.normal')
994 editform = mergeeditform(repo[None], 'import.normal')
995 if opts.get('exact'):
995 if opts.get('exact'):
996 editor = None
996 editor = None
997 else:
997 else:
998 editor = getcommiteditor(editform=editform, **opts)
998 editor = getcommiteditor(editform=editform, **opts)
999 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
999 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
1000 extra = {}
1000 extra = {}
1001 for idfunc in extrapreimport:
1001 for idfunc in extrapreimport:
1002 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1002 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1003 try:
1003 try:
1004 if partial:
1004 if partial:
1005 repo.ui.setconfig('ui', 'allowemptycommit', True)
1005 repo.ui.setconfig('ui', 'allowemptycommit', True)
1006 n = repo.commit(message, user,
1006 n = repo.commit(message, user,
1007 date, match=m,
1007 date, match=m,
1008 editor=editor, extra=extra)
1008 editor=editor, extra=extra)
1009 for idfunc in extrapostimport:
1009 for idfunc in extrapostimport:
1010 extrapostimportmap[idfunc](repo[n])
1010 extrapostimportmap[idfunc](repo[n])
1011 finally:
1011 finally:
1012 repo.ui.restoreconfig(allowemptyback)
1012 repo.ui.restoreconfig(allowemptyback)
1013 else:
1013 else:
1014 if opts.get('exact') or importbranch:
1014 if opts.get('exact') or importbranch:
1015 branch = branch or 'default'
1015 branch = branch or 'default'
1016 else:
1016 else:
1017 branch = p1.branch()
1017 branch = p1.branch()
1018 store = patch.filestore()
1018 store = patch.filestore()
1019 try:
1019 try:
1020 files = set()
1020 files = set()
1021 try:
1021 try:
1022 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1022 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1023 files, eolmode=None)
1023 files, eolmode=None)
1024 except patch.PatchError as e:
1024 except patch.PatchError as e:
1025 raise error.Abort(str(e))
1025 raise error.Abort(str(e))
1026 if opts.get('exact'):
1026 if opts.get('exact'):
1027 editor = None
1027 editor = None
1028 else:
1028 else:
1029 editor = getcommiteditor(editform='import.bypass')
1029 editor = getcommiteditor(editform='import.bypass')
1030 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1030 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1031 message,
1031 message,
1032 user,
1032 user,
1033 date,
1033 date,
1034 branch, files, store,
1034 branch, files, store,
1035 editor=editor)
1035 editor=editor)
1036 n = memctx.commit()
1036 n = memctx.commit()
1037 finally:
1037 finally:
1038 store.close()
1038 store.close()
1039 if opts.get('exact') and nocommit:
1039 if opts.get('exact') and nocommit:
1040 # --exact with --no-commit is still useful in that it does merge
1040 # --exact with --no-commit is still useful in that it does merge
1041 # and branch bits
1041 # and branch bits
1042 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1042 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1043 elif opts.get('exact') and hex(n) != nodeid:
1043 elif opts.get('exact') and hex(n) != nodeid:
1044 raise error.Abort(_('patch is damaged or loses information'))
1044 raise error.Abort(_('patch is damaged or loses information'))
1045 msg = _('applied to working directory')
1045 msg = _('applied to working directory')
1046 if n:
1046 if n:
1047 # i18n: refers to a short changeset id
1047 # i18n: refers to a short changeset id
1048 msg = _('created %s') % short(n)
1048 msg = _('created %s') % short(n)
1049 return (msg, n, rejects)
1049 return (msg, n, rejects)
1050 finally:
1050 finally:
1051 os.unlink(tmpname)
1051 os.unlink(tmpname)
1052
1052
1053 # facility to let extensions include additional data in an exported patch
1053 # facility to let extensions include additional data in an exported patch
1054 # list of identifiers to be executed in order
1054 # list of identifiers to be executed in order
1055 extraexport = []
1055 extraexport = []
1056 # mapping from identifier to actual export function
1056 # mapping from identifier to actual export function
1057 # function as to return a string to be added to the header or None
1057 # function as to return a string to be added to the header or None
1058 # it is given two arguments (sequencenumber, changectx)
1058 # it is given two arguments (sequencenumber, changectx)
1059 extraexportmap = {}
1059 extraexportmap = {}
1060
1060
1061 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1061 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1062 opts=None, match=None):
1062 opts=None, match=None):
1063 '''export changesets as hg patches.'''
1063 '''export changesets as hg patches.'''
1064
1064
1065 total = len(revs)
1065 total = len(revs)
1066 revwidth = max([len(str(rev)) for rev in revs])
1066 revwidth = max([len(str(rev)) for rev in revs])
1067 filemode = {}
1067 filemode = {}
1068
1068
1069 def single(rev, seqno, fp):
1069 def single(rev, seqno, fp):
1070 ctx = repo[rev]
1070 ctx = repo[rev]
1071 node = ctx.node()
1071 node = ctx.node()
1072 parents = [p.node() for p in ctx.parents() if p]
1072 parents = [p.node() for p in ctx.parents() if p]
1073 branch = ctx.branch()
1073 branch = ctx.branch()
1074 if switch_parent:
1074 if switch_parent:
1075 parents.reverse()
1075 parents.reverse()
1076
1076
1077 if parents:
1077 if parents:
1078 prev = parents[0]
1078 prev = parents[0]
1079 else:
1079 else:
1080 prev = nullid
1080 prev = nullid
1081
1081
1082 shouldclose = False
1082 shouldclose = False
1083 if not fp and len(template) > 0:
1083 if not fp and len(template) > 0:
1084 desc_lines = ctx.description().rstrip().split('\n')
1084 desc_lines = ctx.description().rstrip().split('\n')
1085 desc = desc_lines[0] #Commit always has a first line.
1085 desc = desc_lines[0] #Commit always has a first line.
1086 fp = makefileobj(repo, template, node, desc=desc, total=total,
1086 fp = makefileobj(repo, template, node, desc=desc, total=total,
1087 seqno=seqno, revwidth=revwidth, mode='wb',
1087 seqno=seqno, revwidth=revwidth, mode='wb',
1088 modemap=filemode)
1088 modemap=filemode)
1089 shouldclose = True
1089 shouldclose = True
1090 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1090 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1091 repo.ui.note("%s\n" % fp.name)
1091 repo.ui.note("%s\n" % fp.name)
1092
1092
1093 if not fp:
1093 if not fp:
1094 write = repo.ui.write
1094 write = repo.ui.write
1095 else:
1095 else:
1096 def write(s, **kw):
1096 def write(s, **kw):
1097 fp.write(s)
1097 fp.write(s)
1098
1098
1099 write("# HG changeset patch\n")
1099 write("# HG changeset patch\n")
1100 write("# User %s\n" % ctx.user())
1100 write("# User %s\n" % ctx.user())
1101 write("# Date %d %d\n" % ctx.date())
1101 write("# Date %d %d\n" % ctx.date())
1102 write("# %s\n" % util.datestr(ctx.date()))
1102 write("# %s\n" % util.datestr(ctx.date()))
1103 if branch and branch != 'default':
1103 if branch and branch != 'default':
1104 write("# Branch %s\n" % branch)
1104 write("# Branch %s\n" % branch)
1105 write("# Node ID %s\n" % hex(node))
1105 write("# Node ID %s\n" % hex(node))
1106 write("# Parent %s\n" % hex(prev))
1106 write("# Parent %s\n" % hex(prev))
1107 if len(parents) > 1:
1107 if len(parents) > 1:
1108 write("# Parent %s\n" % hex(parents[1]))
1108 write("# Parent %s\n" % hex(parents[1]))
1109
1109
1110 for headerid in extraexport:
1110 for headerid in extraexport:
1111 header = extraexportmap[headerid](seqno, ctx)
1111 header = extraexportmap[headerid](seqno, ctx)
1112 if header is not None:
1112 if header is not None:
1113 write('# %s\n' % header)
1113 write('# %s\n' % header)
1114 write(ctx.description().rstrip())
1114 write(ctx.description().rstrip())
1115 write("\n\n")
1115 write("\n\n")
1116
1116
1117 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1117 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1118 write(chunk, label=label)
1118 write(chunk, label=label)
1119
1119
1120 if shouldclose:
1120 if shouldclose:
1121 fp.close()
1121 fp.close()
1122
1122
1123 for seqno, rev in enumerate(revs):
1123 for seqno, rev in enumerate(revs):
1124 single(rev, seqno + 1, fp)
1124 single(rev, seqno + 1, fp)
1125
1125
1126 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1126 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1127 changes=None, stat=False, fp=None, prefix='',
1127 changes=None, stat=False, fp=None, prefix='',
1128 root='', listsubrepos=False):
1128 root='', listsubrepos=False):
1129 '''show diff or diffstat.'''
1129 '''show diff or diffstat.'''
1130 if fp is None:
1130 if fp is None:
1131 write = ui.write
1131 write = ui.write
1132 else:
1132 else:
1133 def write(s, **kw):
1133 def write(s, **kw):
1134 fp.write(s)
1134 fp.write(s)
1135
1135
1136 if root:
1136 if root:
1137 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1137 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1138 else:
1138 else:
1139 relroot = ''
1139 relroot = ''
1140 if relroot != '':
1140 if relroot != '':
1141 # XXX relative roots currently don't work if the root is within a
1141 # XXX relative roots currently don't work if the root is within a
1142 # subrepo
1142 # subrepo
1143 uirelroot = match.uipath(relroot)
1143 uirelroot = match.uipath(relroot)
1144 relroot += '/'
1144 relroot += '/'
1145 for matchroot in match.files():
1145 for matchroot in match.files():
1146 if not matchroot.startswith(relroot):
1146 if not matchroot.startswith(relroot):
1147 ui.warn(_('warning: %s not inside relative root %s\n') % (
1147 ui.warn(_('warning: %s not inside relative root %s\n') % (
1148 match.uipath(matchroot), uirelroot))
1148 match.uipath(matchroot), uirelroot))
1149
1149
1150 if stat:
1150 if stat:
1151 diffopts = diffopts.copy(context=0)
1151 diffopts = diffopts.copy(context=0)
1152 width = 80
1152 width = 80
1153 if not ui.plain():
1153 if not ui.plain():
1154 width = ui.termwidth()
1154 width = ui.termwidth()
1155 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1155 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1156 prefix=prefix, relroot=relroot)
1156 prefix=prefix, relroot=relroot)
1157 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1157 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1158 width=width,
1158 width=width,
1159 git=diffopts.git):
1159 git=diffopts.git):
1160 write(chunk, label=label)
1160 write(chunk, label=label)
1161 else:
1161 else:
1162 for chunk, label in patch.diffui(repo, node1, node2, match,
1162 for chunk, label in patch.diffui(repo, node1, node2, match,
1163 changes, diffopts, prefix=prefix,
1163 changes, diffopts, prefix=prefix,
1164 relroot=relroot):
1164 relroot=relroot):
1165 write(chunk, label=label)
1165 write(chunk, label=label)
1166
1166
1167 if listsubrepos:
1167 if listsubrepos:
1168 ctx1 = repo[node1]
1168 ctx1 = repo[node1]
1169 ctx2 = repo[node2]
1169 ctx2 = repo[node2]
1170 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1170 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1171 tempnode2 = node2
1171 tempnode2 = node2
1172 try:
1172 try:
1173 if node2 is not None:
1173 if node2 is not None:
1174 tempnode2 = ctx2.substate[subpath][1]
1174 tempnode2 = ctx2.substate[subpath][1]
1175 except KeyError:
1175 except KeyError:
1176 # A subrepo that existed in node1 was deleted between node1 and
1176 # A subrepo that existed in node1 was deleted between node1 and
1177 # node2 (inclusive). Thus, ctx2's substate won't contain that
1177 # node2 (inclusive). Thus, ctx2's substate won't contain that
1178 # subpath. The best we can do is to ignore it.
1178 # subpath. The best we can do is to ignore it.
1179 tempnode2 = None
1179 tempnode2 = None
1180 submatch = matchmod.subdirmatcher(subpath, match)
1180 submatch = matchmod.subdirmatcher(subpath, match)
1181 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1181 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1182 stat=stat, fp=fp, prefix=prefix)
1182 stat=stat, fp=fp, prefix=prefix)
1183
1183
1184 class changeset_printer(object):
1184 class changeset_printer(object):
1185 '''show changeset information when templating not requested.'''
1185 '''show changeset information when templating not requested.'''
1186
1186
1187 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1187 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1188 self.ui = ui
1188 self.ui = ui
1189 self.repo = repo
1189 self.repo = repo
1190 self.buffered = buffered
1190 self.buffered = buffered
1191 self.matchfn = matchfn
1191 self.matchfn = matchfn
1192 self.diffopts = diffopts
1192 self.diffopts = diffopts
1193 self.header = {}
1193 self.header = {}
1194 self.hunk = {}
1194 self.hunk = {}
1195 self.lastheader = None
1195 self.lastheader = None
1196 self.footer = None
1196 self.footer = None
1197
1197
1198 def flush(self, ctx):
1198 def flush(self, ctx):
1199 rev = ctx.rev()
1199 rev = ctx.rev()
1200 if rev in self.header:
1200 if rev in self.header:
1201 h = self.header[rev]
1201 h = self.header[rev]
1202 if h != self.lastheader:
1202 if h != self.lastheader:
1203 self.lastheader = h
1203 self.lastheader = h
1204 self.ui.write(h)
1204 self.ui.write(h)
1205 del self.header[rev]
1205 del self.header[rev]
1206 if rev in self.hunk:
1206 if rev in self.hunk:
1207 self.ui.write(self.hunk[rev])
1207 self.ui.write(self.hunk[rev])
1208 del self.hunk[rev]
1208 del self.hunk[rev]
1209 return 1
1209 return 1
1210 return 0
1210 return 0
1211
1211
1212 def close(self):
1212 def close(self):
1213 if self.footer:
1213 if self.footer:
1214 self.ui.write(self.footer)
1214 self.ui.write(self.footer)
1215
1215
1216 def show(self, ctx, copies=None, matchfn=None, **props):
1216 def show(self, ctx, copies=None, matchfn=None, **props):
1217 if self.buffered:
1217 if self.buffered:
1218 self.ui.pushbuffer(labeled=True)
1218 self.ui.pushbuffer(labeled=True)
1219 self._show(ctx, copies, matchfn, props)
1219 self._show(ctx, copies, matchfn, props)
1220 self.hunk[ctx.rev()] = self.ui.popbuffer()
1220 self.hunk[ctx.rev()] = self.ui.popbuffer()
1221 else:
1221 else:
1222 self._show(ctx, copies, matchfn, props)
1222 self._show(ctx, copies, matchfn, props)
1223
1223
1224 def _show(self, ctx, copies, matchfn, props):
1224 def _show(self, ctx, copies, matchfn, props):
1225 '''show a single changeset or file revision'''
1225 '''show a single changeset or file revision'''
1226 changenode = ctx.node()
1226 changenode = ctx.node()
1227 rev = ctx.rev()
1227 rev = ctx.rev()
1228 if self.ui.debugflag:
1228 if self.ui.debugflag:
1229 hexfunc = hex
1229 hexfunc = hex
1230 else:
1230 else:
1231 hexfunc = short
1231 hexfunc = short
1232 # as of now, wctx.node() and wctx.rev() return None, but we want to
1232 # as of now, wctx.node() and wctx.rev() return None, but we want to
1233 # show the same values as {node} and {rev} templatekw
1233 # show the same values as {node} and {rev} templatekw
1234 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1234 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1235
1235
1236 if self.ui.quiet:
1236 if self.ui.quiet:
1237 self.ui.write("%d:%s\n" % revnode, label='log.node')
1237 self.ui.write("%d:%s\n" % revnode, label='log.node')
1238 return
1238 return
1239
1239
1240 date = util.datestr(ctx.date())
1240 date = util.datestr(ctx.date())
1241
1241
1242 # i18n: column positioning for "hg log"
1242 # i18n: column positioning for "hg log"
1243 self.ui.write(_("changeset: %d:%s\n") % revnode,
1243 self.ui.write(_("changeset: %d:%s\n") % revnode,
1244 label='log.changeset changeset.%s' % ctx.phasestr())
1244 label='log.changeset changeset.%s' % ctx.phasestr())
1245
1245
1246 # branches are shown first before any other names due to backwards
1246 # branches are shown first before any other names due to backwards
1247 # compatibility
1247 # compatibility
1248 branch = ctx.branch()
1248 branch = ctx.branch()
1249 # don't show the default branch name
1249 # don't show the default branch name
1250 if branch != 'default':
1250 if branch != 'default':
1251 # i18n: column positioning for "hg log"
1251 # i18n: column positioning for "hg log"
1252 self.ui.write(_("branch: %s\n") % branch,
1252 self.ui.write(_("branch: %s\n") % branch,
1253 label='log.branch')
1253 label='log.branch')
1254
1254
1255 for name, ns in self.repo.names.iteritems():
1255 for name, ns in self.repo.names.iteritems():
1256 # branches has special logic already handled above, so here we just
1256 # branches has special logic already handled above, so here we just
1257 # skip it
1257 # skip it
1258 if name == 'branches':
1258 if name == 'branches':
1259 continue
1259 continue
1260 # we will use the templatename as the color name since those two
1260 # we will use the templatename as the color name since those two
1261 # should be the same
1261 # should be the same
1262 for name in ns.names(self.repo, changenode):
1262 for name in ns.names(self.repo, changenode):
1263 self.ui.write(ns.logfmt % name,
1263 self.ui.write(ns.logfmt % name,
1264 label='log.%s' % ns.colorname)
1264 label='log.%s' % ns.colorname)
1265 if self.ui.debugflag:
1265 if self.ui.debugflag:
1266 # i18n: column positioning for "hg log"
1266 # i18n: column positioning for "hg log"
1267 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1267 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1268 label='log.phase')
1268 label='log.phase')
1269 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1269 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1270 label = 'log.parent changeset.%s' % pctx.phasestr()
1270 label = 'log.parent changeset.%s' % pctx.phasestr()
1271 # i18n: column positioning for "hg log"
1271 # i18n: column positioning for "hg log"
1272 self.ui.write(_("parent: %d:%s\n")
1272 self.ui.write(_("parent: %d:%s\n")
1273 % (pctx.rev(), hexfunc(pctx.node())),
1273 % (pctx.rev(), hexfunc(pctx.node())),
1274 label=label)
1274 label=label)
1275
1275
1276 if self.ui.debugflag and rev is not None:
1276 if self.ui.debugflag and rev is not None:
1277 mnode = ctx.manifestnode()
1277 mnode = ctx.manifestnode()
1278 # i18n: column positioning for "hg log"
1278 # i18n: column positioning for "hg log"
1279 self.ui.write(_("manifest: %d:%s\n") %
1279 self.ui.write(_("manifest: %d:%s\n") %
1280 (self.repo.manifest.rev(mnode), hex(mnode)),
1280 (self.repo.manifest.rev(mnode), hex(mnode)),
1281 label='ui.debug log.manifest')
1281 label='ui.debug log.manifest')
1282 # i18n: column positioning for "hg log"
1282 # i18n: column positioning for "hg log"
1283 self.ui.write(_("user: %s\n") % ctx.user(),
1283 self.ui.write(_("user: %s\n") % ctx.user(),
1284 label='log.user')
1284 label='log.user')
1285 # i18n: column positioning for "hg log"
1285 # i18n: column positioning for "hg log"
1286 self.ui.write(_("date: %s\n") % date,
1286 self.ui.write(_("date: %s\n") % date,
1287 label='log.date')
1287 label='log.date')
1288
1288
1289 if self.ui.debugflag:
1289 if self.ui.debugflag:
1290 files = ctx.p1().status(ctx)[:3]
1290 files = ctx.p1().status(ctx)[:3]
1291 for key, value in zip([# i18n: column positioning for "hg log"
1291 for key, value in zip([# i18n: column positioning for "hg log"
1292 _("files:"),
1292 _("files:"),
1293 # i18n: column positioning for "hg log"
1293 # i18n: column positioning for "hg log"
1294 _("files+:"),
1294 _("files+:"),
1295 # i18n: column positioning for "hg log"
1295 # i18n: column positioning for "hg log"
1296 _("files-:")], files):
1296 _("files-:")], files):
1297 if value:
1297 if value:
1298 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1298 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1299 label='ui.debug log.files')
1299 label='ui.debug log.files')
1300 elif ctx.files() and self.ui.verbose:
1300 elif ctx.files() and self.ui.verbose:
1301 # i18n: column positioning for "hg log"
1301 # i18n: column positioning for "hg log"
1302 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1302 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1303 label='ui.note log.files')
1303 label='ui.note log.files')
1304 if copies and self.ui.verbose:
1304 if copies and self.ui.verbose:
1305 copies = ['%s (%s)' % c for c in copies]
1305 copies = ['%s (%s)' % c for c in copies]
1306 # i18n: column positioning for "hg log"
1306 # i18n: column positioning for "hg log"
1307 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1307 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1308 label='ui.note log.copies')
1308 label='ui.note log.copies')
1309
1309
1310 extra = ctx.extra()
1310 extra = ctx.extra()
1311 if extra and self.ui.debugflag:
1311 if extra and self.ui.debugflag:
1312 for key, value in sorted(extra.items()):
1312 for key, value in sorted(extra.items()):
1313 # i18n: column positioning for "hg log"
1313 # i18n: column positioning for "hg log"
1314 self.ui.write(_("extra: %s=%s\n")
1314 self.ui.write(_("extra: %s=%s\n")
1315 % (key, value.encode('string_escape')),
1315 % (key, value.encode('string_escape')),
1316 label='ui.debug log.extra')
1316 label='ui.debug log.extra')
1317
1317
1318 description = ctx.description().strip()
1318 description = ctx.description().strip()
1319 if description:
1319 if description:
1320 if self.ui.verbose:
1320 if self.ui.verbose:
1321 self.ui.write(_("description:\n"),
1321 self.ui.write(_("description:\n"),
1322 label='ui.note log.description')
1322 label='ui.note log.description')
1323 self.ui.write(description,
1323 self.ui.write(description,
1324 label='ui.note log.description')
1324 label='ui.note log.description')
1325 self.ui.write("\n\n")
1325 self.ui.write("\n\n")
1326 else:
1326 else:
1327 # i18n: column positioning for "hg log"
1327 # i18n: column positioning for "hg log"
1328 self.ui.write(_("summary: %s\n") %
1328 self.ui.write(_("summary: %s\n") %
1329 description.splitlines()[0],
1329 description.splitlines()[0],
1330 label='log.summary')
1330 label='log.summary')
1331 self.ui.write("\n")
1331 self.ui.write("\n")
1332
1332
1333 self.showpatch(ctx, matchfn)
1333 self.showpatch(ctx, matchfn)
1334
1334
1335 def showpatch(self, ctx, matchfn):
1335 def showpatch(self, ctx, matchfn):
1336 if not matchfn:
1336 if not matchfn:
1337 matchfn = self.matchfn
1337 matchfn = self.matchfn
1338 if matchfn:
1338 if matchfn:
1339 stat = self.diffopts.get('stat')
1339 stat = self.diffopts.get('stat')
1340 diff = self.diffopts.get('patch')
1340 diff = self.diffopts.get('patch')
1341 diffopts = patch.diffallopts(self.ui, self.diffopts)
1341 diffopts = patch.diffallopts(self.ui, self.diffopts)
1342 node = ctx.node()
1342 node = ctx.node()
1343 prev = ctx.p1().node()
1343 prev = ctx.p1().node()
1344 if stat:
1344 if stat:
1345 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1345 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1346 match=matchfn, stat=True)
1346 match=matchfn, stat=True)
1347 if diff:
1347 if diff:
1348 if stat:
1348 if stat:
1349 self.ui.write("\n")
1349 self.ui.write("\n")
1350 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1350 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1351 match=matchfn, stat=False)
1351 match=matchfn, stat=False)
1352 self.ui.write("\n")
1352 self.ui.write("\n")
1353
1353
1354 class jsonchangeset(changeset_printer):
1354 class jsonchangeset(changeset_printer):
1355 '''format changeset information.'''
1355 '''format changeset information.'''
1356
1356
1357 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1357 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1358 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1358 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1359 self.cache = {}
1359 self.cache = {}
1360 self._first = True
1360 self._first = True
1361
1361
1362 def close(self):
1362 def close(self):
1363 if not self._first:
1363 if not self._first:
1364 self.ui.write("\n]\n")
1364 self.ui.write("\n]\n")
1365 else:
1365 else:
1366 self.ui.write("[]\n")
1366 self.ui.write("[]\n")
1367
1367
1368 def _show(self, ctx, copies, matchfn, props):
1368 def _show(self, ctx, copies, matchfn, props):
1369 '''show a single changeset or file revision'''
1369 '''show a single changeset or file revision'''
1370 rev = ctx.rev()
1370 rev = ctx.rev()
1371 if rev is None:
1371 if rev is None:
1372 jrev = jnode = 'null'
1372 jrev = jnode = 'null'
1373 else:
1373 else:
1374 jrev = str(rev)
1374 jrev = str(rev)
1375 jnode = '"%s"' % hex(ctx.node())
1375 jnode = '"%s"' % hex(ctx.node())
1376 j = encoding.jsonescape
1376 j = encoding.jsonescape
1377
1377
1378 if self._first:
1378 if self._first:
1379 self.ui.write("[\n {")
1379 self.ui.write("[\n {")
1380 self._first = False
1380 self._first = False
1381 else:
1381 else:
1382 self.ui.write(",\n {")
1382 self.ui.write(",\n {")
1383
1383
1384 if self.ui.quiet:
1384 if self.ui.quiet:
1385 self.ui.write('\n "rev": %s' % jrev)
1385 self.ui.write('\n "rev": %s' % jrev)
1386 self.ui.write(',\n "node": %s' % jnode)
1386 self.ui.write(',\n "node": %s' % jnode)
1387 self.ui.write('\n }')
1387 self.ui.write('\n }')
1388 return
1388 return
1389
1389
1390 self.ui.write('\n "rev": %s' % jrev)
1390 self.ui.write('\n "rev": %s' % jrev)
1391 self.ui.write(',\n "node": %s' % jnode)
1391 self.ui.write(',\n "node": %s' % jnode)
1392 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1392 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1393 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1393 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1394 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1394 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1395 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1395 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1396 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1396 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1397
1397
1398 self.ui.write(',\n "bookmarks": [%s]' %
1398 self.ui.write(',\n "bookmarks": [%s]' %
1399 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1399 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1400 self.ui.write(',\n "tags": [%s]' %
1400 self.ui.write(',\n "tags": [%s]' %
1401 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1401 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1402 self.ui.write(',\n "parents": [%s]' %
1402 self.ui.write(',\n "parents": [%s]' %
1403 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1403 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1404
1404
1405 if self.ui.debugflag:
1405 if self.ui.debugflag:
1406 if rev is None:
1406 if rev is None:
1407 jmanifestnode = 'null'
1407 jmanifestnode = 'null'
1408 else:
1408 else:
1409 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1409 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1410 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1410 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1411
1411
1412 self.ui.write(',\n "extra": {%s}' %
1412 self.ui.write(',\n "extra": {%s}' %
1413 ", ".join('"%s": "%s"' % (j(k), j(v))
1413 ", ".join('"%s": "%s"' % (j(k), j(v))
1414 for k, v in ctx.extra().items()))
1414 for k, v in ctx.extra().items()))
1415
1415
1416 files = ctx.p1().status(ctx)
1416 files = ctx.p1().status(ctx)
1417 self.ui.write(',\n "modified": [%s]' %
1417 self.ui.write(',\n "modified": [%s]' %
1418 ", ".join('"%s"' % j(f) for f in files[0]))
1418 ", ".join('"%s"' % j(f) for f in files[0]))
1419 self.ui.write(',\n "added": [%s]' %
1419 self.ui.write(',\n "added": [%s]' %
1420 ", ".join('"%s"' % j(f) for f in files[1]))
1420 ", ".join('"%s"' % j(f) for f in files[1]))
1421 self.ui.write(',\n "removed": [%s]' %
1421 self.ui.write(',\n "removed": [%s]' %
1422 ", ".join('"%s"' % j(f) for f in files[2]))
1422 ", ".join('"%s"' % j(f) for f in files[2]))
1423
1423
1424 elif self.ui.verbose:
1424 elif self.ui.verbose:
1425 self.ui.write(',\n "files": [%s]' %
1425 self.ui.write(',\n "files": [%s]' %
1426 ", ".join('"%s"' % j(f) for f in ctx.files()))
1426 ", ".join('"%s"' % j(f) for f in ctx.files()))
1427
1427
1428 if copies:
1428 if copies:
1429 self.ui.write(',\n "copies": {%s}' %
1429 self.ui.write(',\n "copies": {%s}' %
1430 ", ".join('"%s": "%s"' % (j(k), j(v))
1430 ", ".join('"%s": "%s"' % (j(k), j(v))
1431 for k, v in copies))
1431 for k, v in copies))
1432
1432
1433 matchfn = self.matchfn
1433 matchfn = self.matchfn
1434 if matchfn:
1434 if matchfn:
1435 stat = self.diffopts.get('stat')
1435 stat = self.diffopts.get('stat')
1436 diff = self.diffopts.get('patch')
1436 diff = self.diffopts.get('patch')
1437 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1437 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1438 node, prev = ctx.node(), ctx.p1().node()
1438 node, prev = ctx.node(), ctx.p1().node()
1439 if stat:
1439 if stat:
1440 self.ui.pushbuffer()
1440 self.ui.pushbuffer()
1441 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1441 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1442 match=matchfn, stat=True)
1442 match=matchfn, stat=True)
1443 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1443 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1444 if diff:
1444 if diff:
1445 self.ui.pushbuffer()
1445 self.ui.pushbuffer()
1446 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1446 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1447 match=matchfn, stat=False)
1447 match=matchfn, stat=False)
1448 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1448 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1449
1449
1450 self.ui.write("\n }")
1450 self.ui.write("\n }")
1451
1451
1452 class changeset_templater(changeset_printer):
1452 class changeset_templater(changeset_printer):
1453 '''format changeset information.'''
1453 '''format changeset information.'''
1454
1454
1455 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1455 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1456 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1456 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1457 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1457 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1458 defaulttempl = {
1458 defaulttempl = {
1459 'parent': '{rev}:{node|formatnode} ',
1459 'parent': '{rev}:{node|formatnode} ',
1460 'manifest': '{rev}:{node|formatnode}',
1460 'manifest': '{rev}:{node|formatnode}',
1461 'file_copy': '{name} ({source})',
1461 'file_copy': '{name} ({source})',
1462 'extra': '{key}={value|stringescape}'
1462 'extra': '{key}={value|stringescape}'
1463 }
1463 }
1464 # filecopy is preserved for compatibility reasons
1464 # filecopy is preserved for compatibility reasons
1465 defaulttempl['filecopy'] = defaulttempl['file_copy']
1465 defaulttempl['filecopy'] = defaulttempl['file_copy']
1466 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1466 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1467 cache=defaulttempl)
1467 cache=defaulttempl)
1468 if tmpl:
1468 if tmpl:
1469 self.t.cache['changeset'] = tmpl
1469 self.t.cache['changeset'] = tmpl
1470
1470
1471 self.cache = {}
1471 self.cache = {}
1472
1472
1473 # find correct templates for current mode
1473 # find correct templates for current mode
1474 tmplmodes = [
1474 tmplmodes = [
1475 (True, None),
1475 (True, None),
1476 (self.ui.verbose, 'verbose'),
1476 (self.ui.verbose, 'verbose'),
1477 (self.ui.quiet, 'quiet'),
1477 (self.ui.quiet, 'quiet'),
1478 (self.ui.debugflag, 'debug'),
1478 (self.ui.debugflag, 'debug'),
1479 ]
1479 ]
1480
1480
1481 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1481 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1482 'docheader': '', 'docfooter': ''}
1482 'docheader': '', 'docfooter': ''}
1483 for mode, postfix in tmplmodes:
1483 for mode, postfix in tmplmodes:
1484 for t in self._parts:
1484 for t in self._parts:
1485 cur = t
1485 cur = t
1486 if postfix:
1486 if postfix:
1487 cur += "_" + postfix
1487 cur += "_" + postfix
1488 if mode and cur in self.t:
1488 if mode and cur in self.t:
1489 self._parts[t] = cur
1489 self._parts[t] = cur
1490
1490
1491 if self._parts['docheader']:
1491 if self._parts['docheader']:
1492 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1492 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1493
1493
1494 def close(self):
1494 def close(self):
1495 if self._parts['docfooter']:
1495 if self._parts['docfooter']:
1496 if not self.footer:
1496 if not self.footer:
1497 self.footer = ""
1497 self.footer = ""
1498 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1498 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1499 return super(changeset_templater, self).close()
1499 return super(changeset_templater, self).close()
1500
1500
1501 def _show(self, ctx, copies, matchfn, props):
1501 def _show(self, ctx, copies, matchfn, props):
1502 '''show a single changeset or file revision'''
1502 '''show a single changeset or file revision'''
1503 props = props.copy()
1503 props = props.copy()
1504 props.update(templatekw.keywords)
1504 props.update(templatekw.keywords)
1505 props['templ'] = self.t
1505 props['templ'] = self.t
1506 props['ctx'] = ctx
1506 props['ctx'] = ctx
1507 props['repo'] = self.repo
1507 props['repo'] = self.repo
1508 props['ui'] = self.repo.ui
1508 props['ui'] = self.repo.ui
1509 props['revcache'] = {'copies': copies}
1509 props['revcache'] = {'copies': copies}
1510 props['cache'] = self.cache
1510 props['cache'] = self.cache
1511
1511
1512 try:
1512 try:
1513 # write header
1513 # write header
1514 if self._parts['header']:
1514 if self._parts['header']:
1515 h = templater.stringify(self.t(self._parts['header'], **props))
1515 h = templater.stringify(self.t(self._parts['header'], **props))
1516 if self.buffered:
1516 if self.buffered:
1517 self.header[ctx.rev()] = h
1517 self.header[ctx.rev()] = h
1518 else:
1518 else:
1519 if self.lastheader != h:
1519 if self.lastheader != h:
1520 self.lastheader = h
1520 self.lastheader = h
1521 self.ui.write(h)
1521 self.ui.write(h)
1522
1522
1523 # write changeset metadata, then patch if requested
1523 # write changeset metadata, then patch if requested
1524 key = self._parts['changeset']
1524 key = self._parts['changeset']
1525 self.ui.write(templater.stringify(self.t(key, **props)))
1525 self.ui.write(templater.stringify(self.t(key, **props)))
1526 self.showpatch(ctx, matchfn)
1526 self.showpatch(ctx, matchfn)
1527
1527
1528 if self._parts['footer']:
1528 if self._parts['footer']:
1529 if not self.footer:
1529 if not self.footer:
1530 self.footer = templater.stringify(
1530 self.footer = templater.stringify(
1531 self.t(self._parts['footer'], **props))
1531 self.t(self._parts['footer'], **props))
1532 except KeyError as inst:
1532 except KeyError as inst:
1533 msg = _("%s: no key named '%s'")
1533 msg = _("%s: no key named '%s'")
1534 raise error.Abort(msg % (self.t.mapfile, inst.args[0]))
1534 raise error.Abort(msg % (self.t.mapfile, inst.args[0]))
1535 except SyntaxError as inst:
1535 except SyntaxError as inst:
1536 raise error.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1536 raise error.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1537
1537
1538 def gettemplate(ui, tmpl, style):
1538 def gettemplate(ui, tmpl, style):
1539 """
1539 """
1540 Find the template matching the given template spec or style.
1540 Find the template matching the given template spec or style.
1541 """
1541 """
1542
1542
1543 # ui settings
1543 # ui settings
1544 if not tmpl and not style: # template are stronger than style
1544 if not tmpl and not style: # template are stronger than style
1545 tmpl = ui.config('ui', 'logtemplate')
1545 tmpl = ui.config('ui', 'logtemplate')
1546 if tmpl:
1546 if tmpl:
1547 try:
1547 try:
1548 tmpl = templater.unquotestring(tmpl)
1548 tmpl = templater.unquotestring(tmpl)
1549 except SyntaxError:
1549 except SyntaxError:
1550 pass
1550 pass
1551 return tmpl, None
1551 return tmpl, None
1552 else:
1552 else:
1553 style = util.expandpath(ui.config('ui', 'style', ''))
1553 style = util.expandpath(ui.config('ui', 'style', ''))
1554
1554
1555 if not tmpl and style:
1555 if not tmpl and style:
1556 mapfile = style
1556 mapfile = style
1557 if not os.path.split(mapfile)[0]:
1557 if not os.path.split(mapfile)[0]:
1558 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1558 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1559 or templater.templatepath(mapfile))
1559 or templater.templatepath(mapfile))
1560 if mapname:
1560 if mapname:
1561 mapfile = mapname
1561 mapfile = mapname
1562 return None, mapfile
1562 return None, mapfile
1563
1563
1564 if not tmpl:
1564 if not tmpl:
1565 return None, None
1565 return None, None
1566
1566
1567 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1567 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1568
1568
1569 def show_changeset(ui, repo, opts, buffered=False):
1569 def show_changeset(ui, repo, opts, buffered=False):
1570 """show one changeset using template or regular display.
1570 """show one changeset using template or regular display.
1571
1571
1572 Display format will be the first non-empty hit of:
1572 Display format will be the first non-empty hit of:
1573 1. option 'template'
1573 1. option 'template'
1574 2. option 'style'
1574 2. option 'style'
1575 3. [ui] setting 'logtemplate'
1575 3. [ui] setting 'logtemplate'
1576 4. [ui] setting 'style'
1576 4. [ui] setting 'style'
1577 If all of these values are either the unset or the empty string,
1577 If all of these values are either the unset or the empty string,
1578 regular display via changeset_printer() is done.
1578 regular display via changeset_printer() is done.
1579 """
1579 """
1580 # options
1580 # options
1581 matchfn = None
1581 matchfn = None
1582 if opts.get('patch') or opts.get('stat'):
1582 if opts.get('patch') or opts.get('stat'):
1583 matchfn = scmutil.matchall(repo)
1583 matchfn = scmutil.matchall(repo)
1584
1584
1585 if opts.get('template') == 'json':
1585 if opts.get('template') == 'json':
1586 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1586 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1587
1587
1588 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1588 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1589
1589
1590 if not tmpl and not mapfile:
1590 if not tmpl and not mapfile:
1591 return changeset_printer(ui, repo, matchfn, opts, buffered)
1591 return changeset_printer(ui, repo, matchfn, opts, buffered)
1592
1592
1593 try:
1593 try:
1594 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1594 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1595 buffered)
1595 buffered)
1596 except SyntaxError as inst:
1596 except SyntaxError as inst:
1597 raise error.Abort(inst.args[0])
1597 raise error.Abort(inst.args[0])
1598 return t
1598 return t
1599
1599
1600 def showmarker(ui, marker):
1600 def showmarker(ui, marker):
1601 """utility function to display obsolescence marker in a readable way
1601 """utility function to display obsolescence marker in a readable way
1602
1602
1603 To be used by debug function."""
1603 To be used by debug function."""
1604 ui.write(hex(marker.precnode()))
1604 ui.write(hex(marker.precnode()))
1605 for repl in marker.succnodes():
1605 for repl in marker.succnodes():
1606 ui.write(' ')
1606 ui.write(' ')
1607 ui.write(hex(repl))
1607 ui.write(hex(repl))
1608 ui.write(' %X ' % marker.flags())
1608 ui.write(' %X ' % marker.flags())
1609 parents = marker.parentnodes()
1609 parents = marker.parentnodes()
1610 if parents is not None:
1610 if parents is not None:
1611 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1611 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1612 ui.write('(%s) ' % util.datestr(marker.date()))
1612 ui.write('(%s) ' % util.datestr(marker.date()))
1613 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1613 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1614 sorted(marker.metadata().items())
1614 sorted(marker.metadata().items())
1615 if t[0] != 'date')))
1615 if t[0] != 'date')))
1616 ui.write('\n')
1616 ui.write('\n')
1617
1617
1618 def finddate(ui, repo, date):
1618 def finddate(ui, repo, date):
1619 """Find the tipmost changeset that matches the given date spec"""
1619 """Find the tipmost changeset that matches the given date spec"""
1620
1620
1621 df = util.matchdate(date)
1621 df = util.matchdate(date)
1622 m = scmutil.matchall(repo)
1622 m = scmutil.matchall(repo)
1623 results = {}
1623 results = {}
1624
1624
1625 def prep(ctx, fns):
1625 def prep(ctx, fns):
1626 d = ctx.date()
1626 d = ctx.date()
1627 if df(d[0]):
1627 if df(d[0]):
1628 results[ctx.rev()] = d
1628 results[ctx.rev()] = d
1629
1629
1630 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1630 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1631 rev = ctx.rev()
1631 rev = ctx.rev()
1632 if rev in results:
1632 if rev in results:
1633 ui.status(_("found revision %s from %s\n") %
1633 ui.status(_("found revision %s from %s\n") %
1634 (rev, util.datestr(results[rev])))
1634 (rev, util.datestr(results[rev])))
1635 return str(rev)
1635 return str(rev)
1636
1636
1637 raise error.Abort(_("revision matching date not found"))
1637 raise error.Abort(_("revision matching date not found"))
1638
1638
1639 def increasingwindows(windowsize=8, sizelimit=512):
1639 def increasingwindows(windowsize=8, sizelimit=512):
1640 while True:
1640 while True:
1641 yield windowsize
1641 yield windowsize
1642 if windowsize < sizelimit:
1642 if windowsize < sizelimit:
1643 windowsize *= 2
1643 windowsize *= 2
1644
1644
1645 class FileWalkError(Exception):
1645 class FileWalkError(Exception):
1646 pass
1646 pass
1647
1647
1648 def walkfilerevs(repo, match, follow, revs, fncache):
1648 def walkfilerevs(repo, match, follow, revs, fncache):
1649 '''Walks the file history for the matched files.
1649 '''Walks the file history for the matched files.
1650
1650
1651 Returns the changeset revs that are involved in the file history.
1651 Returns the changeset revs that are involved in the file history.
1652
1652
1653 Throws FileWalkError if the file history can't be walked using
1653 Throws FileWalkError if the file history can't be walked using
1654 filelogs alone.
1654 filelogs alone.
1655 '''
1655 '''
1656 wanted = set()
1656 wanted = set()
1657 copies = []
1657 copies = []
1658 minrev, maxrev = min(revs), max(revs)
1658 minrev, maxrev = min(revs), max(revs)
1659 def filerevgen(filelog, last):
1659 def filerevgen(filelog, last):
1660 """
1660 """
1661 Only files, no patterns. Check the history of each file.
1661 Only files, no patterns. Check the history of each file.
1662
1662
1663 Examines filelog entries within minrev, maxrev linkrev range
1663 Examines filelog entries within minrev, maxrev linkrev range
1664 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1664 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1665 tuples in backwards order
1665 tuples in backwards order
1666 """
1666 """
1667 cl_count = len(repo)
1667 cl_count = len(repo)
1668 revs = []
1668 revs = []
1669 for j in xrange(0, last + 1):
1669 for j in xrange(0, last + 1):
1670 linkrev = filelog.linkrev(j)
1670 linkrev = filelog.linkrev(j)
1671 if linkrev < minrev:
1671 if linkrev < minrev:
1672 continue
1672 continue
1673 # only yield rev for which we have the changelog, it can
1673 # only yield rev for which we have the changelog, it can
1674 # happen while doing "hg log" during a pull or commit
1674 # happen while doing "hg log" during a pull or commit
1675 if linkrev >= cl_count:
1675 if linkrev >= cl_count:
1676 break
1676 break
1677
1677
1678 parentlinkrevs = []
1678 parentlinkrevs = []
1679 for p in filelog.parentrevs(j):
1679 for p in filelog.parentrevs(j):
1680 if p != nullrev:
1680 if p != nullrev:
1681 parentlinkrevs.append(filelog.linkrev(p))
1681 parentlinkrevs.append(filelog.linkrev(p))
1682 n = filelog.node(j)
1682 n = filelog.node(j)
1683 revs.append((linkrev, parentlinkrevs,
1683 revs.append((linkrev, parentlinkrevs,
1684 follow and filelog.renamed(n)))
1684 follow and filelog.renamed(n)))
1685
1685
1686 return reversed(revs)
1686 return reversed(revs)
1687 def iterfiles():
1687 def iterfiles():
1688 pctx = repo['.']
1688 pctx = repo['.']
1689 for filename in match.files():
1689 for filename in match.files():
1690 if follow:
1690 if follow:
1691 if filename not in pctx:
1691 if filename not in pctx:
1692 raise error.Abort(_('cannot follow file not in parent '
1692 raise error.Abort(_('cannot follow file not in parent '
1693 'revision: "%s"') % filename)
1693 'revision: "%s"') % filename)
1694 yield filename, pctx[filename].filenode()
1694 yield filename, pctx[filename].filenode()
1695 else:
1695 else:
1696 yield filename, None
1696 yield filename, None
1697 for filename_node in copies:
1697 for filename_node in copies:
1698 yield filename_node
1698 yield filename_node
1699
1699
1700 for file_, node in iterfiles():
1700 for file_, node in iterfiles():
1701 filelog = repo.file(file_)
1701 filelog = repo.file(file_)
1702 if not len(filelog):
1702 if not len(filelog):
1703 if node is None:
1703 if node is None:
1704 # A zero count may be a directory or deleted file, so
1704 # A zero count may be a directory or deleted file, so
1705 # try to find matching entries on the slow path.
1705 # try to find matching entries on the slow path.
1706 if follow:
1706 if follow:
1707 raise error.Abort(
1707 raise error.Abort(
1708 _('cannot follow nonexistent file: "%s"') % file_)
1708 _('cannot follow nonexistent file: "%s"') % file_)
1709 raise FileWalkError("Cannot walk via filelog")
1709 raise FileWalkError("Cannot walk via filelog")
1710 else:
1710 else:
1711 continue
1711 continue
1712
1712
1713 if node is None:
1713 if node is None:
1714 last = len(filelog) - 1
1714 last = len(filelog) - 1
1715 else:
1715 else:
1716 last = filelog.rev(node)
1716 last = filelog.rev(node)
1717
1717
1718 # keep track of all ancestors of the file
1718 # keep track of all ancestors of the file
1719 ancestors = set([filelog.linkrev(last)])
1719 ancestors = set([filelog.linkrev(last)])
1720
1720
1721 # iterate from latest to oldest revision
1721 # iterate from latest to oldest revision
1722 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1722 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1723 if not follow:
1723 if not follow:
1724 if rev > maxrev:
1724 if rev > maxrev:
1725 continue
1725 continue
1726 else:
1726 else:
1727 # Note that last might not be the first interesting
1727 # Note that last might not be the first interesting
1728 # rev to us:
1728 # rev to us:
1729 # if the file has been changed after maxrev, we'll
1729 # if the file has been changed after maxrev, we'll
1730 # have linkrev(last) > maxrev, and we still need
1730 # have linkrev(last) > maxrev, and we still need
1731 # to explore the file graph
1731 # to explore the file graph
1732 if rev not in ancestors:
1732 if rev not in ancestors:
1733 continue
1733 continue
1734 # XXX insert 1327 fix here
1734 # XXX insert 1327 fix here
1735 if flparentlinkrevs:
1735 if flparentlinkrevs:
1736 ancestors.update(flparentlinkrevs)
1736 ancestors.update(flparentlinkrevs)
1737
1737
1738 fncache.setdefault(rev, []).append(file_)
1738 fncache.setdefault(rev, []).append(file_)
1739 wanted.add(rev)
1739 wanted.add(rev)
1740 if copied:
1740 if copied:
1741 copies.append(copied)
1741 copies.append(copied)
1742
1742
1743 return wanted
1743 return wanted
1744
1744
1745 class _followfilter(object):
1745 class _followfilter(object):
1746 def __init__(self, repo, onlyfirst=False):
1746 def __init__(self, repo, onlyfirst=False):
1747 self.repo = repo
1747 self.repo = repo
1748 self.startrev = nullrev
1748 self.startrev = nullrev
1749 self.roots = set()
1749 self.roots = set()
1750 self.onlyfirst = onlyfirst
1750 self.onlyfirst = onlyfirst
1751
1751
1752 def match(self, rev):
1752 def match(self, rev):
1753 def realparents(rev):
1753 def realparents(rev):
1754 if self.onlyfirst:
1754 if self.onlyfirst:
1755 return self.repo.changelog.parentrevs(rev)[0:1]
1755 return self.repo.changelog.parentrevs(rev)[0:1]
1756 else:
1756 else:
1757 return filter(lambda x: x != nullrev,
1757 return filter(lambda x: x != nullrev,
1758 self.repo.changelog.parentrevs(rev))
1758 self.repo.changelog.parentrevs(rev))
1759
1759
1760 if self.startrev == nullrev:
1760 if self.startrev == nullrev:
1761 self.startrev = rev
1761 self.startrev = rev
1762 return True
1762 return True
1763
1763
1764 if rev > self.startrev:
1764 if rev > self.startrev:
1765 # forward: all descendants
1765 # forward: all descendants
1766 if not self.roots:
1766 if not self.roots:
1767 self.roots.add(self.startrev)
1767 self.roots.add(self.startrev)
1768 for parent in realparents(rev):
1768 for parent in realparents(rev):
1769 if parent in self.roots:
1769 if parent in self.roots:
1770 self.roots.add(rev)
1770 self.roots.add(rev)
1771 return True
1771 return True
1772 else:
1772 else:
1773 # backwards: all parents
1773 # backwards: all parents
1774 if not self.roots:
1774 if not self.roots:
1775 self.roots.update(realparents(self.startrev))
1775 self.roots.update(realparents(self.startrev))
1776 if rev in self.roots:
1776 if rev in self.roots:
1777 self.roots.remove(rev)
1777 self.roots.remove(rev)
1778 self.roots.update(realparents(rev))
1778 self.roots.update(realparents(rev))
1779 return True
1779 return True
1780
1780
1781 return False
1781 return False
1782
1782
1783 def walkchangerevs(repo, match, opts, prepare):
1783 def walkchangerevs(repo, match, opts, prepare):
1784 '''Iterate over files and the revs in which they changed.
1784 '''Iterate over files and the revs in which they changed.
1785
1785
1786 Callers most commonly need to iterate backwards over the history
1786 Callers most commonly need to iterate backwards over the history
1787 in which they are interested. Doing so has awful (quadratic-looking)
1787 in which they are interested. Doing so has awful (quadratic-looking)
1788 performance, so we use iterators in a "windowed" way.
1788 performance, so we use iterators in a "windowed" way.
1789
1789
1790 We walk a window of revisions in the desired order. Within the
1790 We walk a window of revisions in the desired order. Within the
1791 window, we first walk forwards to gather data, then in the desired
1791 window, we first walk forwards to gather data, then in the desired
1792 order (usually backwards) to display it.
1792 order (usually backwards) to display it.
1793
1793
1794 This function returns an iterator yielding contexts. Before
1794 This function returns an iterator yielding contexts. Before
1795 yielding each context, the iterator will first call the prepare
1795 yielding each context, the iterator will first call the prepare
1796 function on each context in the window in forward order.'''
1796 function on each context in the window in forward order.'''
1797
1797
1798 follow = opts.get('follow') or opts.get('follow_first')
1798 follow = opts.get('follow') or opts.get('follow_first')
1799 revs = _logrevs(repo, opts)
1799 revs = _logrevs(repo, opts)
1800 if not revs:
1800 if not revs:
1801 return []
1801 return []
1802 wanted = set()
1802 wanted = set()
1803 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1803 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1804 opts.get('removed'))
1804 opts.get('removed'))
1805 fncache = {}
1805 fncache = {}
1806 change = repo.changectx
1806 change = repo.changectx
1807
1807
1808 # First step is to fill wanted, the set of revisions that we want to yield.
1808 # First step is to fill wanted, the set of revisions that we want to yield.
1809 # When it does not induce extra cost, we also fill fncache for revisions in
1809 # When it does not induce extra cost, we also fill fncache for revisions in
1810 # wanted: a cache of filenames that were changed (ctx.files()) and that
1810 # wanted: a cache of filenames that were changed (ctx.files()) and that
1811 # match the file filtering conditions.
1811 # match the file filtering conditions.
1812
1812
1813 if match.always():
1813 if match.always():
1814 # No files, no patterns. Display all revs.
1814 # No files, no patterns. Display all revs.
1815 wanted = revs
1815 wanted = revs
1816 elif not slowpath:
1816 elif not slowpath:
1817 # We only have to read through the filelog to find wanted revisions
1817 # We only have to read through the filelog to find wanted revisions
1818
1818
1819 try:
1819 try:
1820 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1820 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1821 except FileWalkError:
1821 except FileWalkError:
1822 slowpath = True
1822 slowpath = True
1823
1823
1824 # We decided to fall back to the slowpath because at least one
1824 # We decided to fall back to the slowpath because at least one
1825 # of the paths was not a file. Check to see if at least one of them
1825 # of the paths was not a file. Check to see if at least one of them
1826 # existed in history, otherwise simply return
1826 # existed in history, otherwise simply return
1827 for path in match.files():
1827 for path in match.files():
1828 if path == '.' or path in repo.store:
1828 if path == '.' or path in repo.store:
1829 break
1829 break
1830 else:
1830 else:
1831 return []
1831 return []
1832
1832
1833 if slowpath:
1833 if slowpath:
1834 # We have to read the changelog to match filenames against
1834 # We have to read the changelog to match filenames against
1835 # changed files
1835 # changed files
1836
1836
1837 if follow:
1837 if follow:
1838 raise error.Abort(_('can only follow copies/renames for explicit '
1838 raise error.Abort(_('can only follow copies/renames for explicit '
1839 'filenames'))
1839 'filenames'))
1840
1840
1841 # The slow path checks files modified in every changeset.
1841 # The slow path checks files modified in every changeset.
1842 # This is really slow on large repos, so compute the set lazily.
1842 # This is really slow on large repos, so compute the set lazily.
1843 class lazywantedset(object):
1843 class lazywantedset(object):
1844 def __init__(self):
1844 def __init__(self):
1845 self.set = set()
1845 self.set = set()
1846 self.revs = set(revs)
1846 self.revs = set(revs)
1847
1847
1848 # No need to worry about locality here because it will be accessed
1848 # No need to worry about locality here because it will be accessed
1849 # in the same order as the increasing window below.
1849 # in the same order as the increasing window below.
1850 def __contains__(self, value):
1850 def __contains__(self, value):
1851 if value in self.set:
1851 if value in self.set:
1852 return True
1852 return True
1853 elif not value in self.revs:
1853 elif not value in self.revs:
1854 return False
1854 return False
1855 else:
1855 else:
1856 self.revs.discard(value)
1856 self.revs.discard(value)
1857 ctx = change(value)
1857 ctx = change(value)
1858 matches = filter(match, ctx.files())
1858 matches = filter(match, ctx.files())
1859 if matches:
1859 if matches:
1860 fncache[value] = matches
1860 fncache[value] = matches
1861 self.set.add(value)
1861 self.set.add(value)
1862 return True
1862 return True
1863 return False
1863 return False
1864
1864
1865 def discard(self, value):
1865 def discard(self, value):
1866 self.revs.discard(value)
1866 self.revs.discard(value)
1867 self.set.discard(value)
1867 self.set.discard(value)
1868
1868
1869 wanted = lazywantedset()
1869 wanted = lazywantedset()
1870
1870
1871 # it might be worthwhile to do this in the iterator if the rev range
1871 # it might be worthwhile to do this in the iterator if the rev range
1872 # is descending and the prune args are all within that range
1872 # is descending and the prune args are all within that range
1873 for rev in opts.get('prune', ()):
1873 for rev in opts.get('prune', ()):
1874 rev = repo[rev].rev()
1874 rev = repo[rev].rev()
1875 ff = _followfilter(repo)
1875 ff = _followfilter(repo)
1876 stop = min(revs[0], revs[-1])
1876 stop = min(revs[0], revs[-1])
1877 for x in xrange(rev, stop - 1, -1):
1877 for x in xrange(rev, stop - 1, -1):
1878 if ff.match(x):
1878 if ff.match(x):
1879 wanted = wanted - [x]
1879 wanted = wanted - [x]
1880
1880
1881 # Now that wanted is correctly initialized, we can iterate over the
1881 # Now that wanted is correctly initialized, we can iterate over the
1882 # revision range, yielding only revisions in wanted.
1882 # revision range, yielding only revisions in wanted.
1883 def iterate():
1883 def iterate():
1884 if follow and match.always():
1884 if follow and match.always():
1885 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1885 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1886 def want(rev):
1886 def want(rev):
1887 return ff.match(rev) and rev in wanted
1887 return ff.match(rev) and rev in wanted
1888 else:
1888 else:
1889 def want(rev):
1889 def want(rev):
1890 return rev in wanted
1890 return rev in wanted
1891
1891
1892 it = iter(revs)
1892 it = iter(revs)
1893 stopiteration = False
1893 stopiteration = False
1894 for windowsize in increasingwindows():
1894 for windowsize in increasingwindows():
1895 nrevs = []
1895 nrevs = []
1896 for i in xrange(windowsize):
1896 for i in xrange(windowsize):
1897 rev = next(it, None)
1897 rev = next(it, None)
1898 if rev is None:
1898 if rev is None:
1899 stopiteration = True
1899 stopiteration = True
1900 break
1900 break
1901 elif want(rev):
1901 elif want(rev):
1902 nrevs.append(rev)
1902 nrevs.append(rev)
1903 for rev in sorted(nrevs):
1903 for rev in sorted(nrevs):
1904 fns = fncache.get(rev)
1904 fns = fncache.get(rev)
1905 ctx = change(rev)
1905 ctx = change(rev)
1906 if not fns:
1906 if not fns:
1907 def fns_generator():
1907 def fns_generator():
1908 for f in ctx.files():
1908 for f in ctx.files():
1909 if match(f):
1909 if match(f):
1910 yield f
1910 yield f
1911 fns = fns_generator()
1911 fns = fns_generator()
1912 prepare(ctx, fns)
1912 prepare(ctx, fns)
1913 for rev in nrevs:
1913 for rev in nrevs:
1914 yield change(rev)
1914 yield change(rev)
1915
1915
1916 if stopiteration:
1916 if stopiteration:
1917 break
1917 break
1918
1918
1919 return iterate()
1919 return iterate()
1920
1920
1921 def _makefollowlogfilematcher(repo, files, followfirst):
1921 def _makefollowlogfilematcher(repo, files, followfirst):
1922 # When displaying a revision with --patch --follow FILE, we have
1922 # When displaying a revision with --patch --follow FILE, we have
1923 # to know which file of the revision must be diffed. With
1923 # to know which file of the revision must be diffed. With
1924 # --follow, we want the names of the ancestors of FILE in the
1924 # --follow, we want the names of the ancestors of FILE in the
1925 # revision, stored in "fcache". "fcache" is populated by
1925 # revision, stored in "fcache". "fcache" is populated by
1926 # reproducing the graph traversal already done by --follow revset
1926 # reproducing the graph traversal already done by --follow revset
1927 # and relating linkrevs to file names (which is not "correct" but
1927 # and relating linkrevs to file names (which is not "correct" but
1928 # good enough).
1928 # good enough).
1929 fcache = {}
1929 fcache = {}
1930 fcacheready = [False]
1930 fcacheready = [False]
1931 pctx = repo['.']
1931 pctx = repo['.']
1932
1932
1933 def populate():
1933 def populate():
1934 for fn in files:
1934 for fn in files:
1935 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1935 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1936 for c in i:
1936 for c in i:
1937 fcache.setdefault(c.linkrev(), set()).add(c.path())
1937 fcache.setdefault(c.linkrev(), set()).add(c.path())
1938
1938
1939 def filematcher(rev):
1939 def filematcher(rev):
1940 if not fcacheready[0]:
1940 if not fcacheready[0]:
1941 # Lazy initialization
1941 # Lazy initialization
1942 fcacheready[0] = True
1942 fcacheready[0] = True
1943 populate()
1943 populate()
1944 return scmutil.matchfiles(repo, fcache.get(rev, []))
1944 return scmutil.matchfiles(repo, fcache.get(rev, []))
1945
1945
1946 return filematcher
1946 return filematcher
1947
1947
1948 def _makenofollowlogfilematcher(repo, pats, opts):
1948 def _makenofollowlogfilematcher(repo, pats, opts):
1949 '''hook for extensions to override the filematcher for non-follow cases'''
1949 '''hook for extensions to override the filematcher for non-follow cases'''
1950 return None
1950 return None
1951
1951
1952 def _makelogrevset(repo, pats, opts, revs):
1952 def _makelogrevset(repo, pats, opts, revs):
1953 """Return (expr, filematcher) where expr is a revset string built
1953 """Return (expr, filematcher) where expr is a revset string built
1954 from log options and file patterns or None. If --stat or --patch
1954 from log options and file patterns or None. If --stat or --patch
1955 are not passed filematcher is None. Otherwise it is a callable
1955 are not passed filematcher is None. Otherwise it is a callable
1956 taking a revision number and returning a match objects filtering
1956 taking a revision number and returning a match objects filtering
1957 the files to be detailed when displaying the revision.
1957 the files to be detailed when displaying the revision.
1958 """
1958 """
1959 opt2revset = {
1959 opt2revset = {
1960 'no_merges': ('not merge()', None),
1960 'no_merges': ('not merge()', None),
1961 'only_merges': ('merge()', None),
1961 'only_merges': ('merge()', None),
1962 '_ancestors': ('ancestors(%(val)s)', None),
1962 '_ancestors': ('ancestors(%(val)s)', None),
1963 '_fancestors': ('_firstancestors(%(val)s)', None),
1963 '_fancestors': ('_firstancestors(%(val)s)', None),
1964 '_descendants': ('descendants(%(val)s)', None),
1964 '_descendants': ('descendants(%(val)s)', None),
1965 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1965 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1966 '_matchfiles': ('_matchfiles(%(val)s)', None),
1966 '_matchfiles': ('_matchfiles(%(val)s)', None),
1967 'date': ('date(%(val)r)', None),
1967 'date': ('date(%(val)r)', None),
1968 'branch': ('branch(%(val)r)', ' or '),
1968 'branch': ('branch(%(val)r)', ' or '),
1969 '_patslog': ('filelog(%(val)r)', ' or '),
1969 '_patslog': ('filelog(%(val)r)', ' or '),
1970 '_patsfollow': ('follow(%(val)r)', ' or '),
1970 '_patsfollow': ('follow(%(val)r)', ' or '),
1971 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1971 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1972 'keyword': ('keyword(%(val)r)', ' or '),
1972 'keyword': ('keyword(%(val)r)', ' or '),
1973 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1973 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1974 'user': ('user(%(val)r)', ' or '),
1974 'user': ('user(%(val)r)', ' or '),
1975 }
1975 }
1976
1976
1977 opts = dict(opts)
1977 opts = dict(opts)
1978 # follow or not follow?
1978 # follow or not follow?
1979 follow = opts.get('follow') or opts.get('follow_first')
1979 follow = opts.get('follow') or opts.get('follow_first')
1980 if opts.get('follow_first'):
1980 if opts.get('follow_first'):
1981 followfirst = 1
1981 followfirst = 1
1982 else:
1982 else:
1983 followfirst = 0
1983 followfirst = 0
1984 # --follow with FILE behavior depends on revs...
1984 # --follow with FILE behavior depends on revs...
1985 it = iter(revs)
1985 it = iter(revs)
1986 startrev = it.next()
1986 startrev = it.next()
1987 followdescendants = startrev < next(it, startrev)
1987 followdescendants = startrev < next(it, startrev)
1988
1988
1989 # branch and only_branch are really aliases and must be handled at
1989 # branch and only_branch are really aliases and must be handled at
1990 # the same time
1990 # the same time
1991 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1991 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1992 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1992 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1993 # pats/include/exclude are passed to match.match() directly in
1993 # pats/include/exclude are passed to match.match() directly in
1994 # _matchfiles() revset but walkchangerevs() builds its matcher with
1994 # _matchfiles() revset but walkchangerevs() builds its matcher with
1995 # scmutil.match(). The difference is input pats are globbed on
1995 # scmutil.match(). The difference is input pats are globbed on
1996 # platforms without shell expansion (windows).
1996 # platforms without shell expansion (windows).
1997 wctx = repo[None]
1997 wctx = repo[None]
1998 match, pats = scmutil.matchandpats(wctx, pats, opts)
1998 match, pats = scmutil.matchandpats(wctx, pats, opts)
1999 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1999 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2000 opts.get('removed'))
2000 opts.get('removed'))
2001 if not slowpath:
2001 if not slowpath:
2002 for f in match.files():
2002 for f in match.files():
2003 if follow and f not in wctx:
2003 if follow and f not in wctx:
2004 # If the file exists, it may be a directory, so let it
2004 # If the file exists, it may be a directory, so let it
2005 # take the slow path.
2005 # take the slow path.
2006 if os.path.exists(repo.wjoin(f)):
2006 if os.path.exists(repo.wjoin(f)):
2007 slowpath = True
2007 slowpath = True
2008 continue
2008 continue
2009 else:
2009 else:
2010 raise error.Abort(_('cannot follow file not in parent '
2010 raise error.Abort(_('cannot follow file not in parent '
2011 'revision: "%s"') % f)
2011 'revision: "%s"') % f)
2012 filelog = repo.file(f)
2012 filelog = repo.file(f)
2013 if not filelog:
2013 if not filelog:
2014 # A zero count may be a directory or deleted file, so
2014 # A zero count may be a directory or deleted file, so
2015 # try to find matching entries on the slow path.
2015 # try to find matching entries on the slow path.
2016 if follow:
2016 if follow:
2017 raise error.Abort(
2017 raise error.Abort(
2018 _('cannot follow nonexistent file: "%s"') % f)
2018 _('cannot follow nonexistent file: "%s"') % f)
2019 slowpath = True
2019 slowpath = True
2020
2020
2021 # We decided to fall back to the slowpath because at least one
2021 # We decided to fall back to the slowpath because at least one
2022 # of the paths was not a file. Check to see if at least one of them
2022 # of the paths was not a file. Check to see if at least one of them
2023 # existed in history - in that case, we'll continue down the
2023 # existed in history - in that case, we'll continue down the
2024 # slowpath; otherwise, we can turn off the slowpath
2024 # slowpath; otherwise, we can turn off the slowpath
2025 if slowpath:
2025 if slowpath:
2026 for path in match.files():
2026 for path in match.files():
2027 if path == '.' or path in repo.store:
2027 if path == '.' or path in repo.store:
2028 break
2028 break
2029 else:
2029 else:
2030 slowpath = False
2030 slowpath = False
2031
2031
2032 fpats = ('_patsfollow', '_patsfollowfirst')
2032 fpats = ('_patsfollow', '_patsfollowfirst')
2033 fnopats = (('_ancestors', '_fancestors'),
2033 fnopats = (('_ancestors', '_fancestors'),
2034 ('_descendants', '_fdescendants'))
2034 ('_descendants', '_fdescendants'))
2035 if slowpath:
2035 if slowpath:
2036 # See walkchangerevs() slow path.
2036 # See walkchangerevs() slow path.
2037 #
2037 #
2038 # pats/include/exclude cannot be represented as separate
2038 # pats/include/exclude cannot be represented as separate
2039 # revset expressions as their filtering logic applies at file
2039 # revset expressions as their filtering logic applies at file
2040 # level. For instance "-I a -X a" matches a revision touching
2040 # level. For instance "-I a -X a" matches a revision touching
2041 # "a" and "b" while "file(a) and not file(b)" does
2041 # "a" and "b" while "file(a) and not file(b)" does
2042 # not. Besides, filesets are evaluated against the working
2042 # not. Besides, filesets are evaluated against the working
2043 # directory.
2043 # directory.
2044 matchargs = ['r:', 'd:relpath']
2044 matchargs = ['r:', 'd:relpath']
2045 for p in pats:
2045 for p in pats:
2046 matchargs.append('p:' + p)
2046 matchargs.append('p:' + p)
2047 for p in opts.get('include', []):
2047 for p in opts.get('include', []):
2048 matchargs.append('i:' + p)
2048 matchargs.append('i:' + p)
2049 for p in opts.get('exclude', []):
2049 for p in opts.get('exclude', []):
2050 matchargs.append('x:' + p)
2050 matchargs.append('x:' + p)
2051 matchargs = ','.join(('%r' % p) for p in matchargs)
2051 matchargs = ','.join(('%r' % p) for p in matchargs)
2052 opts['_matchfiles'] = matchargs
2052 opts['_matchfiles'] = matchargs
2053 if follow:
2053 if follow:
2054 opts[fnopats[0][followfirst]] = '.'
2054 opts[fnopats[0][followfirst]] = '.'
2055 else:
2055 else:
2056 if follow:
2056 if follow:
2057 if pats:
2057 if pats:
2058 # follow() revset interprets its file argument as a
2058 # follow() revset interprets its file argument as a
2059 # manifest entry, so use match.files(), not pats.
2059 # manifest entry, so use match.files(), not pats.
2060 opts[fpats[followfirst]] = list(match.files())
2060 opts[fpats[followfirst]] = list(match.files())
2061 else:
2061 else:
2062 op = fnopats[followdescendants][followfirst]
2062 op = fnopats[followdescendants][followfirst]
2063 opts[op] = 'rev(%d)' % startrev
2063 opts[op] = 'rev(%d)' % startrev
2064 else:
2064 else:
2065 opts['_patslog'] = list(pats)
2065 opts['_patslog'] = list(pats)
2066
2066
2067 filematcher = None
2067 filematcher = None
2068 if opts.get('patch') or opts.get('stat'):
2068 if opts.get('patch') or opts.get('stat'):
2069 # When following files, track renames via a special matcher.
2069 # When following files, track renames via a special matcher.
2070 # If we're forced to take the slowpath it means we're following
2070 # If we're forced to take the slowpath it means we're following
2071 # at least one pattern/directory, so don't bother with rename tracking.
2071 # at least one pattern/directory, so don't bother with rename tracking.
2072 if follow and not match.always() and not slowpath:
2072 if follow and not match.always() and not slowpath:
2073 # _makefollowlogfilematcher expects its files argument to be
2073 # _makefollowlogfilematcher expects its files argument to be
2074 # relative to the repo root, so use match.files(), not pats.
2074 # relative to the repo root, so use match.files(), not pats.
2075 filematcher = _makefollowlogfilematcher(repo, match.files(),
2075 filematcher = _makefollowlogfilematcher(repo, match.files(),
2076 followfirst)
2076 followfirst)
2077 else:
2077 else:
2078 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2078 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2079 if filematcher is None:
2079 if filematcher is None:
2080 filematcher = lambda rev: match
2080 filematcher = lambda rev: match
2081
2081
2082 expr = []
2082 expr = []
2083 for op, val in sorted(opts.iteritems()):
2083 for op, val in sorted(opts.iteritems()):
2084 if not val:
2084 if not val:
2085 continue
2085 continue
2086 if op not in opt2revset:
2086 if op not in opt2revset:
2087 continue
2087 continue
2088 revop, andor = opt2revset[op]
2088 revop, andor = opt2revset[op]
2089 if '%(val)' not in revop:
2089 if '%(val)' not in revop:
2090 expr.append(revop)
2090 expr.append(revop)
2091 else:
2091 else:
2092 if not isinstance(val, list):
2092 if not isinstance(val, list):
2093 e = revop % {'val': val}
2093 e = revop % {'val': val}
2094 else:
2094 else:
2095 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2095 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2096 expr.append(e)
2096 expr.append(e)
2097
2097
2098 if expr:
2098 if expr:
2099 expr = '(' + ' and '.join(expr) + ')'
2099 expr = '(' + ' and '.join(expr) + ')'
2100 else:
2100 else:
2101 expr = None
2101 expr = None
2102 return expr, filematcher
2102 return expr, filematcher
2103
2103
2104 def _logrevs(repo, opts):
2104 def _logrevs(repo, opts):
2105 # Default --rev value depends on --follow but --follow behavior
2105 # Default --rev value depends on --follow but --follow behavior
2106 # depends on revisions resolved from --rev...
2106 # depends on revisions resolved from --rev...
2107 follow = opts.get('follow') or opts.get('follow_first')
2107 follow = opts.get('follow') or opts.get('follow_first')
2108 if opts.get('rev'):
2108 if opts.get('rev'):
2109 revs = scmutil.revrange(repo, opts['rev'])
2109 revs = scmutil.revrange(repo, opts['rev'])
2110 elif follow and repo.dirstate.p1() == nullid:
2110 elif follow and repo.dirstate.p1() == nullid:
2111 revs = revset.baseset()
2111 revs = revset.baseset()
2112 elif follow:
2112 elif follow:
2113 revs = repo.revs('reverse(:.)')
2113 revs = repo.revs('reverse(:.)')
2114 else:
2114 else:
2115 revs = revset.spanset(repo)
2115 revs = revset.spanset(repo)
2116 revs.reverse()
2116 revs.reverse()
2117 return revs
2117 return revs
2118
2118
2119 def getgraphlogrevs(repo, pats, opts):
2119 def getgraphlogrevs(repo, pats, opts):
2120 """Return (revs, expr, filematcher) where revs is an iterable of
2120 """Return (revs, expr, filematcher) where revs is an iterable of
2121 revision numbers, expr is a revset string built from log options
2121 revision numbers, expr is a revset string built from log options
2122 and file patterns or None, and used to filter 'revs'. If --stat or
2122 and file patterns or None, and used to filter 'revs'. If --stat or
2123 --patch are not passed filematcher is None. Otherwise it is a
2123 --patch are not passed filematcher is None. Otherwise it is a
2124 callable taking a revision number and returning a match objects
2124 callable taking a revision number and returning a match objects
2125 filtering the files to be detailed when displaying the revision.
2125 filtering the files to be detailed when displaying the revision.
2126 """
2126 """
2127 limit = loglimit(opts)
2127 limit = loglimit(opts)
2128 revs = _logrevs(repo, opts)
2128 revs = _logrevs(repo, opts)
2129 if not revs:
2129 if not revs:
2130 return revset.baseset(), None, None
2130 return revset.baseset(), None, None
2131 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2131 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2132 if opts.get('rev'):
2132 if opts.get('rev'):
2133 # User-specified revs might be unsorted, but don't sort before
2133 # User-specified revs might be unsorted, but don't sort before
2134 # _makelogrevset because it might depend on the order of revs
2134 # _makelogrevset because it might depend on the order of revs
2135 revs.sort(reverse=True)
2135 revs.sort(reverse=True)
2136 if expr:
2136 if expr:
2137 # Revset matchers often operate faster on revisions in changelog
2137 # Revset matchers often operate faster on revisions in changelog
2138 # order, because most filters deal with the changelog.
2138 # order, because most filters deal with the changelog.
2139 revs.reverse()
2139 revs.reverse()
2140 matcher = revset.match(repo.ui, expr)
2140 matcher = revset.match(repo.ui, expr)
2141 # Revset matches can reorder revisions. "A or B" typically returns
2141 # Revset matches can reorder revisions. "A or B" typically returns
2142 # returns the revision matching A then the revision matching B. Sort
2142 # returns the revision matching A then the revision matching B. Sort
2143 # again to fix that.
2143 # again to fix that.
2144 revs = matcher(repo, revs)
2144 revs = matcher(repo, revs)
2145 revs.sort(reverse=True)
2145 revs.sort(reverse=True)
2146 if limit is not None:
2146 if limit is not None:
2147 limitedrevs = []
2147 limitedrevs = []
2148 for idx, rev in enumerate(revs):
2148 for idx, rev in enumerate(revs):
2149 if idx >= limit:
2149 if idx >= limit:
2150 break
2150 break
2151 limitedrevs.append(rev)
2151 limitedrevs.append(rev)
2152 revs = revset.baseset(limitedrevs)
2152 revs = revset.baseset(limitedrevs)
2153
2153
2154 return revs, expr, filematcher
2154 return revs, expr, filematcher
2155
2155
2156 def getlogrevs(repo, pats, opts):
2156 def getlogrevs(repo, pats, opts):
2157 """Return (revs, expr, filematcher) where revs is an iterable of
2157 """Return (revs, expr, filematcher) where revs is an iterable of
2158 revision numbers, expr is a revset string built from log options
2158 revision numbers, expr is a revset string built from log options
2159 and file patterns or None, and used to filter 'revs'. If --stat or
2159 and file patterns or None, and used to filter 'revs'. If --stat or
2160 --patch are not passed filematcher is None. Otherwise it is a
2160 --patch are not passed filematcher is None. Otherwise it is a
2161 callable taking a revision number and returning a match objects
2161 callable taking a revision number and returning a match objects
2162 filtering the files to be detailed when displaying the revision.
2162 filtering the files to be detailed when displaying the revision.
2163 """
2163 """
2164 limit = loglimit(opts)
2164 limit = loglimit(opts)
2165 revs = _logrevs(repo, opts)
2165 revs = _logrevs(repo, opts)
2166 if not revs:
2166 if not revs:
2167 return revset.baseset([]), None, None
2167 return revset.baseset([]), None, None
2168 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2168 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2169 if expr:
2169 if expr:
2170 # Revset matchers often operate faster on revisions in changelog
2170 # Revset matchers often operate faster on revisions in changelog
2171 # order, because most filters deal with the changelog.
2171 # order, because most filters deal with the changelog.
2172 if not opts.get('rev'):
2172 if not opts.get('rev'):
2173 revs.reverse()
2173 revs.reverse()
2174 matcher = revset.match(repo.ui, expr)
2174 matcher = revset.match(repo.ui, expr)
2175 # Revset matches can reorder revisions. "A or B" typically returns
2175 # Revset matches can reorder revisions. "A or B" typically returns
2176 # returns the revision matching A then the revision matching B. Sort
2176 # returns the revision matching A then the revision matching B. Sort
2177 # again to fix that.
2177 # again to fix that.
2178 fixopts = ['branch', 'only_branch', 'keyword', 'user']
2178 fixopts = ['branch', 'only_branch', 'keyword', 'user']
2179 oldrevs = revs
2179 oldrevs = revs
2180 revs = matcher(repo, revs)
2180 revs = matcher(repo, revs)
2181 if not opts.get('rev'):
2181 if not opts.get('rev'):
2182 revs.sort(reverse=True)
2182 revs.sort(reverse=True)
2183 elif len(pats) > 1 or any(len(opts.get(op, [])) > 1 for op in fixopts):
2183 elif len(pats) > 1 or any(len(opts.get(op, [])) > 1 for op in fixopts):
2184 # XXX "A or B" is known to change the order; fix it by filtering
2184 # XXX "A or B" is known to change the order; fix it by filtering
2185 # matched set again (issue5100)
2185 # matched set again (issue5100)
2186 revs = oldrevs & revs
2186 revs = oldrevs & revs
2187 if limit is not None:
2187 if limit is not None:
2188 limitedrevs = []
2188 limitedrevs = []
2189 for idx, r in enumerate(revs):
2189 for idx, r in enumerate(revs):
2190 if limit <= idx:
2190 if limit <= idx:
2191 break
2191 break
2192 limitedrevs.append(r)
2192 limitedrevs.append(r)
2193 revs = revset.baseset(limitedrevs)
2193 revs = revset.baseset(limitedrevs)
2194
2194
2195 return revs, expr, filematcher
2195 return revs, expr, filematcher
2196
2196
2197 def _graphnodeformatter(ui, displayer):
2197 def _graphnodeformatter(ui, displayer):
2198 spec = ui.config('ui', 'graphnodetemplate')
2198 spec = ui.config('ui', 'graphnodetemplate')
2199 if not spec:
2199 if not spec:
2200 return templatekw.showgraphnode # fast path for "{graphnode}"
2200 return templatekw.showgraphnode # fast path for "{graphnode}"
2201
2201
2202 templ = formatter.gettemplater(ui, 'graphnode', spec)
2202 templ = formatter.gettemplater(ui, 'graphnode', spec)
2203 cache = {}
2203 cache = {}
2204 if isinstance(displayer, changeset_templater):
2204 if isinstance(displayer, changeset_templater):
2205 cache = displayer.cache # reuse cache of slow templates
2205 cache = displayer.cache # reuse cache of slow templates
2206 props = templatekw.keywords.copy()
2206 props = templatekw.keywords.copy()
2207 props['templ'] = templ
2207 props['templ'] = templ
2208 props['cache'] = cache
2208 props['cache'] = cache
2209 def formatnode(repo, ctx):
2209 def formatnode(repo, ctx):
2210 props['ctx'] = ctx
2210 props['ctx'] = ctx
2211 props['repo'] = repo
2211 props['repo'] = repo
2212 props['ui'] = repo.ui
2212 props['ui'] = repo.ui
2213 props['revcache'] = {}
2213 props['revcache'] = {}
2214 return templater.stringify(templ('graphnode', **props))
2214 return templater.stringify(templ('graphnode', **props))
2215 return formatnode
2215 return formatnode
2216
2216
2217 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2217 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2218 filematcher=None):
2218 filematcher=None):
2219 formatnode = _graphnodeformatter(ui, displayer)
2219 formatnode = _graphnodeformatter(ui, displayer)
2220 state = graphmod.asciistate()
2220 state = graphmod.asciistate()
2221 styles = state['styles']
2221 styles = state['styles']
2222 edgetypes = {
2222 edgetypes = {
2223 'parent': graphmod.PARENT,
2223 'parent': graphmod.PARENT,
2224 'grandparent': graphmod.GRANDPARENT,
2224 'grandparent': graphmod.GRANDPARENT,
2225 'missing': graphmod.MISSINGPARENT
2225 'missing': graphmod.MISSINGPARENT
2226 }
2226 }
2227 for name, key in edgetypes.items():
2227 for name, key in edgetypes.items():
2228 # experimental config: ui.graphstyle.*
2228 # experimental config: ui.graphstyle.*
2229 styles[key] = ui.config('ui', 'graphstyle.%s' % name, styles[key])
2229 styles[key] = ui.config('ui', 'graphstyle.%s' % name, styles[key])
2230 if not styles[key]:
2230 if not styles[key]:
2231 styles[key] = None
2231 styles[key] = None
2232 for rev, type, ctx, parents in dag:
2232 for rev, type, ctx, parents in dag:
2233 char = formatnode(repo, ctx)
2233 char = formatnode(repo, ctx)
2234 copies = None
2234 copies = None
2235 if getrenamed and ctx.rev():
2235 if getrenamed and ctx.rev():
2236 copies = []
2236 copies = []
2237 for fn in ctx.files():
2237 for fn in ctx.files():
2238 rename = getrenamed(fn, ctx.rev())
2238 rename = getrenamed(fn, ctx.rev())
2239 if rename:
2239 if rename:
2240 copies.append((fn, rename[0]))
2240 copies.append((fn, rename[0]))
2241 revmatchfn = None
2241 revmatchfn = None
2242 if filematcher is not None:
2242 if filematcher is not None:
2243 revmatchfn = filematcher(ctx.rev())
2243 revmatchfn = filematcher(ctx.rev())
2244 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2244 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2245 lines = displayer.hunk.pop(rev).split('\n')
2245 lines = displayer.hunk.pop(rev).split('\n')
2246 if not lines[-1]:
2246 if not lines[-1]:
2247 del lines[-1]
2247 del lines[-1]
2248 displayer.flush(ctx)
2248 displayer.flush(ctx)
2249 edges = edgefn(type, char, lines, state, rev, parents)
2249 edges = edgefn(type, char, lines, state, rev, parents)
2250 for type, char, lines, coldata in edges:
2250 for type, char, lines, coldata in edges:
2251 graphmod.ascii(ui, state, type, char, lines, coldata)
2251 graphmod.ascii(ui, state, type, char, lines, coldata)
2252 displayer.close()
2252 displayer.close()
2253
2253
2254 def graphlog(ui, repo, *pats, **opts):
2254 def graphlog(ui, repo, *pats, **opts):
2255 # Parameters are identical to log command ones
2255 # Parameters are identical to log command ones
2256 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2256 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2257 revdag = graphmod.dagwalker(repo, revs)
2257 revdag = graphmod.dagwalker(repo, revs)
2258
2258
2259 getrenamed = None
2259 getrenamed = None
2260 if opts.get('copies'):
2260 if opts.get('copies'):
2261 endrev = None
2261 endrev = None
2262 if opts.get('rev'):
2262 if opts.get('rev'):
2263 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2263 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2264 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2264 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2265 displayer = show_changeset(ui, repo, opts, buffered=True)
2265 displayer = show_changeset(ui, repo, opts, buffered=True)
2266 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2266 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2267 filematcher)
2267 filematcher)
2268
2268
2269 def checkunsupportedgraphflags(pats, opts):
2269 def checkunsupportedgraphflags(pats, opts):
2270 for op in ["newest_first"]:
2270 for op in ["newest_first"]:
2271 if op in opts and opts[op]:
2271 if op in opts and opts[op]:
2272 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2272 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2273 % op.replace("_", "-"))
2273 % op.replace("_", "-"))
2274
2274
2275 def graphrevs(repo, nodes, opts):
2275 def graphrevs(repo, nodes, opts):
2276 limit = loglimit(opts)
2276 limit = loglimit(opts)
2277 nodes.reverse()
2277 nodes.reverse()
2278 if limit is not None:
2278 if limit is not None:
2279 nodes = nodes[:limit]
2279 nodes = nodes[:limit]
2280 return graphmod.nodes(repo, nodes)
2280 return graphmod.nodes(repo, nodes)
2281
2281
2282 def add(ui, repo, match, prefix, explicitonly, **opts):
2282 def add(ui, repo, match, prefix, explicitonly, **opts):
2283 join = lambda f: os.path.join(prefix, f)
2283 join = lambda f: os.path.join(prefix, f)
2284 bad = []
2284 bad = []
2285
2285
2286 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2286 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2287 names = []
2287 names = []
2288 wctx = repo[None]
2288 wctx = repo[None]
2289 cca = None
2289 cca = None
2290 abort, warn = scmutil.checkportabilityalert(ui)
2290 abort, warn = scmutil.checkportabilityalert(ui)
2291 if abort or warn:
2291 if abort or warn:
2292 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2292 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2293
2293
2294 badmatch = matchmod.badmatch(match, badfn)
2294 badmatch = matchmod.badmatch(match, badfn)
2295 dirstate = repo.dirstate
2295 dirstate = repo.dirstate
2296 # We don't want to just call wctx.walk here, since it would return a lot of
2296 # We don't want to just call wctx.walk here, since it would return a lot of
2297 # clean files, which we aren't interested in and takes time.
2297 # clean files, which we aren't interested in and takes time.
2298 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2298 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2299 True, False, full=False)):
2299 True, False, full=False)):
2300 exact = match.exact(f)
2300 exact = match.exact(f)
2301 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2301 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2302 if cca:
2302 if cca:
2303 cca(f)
2303 cca(f)
2304 names.append(f)
2304 names.append(f)
2305 if ui.verbose or not exact:
2305 if ui.verbose or not exact:
2306 ui.status(_('adding %s\n') % match.rel(f))
2306 ui.status(_('adding %s\n') % match.rel(f))
2307
2307
2308 for subpath in sorted(wctx.substate):
2308 for subpath in sorted(wctx.substate):
2309 sub = wctx.sub(subpath)
2309 sub = wctx.sub(subpath)
2310 try:
2310 try:
2311 submatch = matchmod.subdirmatcher(subpath, match)
2311 submatch = matchmod.subdirmatcher(subpath, match)
2312 if opts.get('subrepos'):
2312 if opts.get('subrepos'):
2313 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2313 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2314 else:
2314 else:
2315 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2315 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2316 except error.LookupError:
2316 except error.LookupError:
2317 ui.status(_("skipping missing subrepository: %s\n")
2317 ui.status(_("skipping missing subrepository: %s\n")
2318 % join(subpath))
2318 % join(subpath))
2319
2319
2320 if not opts.get('dry_run'):
2320 if not opts.get('dry_run'):
2321 rejected = wctx.add(names, prefix)
2321 rejected = wctx.add(names, prefix)
2322 bad.extend(f for f in rejected if f in match.files())
2322 bad.extend(f for f in rejected if f in match.files())
2323 return bad
2323 return bad
2324
2324
2325 def forget(ui, repo, match, prefix, explicitonly):
2325 def forget(ui, repo, match, prefix, explicitonly):
2326 join = lambda f: os.path.join(prefix, f)
2326 join = lambda f: os.path.join(prefix, f)
2327 bad = []
2327 bad = []
2328 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2328 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2329 wctx = repo[None]
2329 wctx = repo[None]
2330 forgot = []
2330 forgot = []
2331
2331
2332 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2332 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2333 forget = sorted(s[0] + s[1] + s[3] + s[6])
2333 forget = sorted(s[0] + s[1] + s[3] + s[6])
2334 if explicitonly:
2334 if explicitonly:
2335 forget = [f for f in forget if match.exact(f)]
2335 forget = [f for f in forget if match.exact(f)]
2336
2336
2337 for subpath in sorted(wctx.substate):
2337 for subpath in sorted(wctx.substate):
2338 sub = wctx.sub(subpath)
2338 sub = wctx.sub(subpath)
2339 try:
2339 try:
2340 submatch = matchmod.subdirmatcher(subpath, match)
2340 submatch = matchmod.subdirmatcher(subpath, match)
2341 subbad, subforgot = sub.forget(submatch, prefix)
2341 subbad, subforgot = sub.forget(submatch, prefix)
2342 bad.extend([subpath + '/' + f for f in subbad])
2342 bad.extend([subpath + '/' + f for f in subbad])
2343 forgot.extend([subpath + '/' + f for f in subforgot])
2343 forgot.extend([subpath + '/' + f for f in subforgot])
2344 except error.LookupError:
2344 except error.LookupError:
2345 ui.status(_("skipping missing subrepository: %s\n")
2345 ui.status(_("skipping missing subrepository: %s\n")
2346 % join(subpath))
2346 % join(subpath))
2347
2347
2348 if not explicitonly:
2348 if not explicitonly:
2349 for f in match.files():
2349 for f in match.files():
2350 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2350 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2351 if f not in forgot:
2351 if f not in forgot:
2352 if repo.wvfs.exists(f):
2352 if repo.wvfs.exists(f):
2353 # Don't complain if the exact case match wasn't given.
2353 # Don't complain if the exact case match wasn't given.
2354 # But don't do this until after checking 'forgot', so
2354 # But don't do this until after checking 'forgot', so
2355 # that subrepo files aren't normalized, and this op is
2355 # that subrepo files aren't normalized, and this op is
2356 # purely from data cached by the status walk above.
2356 # purely from data cached by the status walk above.
2357 if repo.dirstate.normalize(f) in repo.dirstate:
2357 if repo.dirstate.normalize(f) in repo.dirstate:
2358 continue
2358 continue
2359 ui.warn(_('not removing %s: '
2359 ui.warn(_('not removing %s: '
2360 'file is already untracked\n')
2360 'file is already untracked\n')
2361 % match.rel(f))
2361 % match.rel(f))
2362 bad.append(f)
2362 bad.append(f)
2363
2363
2364 for f in forget:
2364 for f in forget:
2365 if ui.verbose or not match.exact(f):
2365 if ui.verbose or not match.exact(f):
2366 ui.status(_('removing %s\n') % match.rel(f))
2366 ui.status(_('removing %s\n') % match.rel(f))
2367
2367
2368 rejected = wctx.forget(forget, prefix)
2368 rejected = wctx.forget(forget, prefix)
2369 bad.extend(f for f in rejected if f in match.files())
2369 bad.extend(f for f in rejected if f in match.files())
2370 forgot.extend(f for f in forget if f not in rejected)
2370 forgot.extend(f for f in forget if f not in rejected)
2371 return bad, forgot
2371 return bad, forgot
2372
2372
2373 def files(ui, ctx, m, fm, fmt, subrepos):
2373 def files(ui, ctx, m, fm, fmt, subrepos):
2374 rev = ctx.rev()
2374 rev = ctx.rev()
2375 ret = 1
2375 ret = 1
2376 ds = ctx.repo().dirstate
2376 ds = ctx.repo().dirstate
2377
2377
2378 for f in ctx.matches(m):
2378 for f in ctx.matches(m):
2379 if rev is None and ds[f] == 'r':
2379 if rev is None and ds[f] == 'r':
2380 continue
2380 continue
2381 fm.startitem()
2381 fm.startitem()
2382 if ui.verbose:
2382 if ui.verbose:
2383 fc = ctx[f]
2383 fc = ctx[f]
2384 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2384 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2385 fm.data(abspath=f)
2385 fm.data(abspath=f)
2386 fm.write('path', fmt, m.rel(f))
2386 fm.write('path', fmt, m.rel(f))
2387 ret = 0
2387 ret = 0
2388
2388
2389 for subpath in sorted(ctx.substate):
2389 for subpath in sorted(ctx.substate):
2390 def matchessubrepo(subpath):
2390 def matchessubrepo(subpath):
2391 return (m.exact(subpath)
2391 return (m.exact(subpath)
2392 or any(f.startswith(subpath + '/') for f in m.files()))
2392 or any(f.startswith(subpath + '/') for f in m.files()))
2393
2393
2394 if subrepos or matchessubrepo(subpath):
2394 if subrepos or matchessubrepo(subpath):
2395 sub = ctx.sub(subpath)
2395 sub = ctx.sub(subpath)
2396 try:
2396 try:
2397 submatch = matchmod.subdirmatcher(subpath, m)
2397 submatch = matchmod.subdirmatcher(subpath, m)
2398 recurse = m.exact(subpath) or subrepos
2398 recurse = m.exact(subpath) or subrepos
2399 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2399 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2400 ret = 0
2400 ret = 0
2401 except error.LookupError:
2401 except error.LookupError:
2402 ui.status(_("skipping missing subrepository: %s\n")
2402 ui.status(_("skipping missing subrepository: %s\n")
2403 % m.abs(subpath))
2403 % m.abs(subpath))
2404
2404
2405 return ret
2405 return ret
2406
2406
2407 def remove(ui, repo, m, prefix, after, force, subrepos):
2407 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2408 join = lambda f: os.path.join(prefix, f)
2408 join = lambda f: os.path.join(prefix, f)
2409 ret = 0
2409 ret = 0
2410 s = repo.status(match=m, clean=True)
2410 s = repo.status(match=m, clean=True)
2411 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2411 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2412
2412
2413 wctx = repo[None]
2413 wctx = repo[None]
2414
2414
2415 if warnings is None:
2416 warnings = []
2417 warn = True
2418 else:
2419 warn = False
2420
2415 for subpath in sorted(wctx.substate):
2421 for subpath in sorted(wctx.substate):
2416 def matchessubrepo(matcher, subpath):
2422 def matchessubrepo(matcher, subpath):
2417 if matcher.exact(subpath):
2423 if matcher.exact(subpath):
2418 return True
2424 return True
2419 for f in matcher.files():
2425 for f in matcher.files():
2420 if f.startswith(subpath):
2426 if f.startswith(subpath):
2421 return True
2427 return True
2422 return False
2428 return False
2423
2429
2424 if subrepos or matchessubrepo(m, subpath):
2430 if subrepos or matchessubrepo(m, subpath):
2425 sub = wctx.sub(subpath)
2431 sub = wctx.sub(subpath)
2426 try:
2432 try:
2427 submatch = matchmod.subdirmatcher(subpath, m)
2433 submatch = matchmod.subdirmatcher(subpath, m)
2428 if sub.removefiles(submatch, prefix, after, force, subrepos):
2434 if sub.removefiles(submatch, prefix, after, force, subrepos,
2435 warnings):
2429 ret = 1
2436 ret = 1
2430 except error.LookupError:
2437 except error.LookupError:
2431 ui.status(_("skipping missing subrepository: %s\n")
2438 warnings.append(_("skipping missing subrepository: %s\n")
2432 % join(subpath))
2439 % join(subpath))
2433
2440
2434 # warn about failure to delete explicit files/dirs
2441 # warn about failure to delete explicit files/dirs
2435 deleteddirs = util.dirs(deleted)
2442 deleteddirs = util.dirs(deleted)
2436 for f in m.files():
2443 for f in m.files():
2437 def insubrepo():
2444 def insubrepo():
2438 for subpath in wctx.substate:
2445 for subpath in wctx.substate:
2439 if f.startswith(subpath):
2446 if f.startswith(subpath):
2440 return True
2447 return True
2441 return False
2448 return False
2442
2449
2443 isdir = f in deleteddirs or wctx.hasdir(f)
2450 isdir = f in deleteddirs or wctx.hasdir(f)
2444 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2451 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2445 continue
2452 continue
2446
2453
2447 if repo.wvfs.exists(f):
2454 if repo.wvfs.exists(f):
2448 if repo.wvfs.isdir(f):
2455 if repo.wvfs.isdir(f):
2449 ui.warn(_('not removing %s: no tracked files\n')
2456 warnings.append(_('not removing %s: no tracked files\n')
2450 % m.rel(f))
2457 % m.rel(f))
2451 else:
2458 else:
2452 ui.warn(_('not removing %s: file is untracked\n')
2459 warnings.append(_('not removing %s: file is untracked\n')
2453 % m.rel(f))
2460 % m.rel(f))
2454 # missing files will generate a warning elsewhere
2461 # missing files will generate a warning elsewhere
2455 ret = 1
2462 ret = 1
2456
2463
2457 if force:
2464 if force:
2458 list = modified + deleted + clean + added
2465 list = modified + deleted + clean + added
2459 elif after:
2466 elif after:
2460 list = deleted
2467 list = deleted
2461 for f in modified + added + clean:
2468 for f in modified + added + clean:
2462 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2469 warnings.append(_('not removing %s: file still exists\n') % m.rel(f))
2463 ret = 1
2470 ret = 1
2464 else:
2471 else:
2465 list = deleted + clean
2472 list = deleted + clean
2466 for f in modified:
2473 for f in modified:
2467 ui.warn(_('not removing %s: file is modified (use -f'
2474 warnings.append(_('not removing %s: file is modified (use -f'
2468 ' to force removal)\n') % m.rel(f))
2475 ' to force removal)\n') % m.rel(f))
2469 ret = 1
2476 ret = 1
2470 for f in added:
2477 for f in added:
2471 ui.warn(_('not removing %s: file has been marked for add'
2478 warnings.append(_('not removing %s: file has been marked for add'
2472 ' (use forget to undo)\n') % m.rel(f))
2479 ' (use forget to undo)\n') % m.rel(f))
2473 ret = 1
2480 ret = 1
2474
2481
2475 for f in sorted(list):
2482 for f in sorted(list):
2476 if ui.verbose or not m.exact(f):
2483 if ui.verbose or not m.exact(f):
2477 ui.status(_('removing %s\n') % m.rel(f))
2484 ui.status(_('removing %s\n') % m.rel(f))
2478
2485
2479 with repo.wlock():
2486 with repo.wlock():
2480 if not after:
2487 if not after:
2481 for f in list:
2488 for f in list:
2482 if f in added:
2489 if f in added:
2483 continue # we never unlink added files on remove
2490 continue # we never unlink added files on remove
2484 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2491 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2485 repo[None].forget(list)
2492 repo[None].forget(list)
2486
2493
2494 if warn:
2495 for warning in warnings:
2496 ui.warn(warning)
2497
2487 return ret
2498 return ret
2488
2499
2489 def cat(ui, repo, ctx, matcher, prefix, **opts):
2500 def cat(ui, repo, ctx, matcher, prefix, **opts):
2490 err = 1
2501 err = 1
2491
2502
2492 def write(path):
2503 def write(path):
2493 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2504 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2494 pathname=os.path.join(prefix, path))
2505 pathname=os.path.join(prefix, path))
2495 data = ctx[path].data()
2506 data = ctx[path].data()
2496 if opts.get('decode'):
2507 if opts.get('decode'):
2497 data = repo.wwritedata(path, data)
2508 data = repo.wwritedata(path, data)
2498 fp.write(data)
2509 fp.write(data)
2499 fp.close()
2510 fp.close()
2500
2511
2501 # Automation often uses hg cat on single files, so special case it
2512 # Automation often uses hg cat on single files, so special case it
2502 # for performance to avoid the cost of parsing the manifest.
2513 # for performance to avoid the cost of parsing the manifest.
2503 if len(matcher.files()) == 1 and not matcher.anypats():
2514 if len(matcher.files()) == 1 and not matcher.anypats():
2504 file = matcher.files()[0]
2515 file = matcher.files()[0]
2505 mf = repo.manifest
2516 mf = repo.manifest
2506 mfnode = ctx.manifestnode()
2517 mfnode = ctx.manifestnode()
2507 if mfnode and mf.find(mfnode, file)[0]:
2518 if mfnode and mf.find(mfnode, file)[0]:
2508 write(file)
2519 write(file)
2509 return 0
2520 return 0
2510
2521
2511 # Don't warn about "missing" files that are really in subrepos
2522 # Don't warn about "missing" files that are really in subrepos
2512 def badfn(path, msg):
2523 def badfn(path, msg):
2513 for subpath in ctx.substate:
2524 for subpath in ctx.substate:
2514 if path.startswith(subpath):
2525 if path.startswith(subpath):
2515 return
2526 return
2516 matcher.bad(path, msg)
2527 matcher.bad(path, msg)
2517
2528
2518 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2529 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2519 write(abs)
2530 write(abs)
2520 err = 0
2531 err = 0
2521
2532
2522 for subpath in sorted(ctx.substate):
2533 for subpath in sorted(ctx.substate):
2523 sub = ctx.sub(subpath)
2534 sub = ctx.sub(subpath)
2524 try:
2535 try:
2525 submatch = matchmod.subdirmatcher(subpath, matcher)
2536 submatch = matchmod.subdirmatcher(subpath, matcher)
2526
2537
2527 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2538 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2528 **opts):
2539 **opts):
2529 err = 0
2540 err = 0
2530 except error.RepoLookupError:
2541 except error.RepoLookupError:
2531 ui.status(_("skipping missing subrepository: %s\n")
2542 ui.status(_("skipping missing subrepository: %s\n")
2532 % os.path.join(prefix, subpath))
2543 % os.path.join(prefix, subpath))
2533
2544
2534 return err
2545 return err
2535
2546
2536 def commit(ui, repo, commitfunc, pats, opts):
2547 def commit(ui, repo, commitfunc, pats, opts):
2537 '''commit the specified files or all outstanding changes'''
2548 '''commit the specified files or all outstanding changes'''
2538 date = opts.get('date')
2549 date = opts.get('date')
2539 if date:
2550 if date:
2540 opts['date'] = util.parsedate(date)
2551 opts['date'] = util.parsedate(date)
2541 message = logmessage(ui, opts)
2552 message = logmessage(ui, opts)
2542 matcher = scmutil.match(repo[None], pats, opts)
2553 matcher = scmutil.match(repo[None], pats, opts)
2543
2554
2544 # extract addremove carefully -- this function can be called from a command
2555 # extract addremove carefully -- this function can be called from a command
2545 # that doesn't support addremove
2556 # that doesn't support addremove
2546 if opts.get('addremove'):
2557 if opts.get('addremove'):
2547 if scmutil.addremove(repo, matcher, "", opts) != 0:
2558 if scmutil.addremove(repo, matcher, "", opts) != 0:
2548 raise error.Abort(
2559 raise error.Abort(
2549 _("failed to mark all new/missing files as added/removed"))
2560 _("failed to mark all new/missing files as added/removed"))
2550
2561
2551 return commitfunc(ui, repo, message, matcher, opts)
2562 return commitfunc(ui, repo, message, matcher, opts)
2552
2563
2553 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2564 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2554 # avoid cycle context -> subrepo -> cmdutil
2565 # avoid cycle context -> subrepo -> cmdutil
2555 from . import context
2566 from . import context
2556
2567
2557 # amend will reuse the existing user if not specified, but the obsolete
2568 # amend will reuse the existing user if not specified, but the obsolete
2558 # marker creation requires that the current user's name is specified.
2569 # marker creation requires that the current user's name is specified.
2559 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2570 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2560 ui.username() # raise exception if username not set
2571 ui.username() # raise exception if username not set
2561
2572
2562 ui.note(_('amending changeset %s\n') % old)
2573 ui.note(_('amending changeset %s\n') % old)
2563 base = old.p1()
2574 base = old.p1()
2564 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2575 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2565
2576
2566 wlock = lock = newid = None
2577 wlock = lock = newid = None
2567 try:
2578 try:
2568 wlock = repo.wlock()
2579 wlock = repo.wlock()
2569 lock = repo.lock()
2580 lock = repo.lock()
2570 with repo.transaction('amend') as tr:
2581 with repo.transaction('amend') as tr:
2571 # See if we got a message from -m or -l, if not, open the editor
2582 # See if we got a message from -m or -l, if not, open the editor
2572 # with the message of the changeset to amend
2583 # with the message of the changeset to amend
2573 message = logmessage(ui, opts)
2584 message = logmessage(ui, opts)
2574 # ensure logfile does not conflict with later enforcement of the
2585 # ensure logfile does not conflict with later enforcement of the
2575 # message. potential logfile content has been processed by
2586 # message. potential logfile content has been processed by
2576 # `logmessage` anyway.
2587 # `logmessage` anyway.
2577 opts.pop('logfile')
2588 opts.pop('logfile')
2578 # First, do a regular commit to record all changes in the working
2589 # First, do a regular commit to record all changes in the working
2579 # directory (if there are any)
2590 # directory (if there are any)
2580 ui.callhooks = False
2591 ui.callhooks = False
2581 activebookmark = repo._bookmarks.active
2592 activebookmark = repo._bookmarks.active
2582 try:
2593 try:
2583 repo._bookmarks.active = None
2594 repo._bookmarks.active = None
2584 opts['message'] = 'temporary amend commit for %s' % old
2595 opts['message'] = 'temporary amend commit for %s' % old
2585 node = commit(ui, repo, commitfunc, pats, opts)
2596 node = commit(ui, repo, commitfunc, pats, opts)
2586 finally:
2597 finally:
2587 repo._bookmarks.active = activebookmark
2598 repo._bookmarks.active = activebookmark
2588 repo._bookmarks.recordchange(tr)
2599 repo._bookmarks.recordchange(tr)
2589 ui.callhooks = True
2600 ui.callhooks = True
2590 ctx = repo[node]
2601 ctx = repo[node]
2591
2602
2592 # Participating changesets:
2603 # Participating changesets:
2593 #
2604 #
2594 # node/ctx o - new (intermediate) commit that contains changes
2605 # node/ctx o - new (intermediate) commit that contains changes
2595 # | from working dir to go into amending commit
2606 # | from working dir to go into amending commit
2596 # | (or a workingctx if there were no changes)
2607 # | (or a workingctx if there were no changes)
2597 # |
2608 # |
2598 # old o - changeset to amend
2609 # old o - changeset to amend
2599 # |
2610 # |
2600 # base o - parent of amending changeset
2611 # base o - parent of amending changeset
2601
2612
2602 # Update extra dict from amended commit (e.g. to preserve graft
2613 # Update extra dict from amended commit (e.g. to preserve graft
2603 # source)
2614 # source)
2604 extra.update(old.extra())
2615 extra.update(old.extra())
2605
2616
2606 # Also update it from the intermediate commit or from the wctx
2617 # Also update it from the intermediate commit or from the wctx
2607 extra.update(ctx.extra())
2618 extra.update(ctx.extra())
2608
2619
2609 if len(old.parents()) > 1:
2620 if len(old.parents()) > 1:
2610 # ctx.files() isn't reliable for merges, so fall back to the
2621 # ctx.files() isn't reliable for merges, so fall back to the
2611 # slower repo.status() method
2622 # slower repo.status() method
2612 files = set([fn for st in repo.status(base, old)[:3]
2623 files = set([fn for st in repo.status(base, old)[:3]
2613 for fn in st])
2624 for fn in st])
2614 else:
2625 else:
2615 files = set(old.files())
2626 files = set(old.files())
2616
2627
2617 # Second, we use either the commit we just did, or if there were no
2628 # Second, we use either the commit we just did, or if there were no
2618 # changes the parent of the working directory as the version of the
2629 # changes the parent of the working directory as the version of the
2619 # files in the final amend commit
2630 # files in the final amend commit
2620 if node:
2631 if node:
2621 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2632 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2622
2633
2623 user = ctx.user()
2634 user = ctx.user()
2624 date = ctx.date()
2635 date = ctx.date()
2625 # Recompute copies (avoid recording a -> b -> a)
2636 # Recompute copies (avoid recording a -> b -> a)
2626 copied = copies.pathcopies(base, ctx)
2637 copied = copies.pathcopies(base, ctx)
2627 if old.p2:
2638 if old.p2:
2628 copied.update(copies.pathcopies(old.p2(), ctx))
2639 copied.update(copies.pathcopies(old.p2(), ctx))
2629
2640
2630 # Prune files which were reverted by the updates: if old
2641 # Prune files which were reverted by the updates: if old
2631 # introduced file X and our intermediate commit, node,
2642 # introduced file X and our intermediate commit, node,
2632 # renamed that file, then those two files are the same and
2643 # renamed that file, then those two files are the same and
2633 # we can discard X from our list of files. Likewise if X
2644 # we can discard X from our list of files. Likewise if X
2634 # was deleted, it's no longer relevant
2645 # was deleted, it's no longer relevant
2635 files.update(ctx.files())
2646 files.update(ctx.files())
2636
2647
2637 def samefile(f):
2648 def samefile(f):
2638 if f in ctx.manifest():
2649 if f in ctx.manifest():
2639 a = ctx.filectx(f)
2650 a = ctx.filectx(f)
2640 if f in base.manifest():
2651 if f in base.manifest():
2641 b = base.filectx(f)
2652 b = base.filectx(f)
2642 return (not a.cmp(b)
2653 return (not a.cmp(b)
2643 and a.flags() == b.flags())
2654 and a.flags() == b.flags())
2644 else:
2655 else:
2645 return False
2656 return False
2646 else:
2657 else:
2647 return f not in base.manifest()
2658 return f not in base.manifest()
2648 files = [f for f in files if not samefile(f)]
2659 files = [f for f in files if not samefile(f)]
2649
2660
2650 def filectxfn(repo, ctx_, path):
2661 def filectxfn(repo, ctx_, path):
2651 try:
2662 try:
2652 fctx = ctx[path]
2663 fctx = ctx[path]
2653 flags = fctx.flags()
2664 flags = fctx.flags()
2654 mctx = context.memfilectx(repo,
2665 mctx = context.memfilectx(repo,
2655 fctx.path(), fctx.data(),
2666 fctx.path(), fctx.data(),
2656 islink='l' in flags,
2667 islink='l' in flags,
2657 isexec='x' in flags,
2668 isexec='x' in flags,
2658 copied=copied.get(path))
2669 copied=copied.get(path))
2659 return mctx
2670 return mctx
2660 except KeyError:
2671 except KeyError:
2661 return None
2672 return None
2662 else:
2673 else:
2663 ui.note(_('copying changeset %s to %s\n') % (old, base))
2674 ui.note(_('copying changeset %s to %s\n') % (old, base))
2664
2675
2665 # Use version of files as in the old cset
2676 # Use version of files as in the old cset
2666 def filectxfn(repo, ctx_, path):
2677 def filectxfn(repo, ctx_, path):
2667 try:
2678 try:
2668 return old.filectx(path)
2679 return old.filectx(path)
2669 except KeyError:
2680 except KeyError:
2670 return None
2681 return None
2671
2682
2672 user = opts.get('user') or old.user()
2683 user = opts.get('user') or old.user()
2673 date = opts.get('date') or old.date()
2684 date = opts.get('date') or old.date()
2674 editform = mergeeditform(old, 'commit.amend')
2685 editform = mergeeditform(old, 'commit.amend')
2675 editor = getcommiteditor(editform=editform, **opts)
2686 editor = getcommiteditor(editform=editform, **opts)
2676 if not message:
2687 if not message:
2677 editor = getcommiteditor(edit=True, editform=editform)
2688 editor = getcommiteditor(edit=True, editform=editform)
2678 message = old.description()
2689 message = old.description()
2679
2690
2680 pureextra = extra.copy()
2691 pureextra = extra.copy()
2681 extra['amend_source'] = old.hex()
2692 extra['amend_source'] = old.hex()
2682
2693
2683 new = context.memctx(repo,
2694 new = context.memctx(repo,
2684 parents=[base.node(), old.p2().node()],
2695 parents=[base.node(), old.p2().node()],
2685 text=message,
2696 text=message,
2686 files=files,
2697 files=files,
2687 filectxfn=filectxfn,
2698 filectxfn=filectxfn,
2688 user=user,
2699 user=user,
2689 date=date,
2700 date=date,
2690 extra=extra,
2701 extra=extra,
2691 editor=editor)
2702 editor=editor)
2692
2703
2693 newdesc = changelog.stripdesc(new.description())
2704 newdesc = changelog.stripdesc(new.description())
2694 if ((not node)
2705 if ((not node)
2695 and newdesc == old.description()
2706 and newdesc == old.description()
2696 and user == old.user()
2707 and user == old.user()
2697 and date == old.date()
2708 and date == old.date()
2698 and pureextra == old.extra()):
2709 and pureextra == old.extra()):
2699 # nothing changed. continuing here would create a new node
2710 # nothing changed. continuing here would create a new node
2700 # anyway because of the amend_source noise.
2711 # anyway because of the amend_source noise.
2701 #
2712 #
2702 # This not what we expect from amend.
2713 # This not what we expect from amend.
2703 return old.node()
2714 return old.node()
2704
2715
2705 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2716 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2706 try:
2717 try:
2707 if opts.get('secret'):
2718 if opts.get('secret'):
2708 commitphase = 'secret'
2719 commitphase = 'secret'
2709 else:
2720 else:
2710 commitphase = old.phase()
2721 commitphase = old.phase()
2711 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2722 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2712 newid = repo.commitctx(new)
2723 newid = repo.commitctx(new)
2713 finally:
2724 finally:
2714 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2725 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2715 if newid != old.node():
2726 if newid != old.node():
2716 # Reroute the working copy parent to the new changeset
2727 # Reroute the working copy parent to the new changeset
2717 repo.setparents(newid, nullid)
2728 repo.setparents(newid, nullid)
2718
2729
2719 # Move bookmarks from old parent to amend commit
2730 # Move bookmarks from old parent to amend commit
2720 bms = repo.nodebookmarks(old.node())
2731 bms = repo.nodebookmarks(old.node())
2721 if bms:
2732 if bms:
2722 marks = repo._bookmarks
2733 marks = repo._bookmarks
2723 for bm in bms:
2734 for bm in bms:
2724 ui.debug('moving bookmarks %r from %s to %s\n' %
2735 ui.debug('moving bookmarks %r from %s to %s\n' %
2725 (marks, old.hex(), hex(newid)))
2736 (marks, old.hex(), hex(newid)))
2726 marks[bm] = newid
2737 marks[bm] = newid
2727 marks.recordchange(tr)
2738 marks.recordchange(tr)
2728 #commit the whole amend process
2739 #commit the whole amend process
2729 if createmarkers:
2740 if createmarkers:
2730 # mark the new changeset as successor of the rewritten one
2741 # mark the new changeset as successor of the rewritten one
2731 new = repo[newid]
2742 new = repo[newid]
2732 obs = [(old, (new,))]
2743 obs = [(old, (new,))]
2733 if node:
2744 if node:
2734 obs.append((ctx, ()))
2745 obs.append((ctx, ()))
2735
2746
2736 obsolete.createmarkers(repo, obs)
2747 obsolete.createmarkers(repo, obs)
2737 if not createmarkers and newid != old.node():
2748 if not createmarkers and newid != old.node():
2738 # Strip the intermediate commit (if there was one) and the amended
2749 # Strip the intermediate commit (if there was one) and the amended
2739 # commit
2750 # commit
2740 if node:
2751 if node:
2741 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2752 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2742 ui.note(_('stripping amended changeset %s\n') % old)
2753 ui.note(_('stripping amended changeset %s\n') % old)
2743 repair.strip(ui, repo, old.node(), topic='amend-backup')
2754 repair.strip(ui, repo, old.node(), topic='amend-backup')
2744 finally:
2755 finally:
2745 lockmod.release(lock, wlock)
2756 lockmod.release(lock, wlock)
2746 return newid
2757 return newid
2747
2758
2748 def commiteditor(repo, ctx, subs, editform=''):
2759 def commiteditor(repo, ctx, subs, editform=''):
2749 if ctx.description():
2760 if ctx.description():
2750 return ctx.description()
2761 return ctx.description()
2751 return commitforceeditor(repo, ctx, subs, editform=editform,
2762 return commitforceeditor(repo, ctx, subs, editform=editform,
2752 unchangedmessagedetection=True)
2763 unchangedmessagedetection=True)
2753
2764
2754 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2765 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2755 editform='', unchangedmessagedetection=False):
2766 editform='', unchangedmessagedetection=False):
2756 if not extramsg:
2767 if not extramsg:
2757 extramsg = _("Leave message empty to abort commit.")
2768 extramsg = _("Leave message empty to abort commit.")
2758
2769
2759 forms = [e for e in editform.split('.') if e]
2770 forms = [e for e in editform.split('.') if e]
2760 forms.insert(0, 'changeset')
2771 forms.insert(0, 'changeset')
2761 templatetext = None
2772 templatetext = None
2762 while forms:
2773 while forms:
2763 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2774 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2764 if tmpl:
2775 if tmpl:
2765 templatetext = committext = buildcommittemplate(
2776 templatetext = committext = buildcommittemplate(
2766 repo, ctx, subs, extramsg, tmpl)
2777 repo, ctx, subs, extramsg, tmpl)
2767 break
2778 break
2768 forms.pop()
2779 forms.pop()
2769 else:
2780 else:
2770 committext = buildcommittext(repo, ctx, subs, extramsg)
2781 committext = buildcommittext(repo, ctx, subs, extramsg)
2771
2782
2772 # run editor in the repository root
2783 # run editor in the repository root
2773 olddir = os.getcwd()
2784 olddir = os.getcwd()
2774 os.chdir(repo.root)
2785 os.chdir(repo.root)
2775
2786
2776 # make in-memory changes visible to external process
2787 # make in-memory changes visible to external process
2777 tr = repo.currenttransaction()
2788 tr = repo.currenttransaction()
2778 repo.dirstate.write(tr)
2789 repo.dirstate.write(tr)
2779 pending = tr and tr.writepending() and repo.root
2790 pending = tr and tr.writepending() and repo.root
2780
2791
2781 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2792 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2782 editform=editform, pending=pending)
2793 editform=editform, pending=pending)
2783 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2794 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2784 os.chdir(olddir)
2795 os.chdir(olddir)
2785
2796
2786 if finishdesc:
2797 if finishdesc:
2787 text = finishdesc(text)
2798 text = finishdesc(text)
2788 if not text.strip():
2799 if not text.strip():
2789 raise error.Abort(_("empty commit message"))
2800 raise error.Abort(_("empty commit message"))
2790 if unchangedmessagedetection and editortext == templatetext:
2801 if unchangedmessagedetection and editortext == templatetext:
2791 raise error.Abort(_("commit message unchanged"))
2802 raise error.Abort(_("commit message unchanged"))
2792
2803
2793 return text
2804 return text
2794
2805
2795 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2806 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2796 ui = repo.ui
2807 ui = repo.ui
2797 tmpl, mapfile = gettemplate(ui, tmpl, None)
2808 tmpl, mapfile = gettemplate(ui, tmpl, None)
2798
2809
2799 try:
2810 try:
2800 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2811 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2801 except SyntaxError as inst:
2812 except SyntaxError as inst:
2802 raise error.Abort(inst.args[0])
2813 raise error.Abort(inst.args[0])
2803
2814
2804 for k, v in repo.ui.configitems('committemplate'):
2815 for k, v in repo.ui.configitems('committemplate'):
2805 if k != 'changeset':
2816 if k != 'changeset':
2806 t.t.cache[k] = v
2817 t.t.cache[k] = v
2807
2818
2808 if not extramsg:
2819 if not extramsg:
2809 extramsg = '' # ensure that extramsg is string
2820 extramsg = '' # ensure that extramsg is string
2810
2821
2811 ui.pushbuffer()
2822 ui.pushbuffer()
2812 t.show(ctx, extramsg=extramsg)
2823 t.show(ctx, extramsg=extramsg)
2813 return ui.popbuffer()
2824 return ui.popbuffer()
2814
2825
2815 def hgprefix(msg):
2826 def hgprefix(msg):
2816 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2827 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2817
2828
2818 def buildcommittext(repo, ctx, subs, extramsg):
2829 def buildcommittext(repo, ctx, subs, extramsg):
2819 edittext = []
2830 edittext = []
2820 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2831 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2821 if ctx.description():
2832 if ctx.description():
2822 edittext.append(ctx.description())
2833 edittext.append(ctx.description())
2823 edittext.append("")
2834 edittext.append("")
2824 edittext.append("") # Empty line between message and comments.
2835 edittext.append("") # Empty line between message and comments.
2825 edittext.append(hgprefix(_("Enter commit message."
2836 edittext.append(hgprefix(_("Enter commit message."
2826 " Lines beginning with 'HG:' are removed.")))
2837 " Lines beginning with 'HG:' are removed.")))
2827 edittext.append(hgprefix(extramsg))
2838 edittext.append(hgprefix(extramsg))
2828 edittext.append("HG: --")
2839 edittext.append("HG: --")
2829 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2840 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2830 if ctx.p2():
2841 if ctx.p2():
2831 edittext.append(hgprefix(_("branch merge")))
2842 edittext.append(hgprefix(_("branch merge")))
2832 if ctx.branch():
2843 if ctx.branch():
2833 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2844 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2834 if bookmarks.isactivewdirparent(repo):
2845 if bookmarks.isactivewdirparent(repo):
2835 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2846 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2836 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2847 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2837 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2848 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2838 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2849 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2839 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2850 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2840 if not added and not modified and not removed:
2851 if not added and not modified and not removed:
2841 edittext.append(hgprefix(_("no files changed")))
2852 edittext.append(hgprefix(_("no files changed")))
2842 edittext.append("")
2853 edittext.append("")
2843
2854
2844 return "\n".join(edittext)
2855 return "\n".join(edittext)
2845
2856
2846 def commitstatus(repo, node, branch, bheads=None, opts=None):
2857 def commitstatus(repo, node, branch, bheads=None, opts=None):
2847 if opts is None:
2858 if opts is None:
2848 opts = {}
2859 opts = {}
2849 ctx = repo[node]
2860 ctx = repo[node]
2850 parents = ctx.parents()
2861 parents = ctx.parents()
2851
2862
2852 if (not opts.get('amend') and bheads and node not in bheads and not
2863 if (not opts.get('amend') and bheads and node not in bheads and not
2853 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2864 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2854 repo.ui.status(_('created new head\n'))
2865 repo.ui.status(_('created new head\n'))
2855 # The message is not printed for initial roots. For the other
2866 # The message is not printed for initial roots. For the other
2856 # changesets, it is printed in the following situations:
2867 # changesets, it is printed in the following situations:
2857 #
2868 #
2858 # Par column: for the 2 parents with ...
2869 # Par column: for the 2 parents with ...
2859 # N: null or no parent
2870 # N: null or no parent
2860 # B: parent is on another named branch
2871 # B: parent is on another named branch
2861 # C: parent is a regular non head changeset
2872 # C: parent is a regular non head changeset
2862 # H: parent was a branch head of the current branch
2873 # H: parent was a branch head of the current branch
2863 # Msg column: whether we print "created new head" message
2874 # Msg column: whether we print "created new head" message
2864 # In the following, it is assumed that there already exists some
2875 # In the following, it is assumed that there already exists some
2865 # initial branch heads of the current branch, otherwise nothing is
2876 # initial branch heads of the current branch, otherwise nothing is
2866 # printed anyway.
2877 # printed anyway.
2867 #
2878 #
2868 # Par Msg Comment
2879 # Par Msg Comment
2869 # N N y additional topo root
2880 # N N y additional topo root
2870 #
2881 #
2871 # B N y additional branch root
2882 # B N y additional branch root
2872 # C N y additional topo head
2883 # C N y additional topo head
2873 # H N n usual case
2884 # H N n usual case
2874 #
2885 #
2875 # B B y weird additional branch root
2886 # B B y weird additional branch root
2876 # C B y branch merge
2887 # C B y branch merge
2877 # H B n merge with named branch
2888 # H B n merge with named branch
2878 #
2889 #
2879 # C C y additional head from merge
2890 # C C y additional head from merge
2880 # C H n merge with a head
2891 # C H n merge with a head
2881 #
2892 #
2882 # H H n head merge: head count decreases
2893 # H H n head merge: head count decreases
2883
2894
2884 if not opts.get('close_branch'):
2895 if not opts.get('close_branch'):
2885 for r in parents:
2896 for r in parents:
2886 if r.closesbranch() and r.branch() == branch:
2897 if r.closesbranch() and r.branch() == branch:
2887 repo.ui.status(_('reopening closed branch head %d\n') % r)
2898 repo.ui.status(_('reopening closed branch head %d\n') % r)
2888
2899
2889 if repo.ui.debugflag:
2900 if repo.ui.debugflag:
2890 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2901 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2891 elif repo.ui.verbose:
2902 elif repo.ui.verbose:
2892 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2903 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2893
2904
2894 def postcommitstatus(repo, pats, opts):
2905 def postcommitstatus(repo, pats, opts):
2895 return repo.status(match=scmutil.match(repo[None], pats, opts))
2906 return repo.status(match=scmutil.match(repo[None], pats, opts))
2896
2907
2897 def revert(ui, repo, ctx, parents, *pats, **opts):
2908 def revert(ui, repo, ctx, parents, *pats, **opts):
2898 parent, p2 = parents
2909 parent, p2 = parents
2899 node = ctx.node()
2910 node = ctx.node()
2900
2911
2901 mf = ctx.manifest()
2912 mf = ctx.manifest()
2902 if node == p2:
2913 if node == p2:
2903 parent = p2
2914 parent = p2
2904
2915
2905 # need all matching names in dirstate and manifest of target rev,
2916 # need all matching names in dirstate and manifest of target rev,
2906 # so have to walk both. do not print errors if files exist in one
2917 # so have to walk both. do not print errors if files exist in one
2907 # but not other. in both cases, filesets should be evaluated against
2918 # but not other. in both cases, filesets should be evaluated against
2908 # workingctx to get consistent result (issue4497). this means 'set:**'
2919 # workingctx to get consistent result (issue4497). this means 'set:**'
2909 # cannot be used to select missing files from target rev.
2920 # cannot be used to select missing files from target rev.
2910
2921
2911 # `names` is a mapping for all elements in working copy and target revision
2922 # `names` is a mapping for all elements in working copy and target revision
2912 # The mapping is in the form:
2923 # The mapping is in the form:
2913 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2924 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2914 names = {}
2925 names = {}
2915
2926
2916 with repo.wlock():
2927 with repo.wlock():
2917 ## filling of the `names` mapping
2928 ## filling of the `names` mapping
2918 # walk dirstate to fill `names`
2929 # walk dirstate to fill `names`
2919
2930
2920 interactive = opts.get('interactive', False)
2931 interactive = opts.get('interactive', False)
2921 wctx = repo[None]
2932 wctx = repo[None]
2922 m = scmutil.match(wctx, pats, opts)
2933 m = scmutil.match(wctx, pats, opts)
2923
2934
2924 # we'll need this later
2935 # we'll need this later
2925 targetsubs = sorted(s for s in wctx.substate if m(s))
2936 targetsubs = sorted(s for s in wctx.substate if m(s))
2926
2937
2927 if not m.always():
2938 if not m.always():
2928 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2939 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2929 names[abs] = m.rel(abs), m.exact(abs)
2940 names[abs] = m.rel(abs), m.exact(abs)
2930
2941
2931 # walk target manifest to fill `names`
2942 # walk target manifest to fill `names`
2932
2943
2933 def badfn(path, msg):
2944 def badfn(path, msg):
2934 if path in names:
2945 if path in names:
2935 return
2946 return
2936 if path in ctx.substate:
2947 if path in ctx.substate:
2937 return
2948 return
2938 path_ = path + '/'
2949 path_ = path + '/'
2939 for f in names:
2950 for f in names:
2940 if f.startswith(path_):
2951 if f.startswith(path_):
2941 return
2952 return
2942 ui.warn("%s: %s\n" % (m.rel(path), msg))
2953 ui.warn("%s: %s\n" % (m.rel(path), msg))
2943
2954
2944 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2955 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2945 if abs not in names:
2956 if abs not in names:
2946 names[abs] = m.rel(abs), m.exact(abs)
2957 names[abs] = m.rel(abs), m.exact(abs)
2947
2958
2948 # Find status of all file in `names`.
2959 # Find status of all file in `names`.
2949 m = scmutil.matchfiles(repo, names)
2960 m = scmutil.matchfiles(repo, names)
2950
2961
2951 changes = repo.status(node1=node, match=m,
2962 changes = repo.status(node1=node, match=m,
2952 unknown=True, ignored=True, clean=True)
2963 unknown=True, ignored=True, clean=True)
2953 else:
2964 else:
2954 changes = repo.status(node1=node, match=m)
2965 changes = repo.status(node1=node, match=m)
2955 for kind in changes:
2966 for kind in changes:
2956 for abs in kind:
2967 for abs in kind:
2957 names[abs] = m.rel(abs), m.exact(abs)
2968 names[abs] = m.rel(abs), m.exact(abs)
2958
2969
2959 m = scmutil.matchfiles(repo, names)
2970 m = scmutil.matchfiles(repo, names)
2960
2971
2961 modified = set(changes.modified)
2972 modified = set(changes.modified)
2962 added = set(changes.added)
2973 added = set(changes.added)
2963 removed = set(changes.removed)
2974 removed = set(changes.removed)
2964 _deleted = set(changes.deleted)
2975 _deleted = set(changes.deleted)
2965 unknown = set(changes.unknown)
2976 unknown = set(changes.unknown)
2966 unknown.update(changes.ignored)
2977 unknown.update(changes.ignored)
2967 clean = set(changes.clean)
2978 clean = set(changes.clean)
2968 modadded = set()
2979 modadded = set()
2969
2980
2970 # split between files known in target manifest and the others
2981 # split between files known in target manifest and the others
2971 smf = set(mf)
2982 smf = set(mf)
2972
2983
2973 # determine the exact nature of the deleted changesets
2984 # determine the exact nature of the deleted changesets
2974 deladded = _deleted - smf
2985 deladded = _deleted - smf
2975 deleted = _deleted - deladded
2986 deleted = _deleted - deladded
2976
2987
2977 # We need to account for the state of the file in the dirstate,
2988 # We need to account for the state of the file in the dirstate,
2978 # even when we revert against something else than parent. This will
2989 # even when we revert against something else than parent. This will
2979 # slightly alter the behavior of revert (doing back up or not, delete
2990 # slightly alter the behavior of revert (doing back up or not, delete
2980 # or just forget etc).
2991 # or just forget etc).
2981 if parent == node:
2992 if parent == node:
2982 dsmodified = modified
2993 dsmodified = modified
2983 dsadded = added
2994 dsadded = added
2984 dsremoved = removed
2995 dsremoved = removed
2985 # store all local modifications, useful later for rename detection
2996 # store all local modifications, useful later for rename detection
2986 localchanges = dsmodified | dsadded
2997 localchanges = dsmodified | dsadded
2987 modified, added, removed = set(), set(), set()
2998 modified, added, removed = set(), set(), set()
2988 else:
2999 else:
2989 changes = repo.status(node1=parent, match=m)
3000 changes = repo.status(node1=parent, match=m)
2990 dsmodified = set(changes.modified)
3001 dsmodified = set(changes.modified)
2991 dsadded = set(changes.added)
3002 dsadded = set(changes.added)
2992 dsremoved = set(changes.removed)
3003 dsremoved = set(changes.removed)
2993 # store all local modifications, useful later for rename detection
3004 # store all local modifications, useful later for rename detection
2994 localchanges = dsmodified | dsadded
3005 localchanges = dsmodified | dsadded
2995
3006
2996 # only take into account for removes between wc and target
3007 # only take into account for removes between wc and target
2997 clean |= dsremoved - removed
3008 clean |= dsremoved - removed
2998 dsremoved &= removed
3009 dsremoved &= removed
2999 # distinct between dirstate remove and other
3010 # distinct between dirstate remove and other
3000 removed -= dsremoved
3011 removed -= dsremoved
3001
3012
3002 modadded = added & dsmodified
3013 modadded = added & dsmodified
3003 added -= modadded
3014 added -= modadded
3004
3015
3005 # tell newly modified apart.
3016 # tell newly modified apart.
3006 dsmodified &= modified
3017 dsmodified &= modified
3007 dsmodified |= modified & dsadded # dirstate added may needs backup
3018 dsmodified |= modified & dsadded # dirstate added may needs backup
3008 modified -= dsmodified
3019 modified -= dsmodified
3009
3020
3010 # We need to wait for some post-processing to update this set
3021 # We need to wait for some post-processing to update this set
3011 # before making the distinction. The dirstate will be used for
3022 # before making the distinction. The dirstate will be used for
3012 # that purpose.
3023 # that purpose.
3013 dsadded = added
3024 dsadded = added
3014
3025
3015 # in case of merge, files that are actually added can be reported as
3026 # in case of merge, files that are actually added can be reported as
3016 # modified, we need to post process the result
3027 # modified, we need to post process the result
3017 if p2 != nullid:
3028 if p2 != nullid:
3018 mergeadd = dsmodified - smf
3029 mergeadd = dsmodified - smf
3019 dsadded |= mergeadd
3030 dsadded |= mergeadd
3020 dsmodified -= mergeadd
3031 dsmodified -= mergeadd
3021
3032
3022 # if f is a rename, update `names` to also revert the source
3033 # if f is a rename, update `names` to also revert the source
3023 cwd = repo.getcwd()
3034 cwd = repo.getcwd()
3024 for f in localchanges:
3035 for f in localchanges:
3025 src = repo.dirstate.copied(f)
3036 src = repo.dirstate.copied(f)
3026 # XXX should we check for rename down to target node?
3037 # XXX should we check for rename down to target node?
3027 if src and src not in names and repo.dirstate[src] == 'r':
3038 if src and src not in names and repo.dirstate[src] == 'r':
3028 dsremoved.add(src)
3039 dsremoved.add(src)
3029 names[src] = (repo.pathto(src, cwd), True)
3040 names[src] = (repo.pathto(src, cwd), True)
3030
3041
3031 # distinguish between file to forget and the other
3042 # distinguish between file to forget and the other
3032 added = set()
3043 added = set()
3033 for abs in dsadded:
3044 for abs in dsadded:
3034 if repo.dirstate[abs] != 'a':
3045 if repo.dirstate[abs] != 'a':
3035 added.add(abs)
3046 added.add(abs)
3036 dsadded -= added
3047 dsadded -= added
3037
3048
3038 for abs in deladded:
3049 for abs in deladded:
3039 if repo.dirstate[abs] == 'a':
3050 if repo.dirstate[abs] == 'a':
3040 dsadded.add(abs)
3051 dsadded.add(abs)
3041 deladded -= dsadded
3052 deladded -= dsadded
3042
3053
3043 # For files marked as removed, we check if an unknown file is present at
3054 # For files marked as removed, we check if an unknown file is present at
3044 # the same path. If a such file exists it may need to be backed up.
3055 # the same path. If a such file exists it may need to be backed up.
3045 # Making the distinction at this stage helps have simpler backup
3056 # Making the distinction at this stage helps have simpler backup
3046 # logic.
3057 # logic.
3047 removunk = set()
3058 removunk = set()
3048 for abs in removed:
3059 for abs in removed:
3049 target = repo.wjoin(abs)
3060 target = repo.wjoin(abs)
3050 if os.path.lexists(target):
3061 if os.path.lexists(target):
3051 removunk.add(abs)
3062 removunk.add(abs)
3052 removed -= removunk
3063 removed -= removunk
3053
3064
3054 dsremovunk = set()
3065 dsremovunk = set()
3055 for abs in dsremoved:
3066 for abs in dsremoved:
3056 target = repo.wjoin(abs)
3067 target = repo.wjoin(abs)
3057 if os.path.lexists(target):
3068 if os.path.lexists(target):
3058 dsremovunk.add(abs)
3069 dsremovunk.add(abs)
3059 dsremoved -= dsremovunk
3070 dsremoved -= dsremovunk
3060
3071
3061 # action to be actually performed by revert
3072 # action to be actually performed by revert
3062 # (<list of file>, message>) tuple
3073 # (<list of file>, message>) tuple
3063 actions = {'revert': ([], _('reverting %s\n')),
3074 actions = {'revert': ([], _('reverting %s\n')),
3064 'add': ([], _('adding %s\n')),
3075 'add': ([], _('adding %s\n')),
3065 'remove': ([], _('removing %s\n')),
3076 'remove': ([], _('removing %s\n')),
3066 'drop': ([], _('removing %s\n')),
3077 'drop': ([], _('removing %s\n')),
3067 'forget': ([], _('forgetting %s\n')),
3078 'forget': ([], _('forgetting %s\n')),
3068 'undelete': ([], _('undeleting %s\n')),
3079 'undelete': ([], _('undeleting %s\n')),
3069 'noop': (None, _('no changes needed to %s\n')),
3080 'noop': (None, _('no changes needed to %s\n')),
3070 'unknown': (None, _('file not managed: %s\n')),
3081 'unknown': (None, _('file not managed: %s\n')),
3071 }
3082 }
3072
3083
3073 # "constant" that convey the backup strategy.
3084 # "constant" that convey the backup strategy.
3074 # All set to `discard` if `no-backup` is set do avoid checking
3085 # All set to `discard` if `no-backup` is set do avoid checking
3075 # no_backup lower in the code.
3086 # no_backup lower in the code.
3076 # These values are ordered for comparison purposes
3087 # These values are ordered for comparison purposes
3077 backup = 2 # unconditionally do backup
3088 backup = 2 # unconditionally do backup
3078 check = 1 # check if the existing file differs from target
3089 check = 1 # check if the existing file differs from target
3079 discard = 0 # never do backup
3090 discard = 0 # never do backup
3080 if opts.get('no_backup'):
3091 if opts.get('no_backup'):
3081 backup = check = discard
3092 backup = check = discard
3082
3093
3083 backupanddel = actions['remove']
3094 backupanddel = actions['remove']
3084 if not opts.get('no_backup'):
3095 if not opts.get('no_backup'):
3085 backupanddel = actions['drop']
3096 backupanddel = actions['drop']
3086
3097
3087 disptable = (
3098 disptable = (
3088 # dispatch table:
3099 # dispatch table:
3089 # file state
3100 # file state
3090 # action
3101 # action
3091 # make backup
3102 # make backup
3092
3103
3093 ## Sets that results that will change file on disk
3104 ## Sets that results that will change file on disk
3094 # Modified compared to target, no local change
3105 # Modified compared to target, no local change
3095 (modified, actions['revert'], discard),
3106 (modified, actions['revert'], discard),
3096 # Modified compared to target, but local file is deleted
3107 # Modified compared to target, but local file is deleted
3097 (deleted, actions['revert'], discard),
3108 (deleted, actions['revert'], discard),
3098 # Modified compared to target, local change
3109 # Modified compared to target, local change
3099 (dsmodified, actions['revert'], backup),
3110 (dsmodified, actions['revert'], backup),
3100 # Added since target
3111 # Added since target
3101 (added, actions['remove'], discard),
3112 (added, actions['remove'], discard),
3102 # Added in working directory
3113 # Added in working directory
3103 (dsadded, actions['forget'], discard),
3114 (dsadded, actions['forget'], discard),
3104 # Added since target, have local modification
3115 # Added since target, have local modification
3105 (modadded, backupanddel, backup),
3116 (modadded, backupanddel, backup),
3106 # Added since target but file is missing in working directory
3117 # Added since target but file is missing in working directory
3107 (deladded, actions['drop'], discard),
3118 (deladded, actions['drop'], discard),
3108 # Removed since target, before working copy parent
3119 # Removed since target, before working copy parent
3109 (removed, actions['add'], discard),
3120 (removed, actions['add'], discard),
3110 # Same as `removed` but an unknown file exists at the same path
3121 # Same as `removed` but an unknown file exists at the same path
3111 (removunk, actions['add'], check),
3122 (removunk, actions['add'], check),
3112 # Removed since targe, marked as such in working copy parent
3123 # Removed since targe, marked as such in working copy parent
3113 (dsremoved, actions['undelete'], discard),
3124 (dsremoved, actions['undelete'], discard),
3114 # Same as `dsremoved` but an unknown file exists at the same path
3125 # Same as `dsremoved` but an unknown file exists at the same path
3115 (dsremovunk, actions['undelete'], check),
3126 (dsremovunk, actions['undelete'], check),
3116 ## the following sets does not result in any file changes
3127 ## the following sets does not result in any file changes
3117 # File with no modification
3128 # File with no modification
3118 (clean, actions['noop'], discard),
3129 (clean, actions['noop'], discard),
3119 # Existing file, not tracked anywhere
3130 # Existing file, not tracked anywhere
3120 (unknown, actions['unknown'], discard),
3131 (unknown, actions['unknown'], discard),
3121 )
3132 )
3122
3133
3123 for abs, (rel, exact) in sorted(names.items()):
3134 for abs, (rel, exact) in sorted(names.items()):
3124 # target file to be touch on disk (relative to cwd)
3135 # target file to be touch on disk (relative to cwd)
3125 target = repo.wjoin(abs)
3136 target = repo.wjoin(abs)
3126 # search the entry in the dispatch table.
3137 # search the entry in the dispatch table.
3127 # if the file is in any of these sets, it was touched in the working
3138 # if the file is in any of these sets, it was touched in the working
3128 # directory parent and we are sure it needs to be reverted.
3139 # directory parent and we are sure it needs to be reverted.
3129 for table, (xlist, msg), dobackup in disptable:
3140 for table, (xlist, msg), dobackup in disptable:
3130 if abs not in table:
3141 if abs not in table:
3131 continue
3142 continue
3132 if xlist is not None:
3143 if xlist is not None:
3133 xlist.append(abs)
3144 xlist.append(abs)
3134 if dobackup and (backup <= dobackup
3145 if dobackup and (backup <= dobackup
3135 or wctx[abs].cmp(ctx[abs])):
3146 or wctx[abs].cmp(ctx[abs])):
3136 bakname = scmutil.origpath(ui, repo, rel)
3147 bakname = scmutil.origpath(ui, repo, rel)
3137 ui.note(_('saving current version of %s as %s\n') %
3148 ui.note(_('saving current version of %s as %s\n') %
3138 (rel, bakname))
3149 (rel, bakname))
3139 if not opts.get('dry_run'):
3150 if not opts.get('dry_run'):
3140 if interactive:
3151 if interactive:
3141 util.copyfile(target, bakname)
3152 util.copyfile(target, bakname)
3142 else:
3153 else:
3143 util.rename(target, bakname)
3154 util.rename(target, bakname)
3144 if ui.verbose or not exact:
3155 if ui.verbose or not exact:
3145 if not isinstance(msg, basestring):
3156 if not isinstance(msg, basestring):
3146 msg = msg(abs)
3157 msg = msg(abs)
3147 ui.status(msg % rel)
3158 ui.status(msg % rel)
3148 elif exact:
3159 elif exact:
3149 ui.warn(msg % rel)
3160 ui.warn(msg % rel)
3150 break
3161 break
3151
3162
3152 if not opts.get('dry_run'):
3163 if not opts.get('dry_run'):
3153 needdata = ('revert', 'add', 'undelete')
3164 needdata = ('revert', 'add', 'undelete')
3154 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3165 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3155 _performrevert(repo, parents, ctx, actions, interactive)
3166 _performrevert(repo, parents, ctx, actions, interactive)
3156
3167
3157 if targetsubs:
3168 if targetsubs:
3158 # Revert the subrepos on the revert list
3169 # Revert the subrepos on the revert list
3159 for sub in targetsubs:
3170 for sub in targetsubs:
3160 try:
3171 try:
3161 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3172 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3162 except KeyError:
3173 except KeyError:
3163 raise error.Abort("subrepository '%s' does not exist in %s!"
3174 raise error.Abort("subrepository '%s' does not exist in %s!"
3164 % (sub, short(ctx.node())))
3175 % (sub, short(ctx.node())))
3165
3176
3166 def _revertprefetch(repo, ctx, *files):
3177 def _revertprefetch(repo, ctx, *files):
3167 """Let extension changing the storage layer prefetch content"""
3178 """Let extension changing the storage layer prefetch content"""
3168 pass
3179 pass
3169
3180
3170 def _performrevert(repo, parents, ctx, actions, interactive=False):
3181 def _performrevert(repo, parents, ctx, actions, interactive=False):
3171 """function that actually perform all the actions computed for revert
3182 """function that actually perform all the actions computed for revert
3172
3183
3173 This is an independent function to let extension to plug in and react to
3184 This is an independent function to let extension to plug in and react to
3174 the imminent revert.
3185 the imminent revert.
3175
3186
3176 Make sure you have the working directory locked when calling this function.
3187 Make sure you have the working directory locked when calling this function.
3177 """
3188 """
3178 parent, p2 = parents
3189 parent, p2 = parents
3179 node = ctx.node()
3190 node = ctx.node()
3180 excluded_files = []
3191 excluded_files = []
3181 matcher_opts = {"exclude": excluded_files}
3192 matcher_opts = {"exclude": excluded_files}
3182
3193
3183 def checkout(f):
3194 def checkout(f):
3184 fc = ctx[f]
3195 fc = ctx[f]
3185 repo.wwrite(f, fc.data(), fc.flags())
3196 repo.wwrite(f, fc.data(), fc.flags())
3186
3197
3187 audit_path = pathutil.pathauditor(repo.root)
3198 audit_path = pathutil.pathauditor(repo.root)
3188 for f in actions['forget'][0]:
3199 for f in actions['forget'][0]:
3189 if interactive:
3200 if interactive:
3190 choice = \
3201 choice = \
3191 repo.ui.promptchoice(
3202 repo.ui.promptchoice(
3192 _("forget added file %s (yn)?$$ &Yes $$ &No")
3203 _("forget added file %s (yn)?$$ &Yes $$ &No")
3193 % f)
3204 % f)
3194 if choice == 0:
3205 if choice == 0:
3195 repo.dirstate.drop(f)
3206 repo.dirstate.drop(f)
3196 else:
3207 else:
3197 excluded_files.append(repo.wjoin(f))
3208 excluded_files.append(repo.wjoin(f))
3198 else:
3209 else:
3199 repo.dirstate.drop(f)
3210 repo.dirstate.drop(f)
3200 for f in actions['remove'][0]:
3211 for f in actions['remove'][0]:
3201 audit_path(f)
3212 audit_path(f)
3202 try:
3213 try:
3203 util.unlinkpath(repo.wjoin(f))
3214 util.unlinkpath(repo.wjoin(f))
3204 except OSError:
3215 except OSError:
3205 pass
3216 pass
3206 repo.dirstate.remove(f)
3217 repo.dirstate.remove(f)
3207 for f in actions['drop'][0]:
3218 for f in actions['drop'][0]:
3208 audit_path(f)
3219 audit_path(f)
3209 repo.dirstate.remove(f)
3220 repo.dirstate.remove(f)
3210
3221
3211 normal = None
3222 normal = None
3212 if node == parent:
3223 if node == parent:
3213 # We're reverting to our parent. If possible, we'd like status
3224 # We're reverting to our parent. If possible, we'd like status
3214 # to report the file as clean. We have to use normallookup for
3225 # to report the file as clean. We have to use normallookup for
3215 # merges to avoid losing information about merged/dirty files.
3226 # merges to avoid losing information about merged/dirty files.
3216 if p2 != nullid:
3227 if p2 != nullid:
3217 normal = repo.dirstate.normallookup
3228 normal = repo.dirstate.normallookup
3218 else:
3229 else:
3219 normal = repo.dirstate.normal
3230 normal = repo.dirstate.normal
3220
3231
3221 newlyaddedandmodifiedfiles = set()
3232 newlyaddedandmodifiedfiles = set()
3222 if interactive:
3233 if interactive:
3223 # Prompt the user for changes to revert
3234 # Prompt the user for changes to revert
3224 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3235 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3225 m = scmutil.match(ctx, torevert, matcher_opts)
3236 m = scmutil.match(ctx, torevert, matcher_opts)
3226 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3237 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3227 diffopts.nodates = True
3238 diffopts.nodates = True
3228 diffopts.git = True
3239 diffopts.git = True
3229 reversehunks = repo.ui.configbool('experimental',
3240 reversehunks = repo.ui.configbool('experimental',
3230 'revertalternateinteractivemode',
3241 'revertalternateinteractivemode',
3231 True)
3242 True)
3232 if reversehunks:
3243 if reversehunks:
3233 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3244 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3234 else:
3245 else:
3235 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3246 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3236 originalchunks = patch.parsepatch(diff)
3247 originalchunks = patch.parsepatch(diff)
3237
3248
3238 try:
3249 try:
3239
3250
3240 chunks, opts = recordfilter(repo.ui, originalchunks)
3251 chunks, opts = recordfilter(repo.ui, originalchunks)
3241 if reversehunks:
3252 if reversehunks:
3242 chunks = patch.reversehunks(chunks)
3253 chunks = patch.reversehunks(chunks)
3243
3254
3244 except patch.PatchError as err:
3255 except patch.PatchError as err:
3245 raise error.Abort(_('error parsing patch: %s') % err)
3256 raise error.Abort(_('error parsing patch: %s') % err)
3246
3257
3247 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3258 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3248 # Apply changes
3259 # Apply changes
3249 fp = cStringIO.StringIO()
3260 fp = cStringIO.StringIO()
3250 for c in chunks:
3261 for c in chunks:
3251 c.write(fp)
3262 c.write(fp)
3252 dopatch = fp.tell()
3263 dopatch = fp.tell()
3253 fp.seek(0)
3264 fp.seek(0)
3254 if dopatch:
3265 if dopatch:
3255 try:
3266 try:
3256 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3267 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3257 except patch.PatchError as err:
3268 except patch.PatchError as err:
3258 raise error.Abort(str(err))
3269 raise error.Abort(str(err))
3259 del fp
3270 del fp
3260 else:
3271 else:
3261 for f in actions['revert'][0]:
3272 for f in actions['revert'][0]:
3262 checkout(f)
3273 checkout(f)
3263 if normal:
3274 if normal:
3264 normal(f)
3275 normal(f)
3265
3276
3266 for f in actions['add'][0]:
3277 for f in actions['add'][0]:
3267 # Don't checkout modified files, they are already created by the diff
3278 # Don't checkout modified files, they are already created by the diff
3268 if f not in newlyaddedandmodifiedfiles:
3279 if f not in newlyaddedandmodifiedfiles:
3269 checkout(f)
3280 checkout(f)
3270 repo.dirstate.add(f)
3281 repo.dirstate.add(f)
3271
3282
3272 normal = repo.dirstate.normallookup
3283 normal = repo.dirstate.normallookup
3273 if node == parent and p2 == nullid:
3284 if node == parent and p2 == nullid:
3274 normal = repo.dirstate.normal
3285 normal = repo.dirstate.normal
3275 for f in actions['undelete'][0]:
3286 for f in actions['undelete'][0]:
3276 checkout(f)
3287 checkout(f)
3277 normal(f)
3288 normal(f)
3278
3289
3279 copied = copies.pathcopies(repo[parent], ctx)
3290 copied = copies.pathcopies(repo[parent], ctx)
3280
3291
3281 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3292 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3282 if f in copied:
3293 if f in copied:
3283 repo.dirstate.copy(copied[f], f)
3294 repo.dirstate.copy(copied[f], f)
3284
3295
3285 def command(table):
3296 def command(table):
3286 """Returns a function object to be used as a decorator for making commands.
3297 """Returns a function object to be used as a decorator for making commands.
3287
3298
3288 This function receives a command table as its argument. The table should
3299 This function receives a command table as its argument. The table should
3289 be a dict.
3300 be a dict.
3290
3301
3291 The returned function can be used as a decorator for adding commands
3302 The returned function can be used as a decorator for adding commands
3292 to that command table. This function accepts multiple arguments to define
3303 to that command table. This function accepts multiple arguments to define
3293 a command.
3304 a command.
3294
3305
3295 The first argument is the command name.
3306 The first argument is the command name.
3296
3307
3297 The options argument is an iterable of tuples defining command arguments.
3308 The options argument is an iterable of tuples defining command arguments.
3298 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3309 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3299
3310
3300 The synopsis argument defines a short, one line summary of how to use the
3311 The synopsis argument defines a short, one line summary of how to use the
3301 command. This shows up in the help output.
3312 command. This shows up in the help output.
3302
3313
3303 The norepo argument defines whether the command does not require a
3314 The norepo argument defines whether the command does not require a
3304 local repository. Most commands operate against a repository, thus the
3315 local repository. Most commands operate against a repository, thus the
3305 default is False.
3316 default is False.
3306
3317
3307 The optionalrepo argument defines whether the command optionally requires
3318 The optionalrepo argument defines whether the command optionally requires
3308 a local repository.
3319 a local repository.
3309
3320
3310 The inferrepo argument defines whether to try to find a repository from the
3321 The inferrepo argument defines whether to try to find a repository from the
3311 command line arguments. If True, arguments will be examined for potential
3322 command line arguments. If True, arguments will be examined for potential
3312 repository locations. See ``findrepo()``. If a repository is found, it
3323 repository locations. See ``findrepo()``. If a repository is found, it
3313 will be used.
3324 will be used.
3314 """
3325 """
3315 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3326 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3316 inferrepo=False):
3327 inferrepo=False):
3317 def decorator(func):
3328 def decorator(func):
3318 func.norepo = norepo
3329 func.norepo = norepo
3319 func.optionalrepo = optionalrepo
3330 func.optionalrepo = optionalrepo
3320 func.inferrepo = inferrepo
3331 func.inferrepo = inferrepo
3321 if synopsis:
3332 if synopsis:
3322 table[name] = func, list(options), synopsis
3333 table[name] = func, list(options), synopsis
3323 else:
3334 else:
3324 table[name] = func, list(options)
3335 table[name] = func, list(options)
3325 return func
3336 return func
3326 return decorator
3337 return decorator
3327
3338
3328 return cmd
3339 return cmd
3329
3340
3330 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3341 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3331 # commands.outgoing. "missing" is "missing" of the result of
3342 # commands.outgoing. "missing" is "missing" of the result of
3332 # "findcommonoutgoing()"
3343 # "findcommonoutgoing()"
3333 outgoinghooks = util.hooks()
3344 outgoinghooks = util.hooks()
3334
3345
3335 # a list of (ui, repo) functions called by commands.summary
3346 # a list of (ui, repo) functions called by commands.summary
3336 summaryhooks = util.hooks()
3347 summaryhooks = util.hooks()
3337
3348
3338 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3349 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3339 #
3350 #
3340 # functions should return tuple of booleans below, if 'changes' is None:
3351 # functions should return tuple of booleans below, if 'changes' is None:
3341 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3352 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3342 #
3353 #
3343 # otherwise, 'changes' is a tuple of tuples below:
3354 # otherwise, 'changes' is a tuple of tuples below:
3344 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3355 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3345 # - (desturl, destbranch, destpeer, outgoing)
3356 # - (desturl, destbranch, destpeer, outgoing)
3346 summaryremotehooks = util.hooks()
3357 summaryremotehooks = util.hooks()
3347
3358
3348 # A list of state files kept by multistep operations like graft.
3359 # A list of state files kept by multistep operations like graft.
3349 # Since graft cannot be aborted, it is considered 'clearable' by update.
3360 # Since graft cannot be aborted, it is considered 'clearable' by update.
3350 # note: bisect is intentionally excluded
3361 # note: bisect is intentionally excluded
3351 # (state file, clearable, allowcommit, error, hint)
3362 # (state file, clearable, allowcommit, error, hint)
3352 unfinishedstates = [
3363 unfinishedstates = [
3353 ('graftstate', True, False, _('graft in progress'),
3364 ('graftstate', True, False, _('graft in progress'),
3354 _("use 'hg graft --continue' or 'hg update' to abort")),
3365 _("use 'hg graft --continue' or 'hg update' to abort")),
3355 ('updatestate', True, False, _('last update was interrupted'),
3366 ('updatestate', True, False, _('last update was interrupted'),
3356 _("use 'hg update' to get a consistent checkout"))
3367 _("use 'hg update' to get a consistent checkout"))
3357 ]
3368 ]
3358
3369
3359 def checkunfinished(repo, commit=False):
3370 def checkunfinished(repo, commit=False):
3360 '''Look for an unfinished multistep operation, like graft, and abort
3371 '''Look for an unfinished multistep operation, like graft, and abort
3361 if found. It's probably good to check this right before
3372 if found. It's probably good to check this right before
3362 bailifchanged().
3373 bailifchanged().
3363 '''
3374 '''
3364 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3375 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3365 if commit and allowcommit:
3376 if commit and allowcommit:
3366 continue
3377 continue
3367 if repo.vfs.exists(f):
3378 if repo.vfs.exists(f):
3368 raise error.Abort(msg, hint=hint)
3379 raise error.Abort(msg, hint=hint)
3369
3380
3370 def clearunfinished(repo):
3381 def clearunfinished(repo):
3371 '''Check for unfinished operations (as above), and clear the ones
3382 '''Check for unfinished operations (as above), and clear the ones
3372 that are clearable.
3383 that are clearable.
3373 '''
3384 '''
3374 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3385 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3375 if not clearable and repo.vfs.exists(f):
3386 if not clearable and repo.vfs.exists(f):
3376 raise error.Abort(msg, hint=hint)
3387 raise error.Abort(msg, hint=hint)
3377 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3388 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3378 if clearable and repo.vfs.exists(f):
3389 if clearable and repo.vfs.exists(f):
3379 util.unlink(repo.join(f))
3390 util.unlink(repo.join(f))
3380
3391
3381 afterresolvedstates = [
3392 afterresolvedstates = [
3382 ('graftstate',
3393 ('graftstate',
3383 _('hg graft --continue')),
3394 _('hg graft --continue')),
3384 ]
3395 ]
3385
3396
3386 def howtocontinue(repo):
3397 def howtocontinue(repo):
3387 '''Check for an unfinished operation and return the command to finish
3398 '''Check for an unfinished operation and return the command to finish
3388 it.
3399 it.
3389
3400
3390 afterresolvedstates tupples define a .hg/{file} and the corresponding
3401 afterresolvedstates tupples define a .hg/{file} and the corresponding
3391 command needed to finish it.
3402 command needed to finish it.
3392
3403
3393 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3404 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3394 a boolean.
3405 a boolean.
3395 '''
3406 '''
3396 contmsg = _("continue: %s")
3407 contmsg = _("continue: %s")
3397 for f, msg in afterresolvedstates:
3408 for f, msg in afterresolvedstates:
3398 if repo.vfs.exists(f):
3409 if repo.vfs.exists(f):
3399 return contmsg % msg, True
3410 return contmsg % msg, True
3400 workingctx = repo[None]
3411 workingctx = repo[None]
3401 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3412 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3402 for s in workingctx.substate)
3413 for s in workingctx.substate)
3403 if dirty:
3414 if dirty:
3404 return contmsg % _("hg commit"), False
3415 return contmsg % _("hg commit"), False
3405 return None, None
3416 return None, None
3406
3417
3407 def checkafterresolved(repo):
3418 def checkafterresolved(repo):
3408 '''Inform the user about the next action after completing hg resolve
3419 '''Inform the user about the next action after completing hg resolve
3409
3420
3410 If there's a matching afterresolvedstates, howtocontinue will yield
3421 If there's a matching afterresolvedstates, howtocontinue will yield
3411 repo.ui.warn as the reporter.
3422 repo.ui.warn as the reporter.
3412
3423
3413 Otherwise, it will yield repo.ui.note.
3424 Otherwise, it will yield repo.ui.note.
3414 '''
3425 '''
3415 msg, warning = howtocontinue(repo)
3426 msg, warning = howtocontinue(repo)
3416 if msg is not None:
3427 if msg is not None:
3417 if warning:
3428 if warning:
3418 repo.ui.warn("%s\n" % msg)
3429 repo.ui.warn("%s\n" % msg)
3419 else:
3430 else:
3420 repo.ui.note("%s\n" % msg)
3431 repo.ui.note("%s\n" % msg)
3421
3432
3422 def wrongtooltocontinue(repo, task):
3433 def wrongtooltocontinue(repo, task):
3423 '''Raise an abort suggesting how to properly continue if there is an
3434 '''Raise an abort suggesting how to properly continue if there is an
3424 active task.
3435 active task.
3425
3436
3426 Uses howtocontinue() to find the active task.
3437 Uses howtocontinue() to find the active task.
3427
3438
3428 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3439 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3429 a hint.
3440 a hint.
3430 '''
3441 '''
3431 after = howtocontinue(repo)
3442 after = howtocontinue(repo)
3432 hint = None
3443 hint = None
3433 if after[1]:
3444 if after[1]:
3434 hint = after[0]
3445 hint = after[0]
3435 raise error.Abort(_('no %s in progress') % task, hint=hint)
3446 raise error.Abort(_('no %s in progress') % task, hint=hint)
3436
3447
3437 class dirstateguard(object):
3448 class dirstateguard(object):
3438 '''Restore dirstate at unexpected failure.
3449 '''Restore dirstate at unexpected failure.
3439
3450
3440 At the construction, this class does:
3451 At the construction, this class does:
3441
3452
3442 - write current ``repo.dirstate`` out, and
3453 - write current ``repo.dirstate`` out, and
3443 - save ``.hg/dirstate`` into the backup file
3454 - save ``.hg/dirstate`` into the backup file
3444
3455
3445 This restores ``.hg/dirstate`` from backup file, if ``release()``
3456 This restores ``.hg/dirstate`` from backup file, if ``release()``
3446 is invoked before ``close()``.
3457 is invoked before ``close()``.
3447
3458
3448 This just removes the backup file at ``close()`` before ``release()``.
3459 This just removes the backup file at ``close()`` before ``release()``.
3449 '''
3460 '''
3450
3461
3451 def __init__(self, repo, name):
3462 def __init__(self, repo, name):
3452 self._repo = repo
3463 self._repo = repo
3453 self._suffix = '.backup.%s.%d' % (name, id(self))
3464 self._suffix = '.backup.%s.%d' % (name, id(self))
3454 repo.dirstate._savebackup(repo.currenttransaction(), self._suffix)
3465 repo.dirstate._savebackup(repo.currenttransaction(), self._suffix)
3455 self._active = True
3466 self._active = True
3456 self._closed = False
3467 self._closed = False
3457
3468
3458 def __del__(self):
3469 def __del__(self):
3459 if self._active: # still active
3470 if self._active: # still active
3460 # this may occur, even if this class is used correctly:
3471 # this may occur, even if this class is used correctly:
3461 # for example, releasing other resources like transaction
3472 # for example, releasing other resources like transaction
3462 # may raise exception before ``dirstateguard.release`` in
3473 # may raise exception before ``dirstateguard.release`` in
3463 # ``release(tr, ....)``.
3474 # ``release(tr, ....)``.
3464 self._abort()
3475 self._abort()
3465
3476
3466 def close(self):
3477 def close(self):
3467 if not self._active: # already inactivated
3478 if not self._active: # already inactivated
3468 msg = (_("can't close already inactivated backup: dirstate%s")
3479 msg = (_("can't close already inactivated backup: dirstate%s")
3469 % self._suffix)
3480 % self._suffix)
3470 raise error.Abort(msg)
3481 raise error.Abort(msg)
3471
3482
3472 self._repo.dirstate._clearbackup(self._repo.currenttransaction(),
3483 self._repo.dirstate._clearbackup(self._repo.currenttransaction(),
3473 self._suffix)
3484 self._suffix)
3474 self._active = False
3485 self._active = False
3475 self._closed = True
3486 self._closed = True
3476
3487
3477 def _abort(self):
3488 def _abort(self):
3478 self._repo.dirstate._restorebackup(self._repo.currenttransaction(),
3489 self._repo.dirstate._restorebackup(self._repo.currenttransaction(),
3479 self._suffix)
3490 self._suffix)
3480 self._active = False
3491 self._active = False
3481
3492
3482 def release(self):
3493 def release(self):
3483 if not self._closed:
3494 if not self._closed:
3484 if not self._active: # already inactivated
3495 if not self._active: # already inactivated
3485 msg = (_("can't release already inactivated backup:"
3496 msg = (_("can't release already inactivated backup:"
3486 " dirstate%s")
3497 " dirstate%s")
3487 % self._suffix)
3498 % self._suffix)
3488 raise error.Abort(msg)
3499 raise error.Abort(msg)
3489 self._abort()
3500 self._abort()
@@ -1,1938 +1,1940 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy
10 import copy
11 import errno
11 import errno
12 import os
12 import os
13 import posixpath
13 import posixpath
14 import re
14 import re
15 import stat
15 import stat
16 import subprocess
16 import subprocess
17 import sys
17 import sys
18 import tarfile
18 import tarfile
19 import xml.dom.minidom
19 import xml.dom.minidom
20
20
21
21
22 from .i18n import _
22 from .i18n import _
23 from . import (
23 from . import (
24 cmdutil,
24 cmdutil,
25 config,
25 config,
26 error,
26 error,
27 exchange,
27 exchange,
28 match as matchmod,
28 match as matchmod,
29 node,
29 node,
30 pathutil,
30 pathutil,
31 phases,
31 phases,
32 scmutil,
32 scmutil,
33 util,
33 util,
34 )
34 )
35
35
36 hg = None
36 hg = None
37 propertycache = util.propertycache
37 propertycache = util.propertycache
38
38
39 nullstate = ('', '', 'empty')
39 nullstate = ('', '', 'empty')
40
40
41 def _expandedabspath(path):
41 def _expandedabspath(path):
42 '''
42 '''
43 get a path or url and if it is a path expand it and return an absolute path
43 get a path or url and if it is a path expand it and return an absolute path
44 '''
44 '''
45 expandedpath = util.urllocalpath(util.expandpath(path))
45 expandedpath = util.urllocalpath(util.expandpath(path))
46 u = util.url(expandedpath)
46 u = util.url(expandedpath)
47 if not u.scheme:
47 if not u.scheme:
48 path = util.normpath(os.path.abspath(u.path))
48 path = util.normpath(os.path.abspath(u.path))
49 return path
49 return path
50
50
51 def _getstorehashcachename(remotepath):
51 def _getstorehashcachename(remotepath):
52 '''get a unique filename for the store hash cache of a remote repository'''
52 '''get a unique filename for the store hash cache of a remote repository'''
53 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
53 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
54
54
55 class SubrepoAbort(error.Abort):
55 class SubrepoAbort(error.Abort):
56 """Exception class used to avoid handling a subrepo error more than once"""
56 """Exception class used to avoid handling a subrepo error more than once"""
57 def __init__(self, *args, **kw):
57 def __init__(self, *args, **kw):
58 error.Abort.__init__(self, *args, **kw)
58 error.Abort.__init__(self, *args, **kw)
59 self.subrepo = kw.get('subrepo')
59 self.subrepo = kw.get('subrepo')
60 self.cause = kw.get('cause')
60 self.cause = kw.get('cause')
61
61
62 def annotatesubrepoerror(func):
62 def annotatesubrepoerror(func):
63 def decoratedmethod(self, *args, **kargs):
63 def decoratedmethod(self, *args, **kargs):
64 try:
64 try:
65 res = func(self, *args, **kargs)
65 res = func(self, *args, **kargs)
66 except SubrepoAbort as ex:
66 except SubrepoAbort as ex:
67 # This exception has already been handled
67 # This exception has already been handled
68 raise ex
68 raise ex
69 except error.Abort as ex:
69 except error.Abort as ex:
70 subrepo = subrelpath(self)
70 subrepo = subrelpath(self)
71 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
71 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
72 # avoid handling this exception by raising a SubrepoAbort exception
72 # avoid handling this exception by raising a SubrepoAbort exception
73 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
73 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
74 cause=sys.exc_info())
74 cause=sys.exc_info())
75 return res
75 return res
76 return decoratedmethod
76 return decoratedmethod
77
77
78 def state(ctx, ui):
78 def state(ctx, ui):
79 """return a state dict, mapping subrepo paths configured in .hgsub
79 """return a state dict, mapping subrepo paths configured in .hgsub
80 to tuple: (source from .hgsub, revision from .hgsubstate, kind
80 to tuple: (source from .hgsub, revision from .hgsubstate, kind
81 (key in types dict))
81 (key in types dict))
82 """
82 """
83 p = config.config()
83 p = config.config()
84 repo = ctx.repo()
84 repo = ctx.repo()
85 def read(f, sections=None, remap=None):
85 def read(f, sections=None, remap=None):
86 if f in ctx:
86 if f in ctx:
87 try:
87 try:
88 data = ctx[f].data()
88 data = ctx[f].data()
89 except IOError as err:
89 except IOError as err:
90 if err.errno != errno.ENOENT:
90 if err.errno != errno.ENOENT:
91 raise
91 raise
92 # handle missing subrepo spec files as removed
92 # handle missing subrepo spec files as removed
93 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
93 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
94 repo.pathto(f))
94 repo.pathto(f))
95 return
95 return
96 p.parse(f, data, sections, remap, read)
96 p.parse(f, data, sections, remap, read)
97 else:
97 else:
98 raise error.Abort(_("subrepo spec file \'%s\' not found") %
98 raise error.Abort(_("subrepo spec file \'%s\' not found") %
99 repo.pathto(f))
99 repo.pathto(f))
100 if '.hgsub' in ctx:
100 if '.hgsub' in ctx:
101 read('.hgsub')
101 read('.hgsub')
102
102
103 for path, src in ui.configitems('subpaths'):
103 for path, src in ui.configitems('subpaths'):
104 p.set('subpaths', path, src, ui.configsource('subpaths', path))
104 p.set('subpaths', path, src, ui.configsource('subpaths', path))
105
105
106 rev = {}
106 rev = {}
107 if '.hgsubstate' in ctx:
107 if '.hgsubstate' in ctx:
108 try:
108 try:
109 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
109 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
110 l = l.lstrip()
110 l = l.lstrip()
111 if not l:
111 if not l:
112 continue
112 continue
113 try:
113 try:
114 revision, path = l.split(" ", 1)
114 revision, path = l.split(" ", 1)
115 except ValueError:
115 except ValueError:
116 raise error.Abort(_("invalid subrepository revision "
116 raise error.Abort(_("invalid subrepository revision "
117 "specifier in \'%s\' line %d")
117 "specifier in \'%s\' line %d")
118 % (repo.pathto('.hgsubstate'), (i + 1)))
118 % (repo.pathto('.hgsubstate'), (i + 1)))
119 rev[path] = revision
119 rev[path] = revision
120 except IOError as err:
120 except IOError as err:
121 if err.errno != errno.ENOENT:
121 if err.errno != errno.ENOENT:
122 raise
122 raise
123
123
124 def remap(src):
124 def remap(src):
125 for pattern, repl in p.items('subpaths'):
125 for pattern, repl in p.items('subpaths'):
126 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
126 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
127 # does a string decode.
127 # does a string decode.
128 repl = repl.encode('string-escape')
128 repl = repl.encode('string-escape')
129 # However, we still want to allow back references to go
129 # However, we still want to allow back references to go
130 # through unharmed, so we turn r'\\1' into r'\1'. Again,
130 # through unharmed, so we turn r'\\1' into r'\1'. Again,
131 # extra escapes are needed because re.sub string decodes.
131 # extra escapes are needed because re.sub string decodes.
132 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
132 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
133 try:
133 try:
134 src = re.sub(pattern, repl, src, 1)
134 src = re.sub(pattern, repl, src, 1)
135 except re.error as e:
135 except re.error as e:
136 raise error.Abort(_("bad subrepository pattern in %s: %s")
136 raise error.Abort(_("bad subrepository pattern in %s: %s")
137 % (p.source('subpaths', pattern), e))
137 % (p.source('subpaths', pattern), e))
138 return src
138 return src
139
139
140 state = {}
140 state = {}
141 for path, src in p[''].items():
141 for path, src in p[''].items():
142 kind = 'hg'
142 kind = 'hg'
143 if src.startswith('['):
143 if src.startswith('['):
144 if ']' not in src:
144 if ']' not in src:
145 raise error.Abort(_('missing ] in subrepo source'))
145 raise error.Abort(_('missing ] in subrepo source'))
146 kind, src = src.split(']', 1)
146 kind, src = src.split(']', 1)
147 kind = kind[1:]
147 kind = kind[1:]
148 src = src.lstrip() # strip any extra whitespace after ']'
148 src = src.lstrip() # strip any extra whitespace after ']'
149
149
150 if not util.url(src).isabs():
150 if not util.url(src).isabs():
151 parent = _abssource(repo, abort=False)
151 parent = _abssource(repo, abort=False)
152 if parent:
152 if parent:
153 parent = util.url(parent)
153 parent = util.url(parent)
154 parent.path = posixpath.join(parent.path or '', src)
154 parent.path = posixpath.join(parent.path or '', src)
155 parent.path = posixpath.normpath(parent.path)
155 parent.path = posixpath.normpath(parent.path)
156 joined = str(parent)
156 joined = str(parent)
157 # Remap the full joined path and use it if it changes,
157 # Remap the full joined path and use it if it changes,
158 # else remap the original source.
158 # else remap the original source.
159 remapped = remap(joined)
159 remapped = remap(joined)
160 if remapped == joined:
160 if remapped == joined:
161 src = remap(src)
161 src = remap(src)
162 else:
162 else:
163 src = remapped
163 src = remapped
164
164
165 src = remap(src)
165 src = remap(src)
166 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
166 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
167
167
168 return state
168 return state
169
169
170 def writestate(repo, state):
170 def writestate(repo, state):
171 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
171 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
172 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
172 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
173 if state[s][1] != nullstate[1]]
173 if state[s][1] != nullstate[1]]
174 repo.wwrite('.hgsubstate', ''.join(lines), '')
174 repo.wwrite('.hgsubstate', ''.join(lines), '')
175
175
176 def submerge(repo, wctx, mctx, actx, overwrite):
176 def submerge(repo, wctx, mctx, actx, overwrite):
177 """delegated from merge.applyupdates: merging of .hgsubstate file
177 """delegated from merge.applyupdates: merging of .hgsubstate file
178 in working context, merging context and ancestor context"""
178 in working context, merging context and ancestor context"""
179 if mctx == actx: # backwards?
179 if mctx == actx: # backwards?
180 actx = wctx.p1()
180 actx = wctx.p1()
181 s1 = wctx.substate
181 s1 = wctx.substate
182 s2 = mctx.substate
182 s2 = mctx.substate
183 sa = actx.substate
183 sa = actx.substate
184 sm = {}
184 sm = {}
185
185
186 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
186 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
187
187
188 def debug(s, msg, r=""):
188 def debug(s, msg, r=""):
189 if r:
189 if r:
190 r = "%s:%s:%s" % r
190 r = "%s:%s:%s" % r
191 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
191 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
192
192
193 for s, l in sorted(s1.iteritems()):
193 for s, l in sorted(s1.iteritems()):
194 a = sa.get(s, nullstate)
194 a = sa.get(s, nullstate)
195 ld = l # local state with possible dirty flag for compares
195 ld = l # local state with possible dirty flag for compares
196 if wctx.sub(s).dirty():
196 if wctx.sub(s).dirty():
197 ld = (l[0], l[1] + "+")
197 ld = (l[0], l[1] + "+")
198 if wctx == actx: # overwrite
198 if wctx == actx: # overwrite
199 a = ld
199 a = ld
200
200
201 if s in s2:
201 if s in s2:
202 r = s2[s]
202 r = s2[s]
203 if ld == r or r == a: # no change or local is newer
203 if ld == r or r == a: # no change or local is newer
204 sm[s] = l
204 sm[s] = l
205 continue
205 continue
206 elif ld == a: # other side changed
206 elif ld == a: # other side changed
207 debug(s, "other changed, get", r)
207 debug(s, "other changed, get", r)
208 wctx.sub(s).get(r, overwrite)
208 wctx.sub(s).get(r, overwrite)
209 sm[s] = r
209 sm[s] = r
210 elif ld[0] != r[0]: # sources differ
210 elif ld[0] != r[0]: # sources differ
211 if repo.ui.promptchoice(
211 if repo.ui.promptchoice(
212 _(' subrepository sources for %s differ\n'
212 _(' subrepository sources for %s differ\n'
213 'use (l)ocal source (%s) or (r)emote source (%s)?'
213 'use (l)ocal source (%s) or (r)emote source (%s)?'
214 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
214 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
215 debug(s, "prompt changed, get", r)
215 debug(s, "prompt changed, get", r)
216 wctx.sub(s).get(r, overwrite)
216 wctx.sub(s).get(r, overwrite)
217 sm[s] = r
217 sm[s] = r
218 elif ld[1] == a[1]: # local side is unchanged
218 elif ld[1] == a[1]: # local side is unchanged
219 debug(s, "other side changed, get", r)
219 debug(s, "other side changed, get", r)
220 wctx.sub(s).get(r, overwrite)
220 wctx.sub(s).get(r, overwrite)
221 sm[s] = r
221 sm[s] = r
222 else:
222 else:
223 debug(s, "both sides changed")
223 debug(s, "both sides changed")
224 srepo = wctx.sub(s)
224 srepo = wctx.sub(s)
225 option = repo.ui.promptchoice(
225 option = repo.ui.promptchoice(
226 _(' subrepository %s diverged (local revision: %s, '
226 _(' subrepository %s diverged (local revision: %s, '
227 'remote revision: %s)\n'
227 'remote revision: %s)\n'
228 '(M)erge, keep (l)ocal or keep (r)emote?'
228 '(M)erge, keep (l)ocal or keep (r)emote?'
229 '$$ &Merge $$ &Local $$ &Remote')
229 '$$ &Merge $$ &Local $$ &Remote')
230 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
230 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
231 if option == 0:
231 if option == 0:
232 wctx.sub(s).merge(r)
232 wctx.sub(s).merge(r)
233 sm[s] = l
233 sm[s] = l
234 debug(s, "merge with", r)
234 debug(s, "merge with", r)
235 elif option == 1:
235 elif option == 1:
236 sm[s] = l
236 sm[s] = l
237 debug(s, "keep local subrepo revision", l)
237 debug(s, "keep local subrepo revision", l)
238 else:
238 else:
239 wctx.sub(s).get(r, overwrite)
239 wctx.sub(s).get(r, overwrite)
240 sm[s] = r
240 sm[s] = r
241 debug(s, "get remote subrepo revision", r)
241 debug(s, "get remote subrepo revision", r)
242 elif ld == a: # remote removed, local unchanged
242 elif ld == a: # remote removed, local unchanged
243 debug(s, "remote removed, remove")
243 debug(s, "remote removed, remove")
244 wctx.sub(s).remove()
244 wctx.sub(s).remove()
245 elif a == nullstate: # not present in remote or ancestor
245 elif a == nullstate: # not present in remote or ancestor
246 debug(s, "local added, keep")
246 debug(s, "local added, keep")
247 sm[s] = l
247 sm[s] = l
248 continue
248 continue
249 else:
249 else:
250 if repo.ui.promptchoice(
250 if repo.ui.promptchoice(
251 _(' local changed subrepository %s which remote removed\n'
251 _(' local changed subrepository %s which remote removed\n'
252 'use (c)hanged version or (d)elete?'
252 'use (c)hanged version or (d)elete?'
253 '$$ &Changed $$ &Delete') % s, 0):
253 '$$ &Changed $$ &Delete') % s, 0):
254 debug(s, "prompt remove")
254 debug(s, "prompt remove")
255 wctx.sub(s).remove()
255 wctx.sub(s).remove()
256
256
257 for s, r in sorted(s2.items()):
257 for s, r in sorted(s2.items()):
258 if s in s1:
258 if s in s1:
259 continue
259 continue
260 elif s not in sa:
260 elif s not in sa:
261 debug(s, "remote added, get", r)
261 debug(s, "remote added, get", r)
262 mctx.sub(s).get(r)
262 mctx.sub(s).get(r)
263 sm[s] = r
263 sm[s] = r
264 elif r != sa[s]:
264 elif r != sa[s]:
265 if repo.ui.promptchoice(
265 if repo.ui.promptchoice(
266 _(' remote changed subrepository %s which local removed\n'
266 _(' remote changed subrepository %s which local removed\n'
267 'use (c)hanged version or (d)elete?'
267 'use (c)hanged version or (d)elete?'
268 '$$ &Changed $$ &Delete') % s, 0) == 0:
268 '$$ &Changed $$ &Delete') % s, 0) == 0:
269 debug(s, "prompt recreate", r)
269 debug(s, "prompt recreate", r)
270 mctx.sub(s).get(r)
270 mctx.sub(s).get(r)
271 sm[s] = r
271 sm[s] = r
272
272
273 # record merged .hgsubstate
273 # record merged .hgsubstate
274 writestate(repo, sm)
274 writestate(repo, sm)
275 return sm
275 return sm
276
276
277 def _updateprompt(ui, sub, dirty, local, remote):
277 def _updateprompt(ui, sub, dirty, local, remote):
278 if dirty:
278 if dirty:
279 msg = (_(' subrepository sources for %s differ\n'
279 msg = (_(' subrepository sources for %s differ\n'
280 'use (l)ocal source (%s) or (r)emote source (%s)?'
280 'use (l)ocal source (%s) or (r)emote source (%s)?'
281 '$$ &Local $$ &Remote')
281 '$$ &Local $$ &Remote')
282 % (subrelpath(sub), local, remote))
282 % (subrelpath(sub), local, remote))
283 else:
283 else:
284 msg = (_(' subrepository sources for %s differ (in checked out '
284 msg = (_(' subrepository sources for %s differ (in checked out '
285 'version)\n'
285 'version)\n'
286 'use (l)ocal source (%s) or (r)emote source (%s)?'
286 'use (l)ocal source (%s) or (r)emote source (%s)?'
287 '$$ &Local $$ &Remote')
287 '$$ &Local $$ &Remote')
288 % (subrelpath(sub), local, remote))
288 % (subrelpath(sub), local, remote))
289 return ui.promptchoice(msg, 0)
289 return ui.promptchoice(msg, 0)
290
290
291 def reporelpath(repo):
291 def reporelpath(repo):
292 """return path to this (sub)repo as seen from outermost repo"""
292 """return path to this (sub)repo as seen from outermost repo"""
293 parent = repo
293 parent = repo
294 while util.safehasattr(parent, '_subparent'):
294 while util.safehasattr(parent, '_subparent'):
295 parent = parent._subparent
295 parent = parent._subparent
296 return repo.root[len(pathutil.normasprefix(parent.root)):]
296 return repo.root[len(pathutil.normasprefix(parent.root)):]
297
297
298 def subrelpath(sub):
298 def subrelpath(sub):
299 """return path to this subrepo as seen from outermost repo"""
299 """return path to this subrepo as seen from outermost repo"""
300 return sub._relpath
300 return sub._relpath
301
301
302 def _abssource(repo, push=False, abort=True):
302 def _abssource(repo, push=False, abort=True):
303 """return pull/push path of repo - either based on parent repo .hgsub info
303 """return pull/push path of repo - either based on parent repo .hgsub info
304 or on the top repo config. Abort or return None if no source found."""
304 or on the top repo config. Abort or return None if no source found."""
305 if util.safehasattr(repo, '_subparent'):
305 if util.safehasattr(repo, '_subparent'):
306 source = util.url(repo._subsource)
306 source = util.url(repo._subsource)
307 if source.isabs():
307 if source.isabs():
308 return str(source)
308 return str(source)
309 source.path = posixpath.normpath(source.path)
309 source.path = posixpath.normpath(source.path)
310 parent = _abssource(repo._subparent, push, abort=False)
310 parent = _abssource(repo._subparent, push, abort=False)
311 if parent:
311 if parent:
312 parent = util.url(util.pconvert(parent))
312 parent = util.url(util.pconvert(parent))
313 parent.path = posixpath.join(parent.path or '', source.path)
313 parent.path = posixpath.join(parent.path or '', source.path)
314 parent.path = posixpath.normpath(parent.path)
314 parent.path = posixpath.normpath(parent.path)
315 return str(parent)
315 return str(parent)
316 else: # recursion reached top repo
316 else: # recursion reached top repo
317 if util.safehasattr(repo, '_subtoppath'):
317 if util.safehasattr(repo, '_subtoppath'):
318 return repo._subtoppath
318 return repo._subtoppath
319 if push and repo.ui.config('paths', 'default-push'):
319 if push and repo.ui.config('paths', 'default-push'):
320 return repo.ui.config('paths', 'default-push')
320 return repo.ui.config('paths', 'default-push')
321 if repo.ui.config('paths', 'default'):
321 if repo.ui.config('paths', 'default'):
322 return repo.ui.config('paths', 'default')
322 return repo.ui.config('paths', 'default')
323 if repo.shared():
323 if repo.shared():
324 # chop off the .hg component to get the default path form
324 # chop off the .hg component to get the default path form
325 return os.path.dirname(repo.sharedpath)
325 return os.path.dirname(repo.sharedpath)
326 if abort:
326 if abort:
327 raise error.Abort(_("default path for subrepository not found"))
327 raise error.Abort(_("default path for subrepository not found"))
328
328
329 def _sanitize(ui, vfs, ignore):
329 def _sanitize(ui, vfs, ignore):
330 for dirname, dirs, names in vfs.walk():
330 for dirname, dirs, names in vfs.walk():
331 for i, d in enumerate(dirs):
331 for i, d in enumerate(dirs):
332 if d.lower() == ignore:
332 if d.lower() == ignore:
333 del dirs[i]
333 del dirs[i]
334 break
334 break
335 if vfs.basename(dirname).lower() != '.hg':
335 if vfs.basename(dirname).lower() != '.hg':
336 continue
336 continue
337 for f in names:
337 for f in names:
338 if f.lower() == 'hgrc':
338 if f.lower() == 'hgrc':
339 ui.warn(_("warning: removing potentially hostile 'hgrc' "
339 ui.warn(_("warning: removing potentially hostile 'hgrc' "
340 "in '%s'\n") % vfs.join(dirname))
340 "in '%s'\n") % vfs.join(dirname))
341 vfs.unlink(vfs.reljoin(dirname, f))
341 vfs.unlink(vfs.reljoin(dirname, f))
342
342
343 def subrepo(ctx, path, allowwdir=False):
343 def subrepo(ctx, path, allowwdir=False):
344 """return instance of the right subrepo class for subrepo in path"""
344 """return instance of the right subrepo class for subrepo in path"""
345 # subrepo inherently violates our import layering rules
345 # subrepo inherently violates our import layering rules
346 # because it wants to make repo objects from deep inside the stack
346 # because it wants to make repo objects from deep inside the stack
347 # so we manually delay the circular imports to not break
347 # so we manually delay the circular imports to not break
348 # scripts that don't use our demand-loading
348 # scripts that don't use our demand-loading
349 global hg
349 global hg
350 from . import hg as h
350 from . import hg as h
351 hg = h
351 hg = h
352
352
353 pathutil.pathauditor(ctx.repo().root)(path)
353 pathutil.pathauditor(ctx.repo().root)(path)
354 state = ctx.substate[path]
354 state = ctx.substate[path]
355 if state[2] not in types:
355 if state[2] not in types:
356 raise error.Abort(_('unknown subrepo type %s') % state[2])
356 raise error.Abort(_('unknown subrepo type %s') % state[2])
357 if allowwdir:
357 if allowwdir:
358 state = (state[0], ctx.subrev(path), state[2])
358 state = (state[0], ctx.subrev(path), state[2])
359 return types[state[2]](ctx, path, state[:2])
359 return types[state[2]](ctx, path, state[:2])
360
360
361 def nullsubrepo(ctx, path, pctx):
361 def nullsubrepo(ctx, path, pctx):
362 """return an empty subrepo in pctx for the extant subrepo in ctx"""
362 """return an empty subrepo in pctx for the extant subrepo in ctx"""
363 # subrepo inherently violates our import layering rules
363 # subrepo inherently violates our import layering rules
364 # because it wants to make repo objects from deep inside the stack
364 # because it wants to make repo objects from deep inside the stack
365 # so we manually delay the circular imports to not break
365 # so we manually delay the circular imports to not break
366 # scripts that don't use our demand-loading
366 # scripts that don't use our demand-loading
367 global hg
367 global hg
368 from . import hg as h
368 from . import hg as h
369 hg = h
369 hg = h
370
370
371 pathutil.pathauditor(ctx.repo().root)(path)
371 pathutil.pathauditor(ctx.repo().root)(path)
372 state = ctx.substate[path]
372 state = ctx.substate[path]
373 if state[2] not in types:
373 if state[2] not in types:
374 raise error.Abort(_('unknown subrepo type %s') % state[2])
374 raise error.Abort(_('unknown subrepo type %s') % state[2])
375 subrev = ''
375 subrev = ''
376 if state[2] == 'hg':
376 if state[2] == 'hg':
377 subrev = "0" * 40
377 subrev = "0" * 40
378 return types[state[2]](pctx, path, (state[0], subrev))
378 return types[state[2]](pctx, path, (state[0], subrev))
379
379
380 def newcommitphase(ui, ctx):
380 def newcommitphase(ui, ctx):
381 commitphase = phases.newcommitphase(ui)
381 commitphase = phases.newcommitphase(ui)
382 substate = getattr(ctx, "substate", None)
382 substate = getattr(ctx, "substate", None)
383 if not substate:
383 if not substate:
384 return commitphase
384 return commitphase
385 check = ui.config('phases', 'checksubrepos', 'follow')
385 check = ui.config('phases', 'checksubrepos', 'follow')
386 if check not in ('ignore', 'follow', 'abort'):
386 if check not in ('ignore', 'follow', 'abort'):
387 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
387 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
388 % (check))
388 % (check))
389 if check == 'ignore':
389 if check == 'ignore':
390 return commitphase
390 return commitphase
391 maxphase = phases.public
391 maxphase = phases.public
392 maxsub = None
392 maxsub = None
393 for s in sorted(substate):
393 for s in sorted(substate):
394 sub = ctx.sub(s)
394 sub = ctx.sub(s)
395 subphase = sub.phase(substate[s][1])
395 subphase = sub.phase(substate[s][1])
396 if maxphase < subphase:
396 if maxphase < subphase:
397 maxphase = subphase
397 maxphase = subphase
398 maxsub = s
398 maxsub = s
399 if commitphase < maxphase:
399 if commitphase < maxphase:
400 if check == 'abort':
400 if check == 'abort':
401 raise error.Abort(_("can't commit in %s phase"
401 raise error.Abort(_("can't commit in %s phase"
402 " conflicting %s from subrepository %s") %
402 " conflicting %s from subrepository %s") %
403 (phases.phasenames[commitphase],
403 (phases.phasenames[commitphase],
404 phases.phasenames[maxphase], maxsub))
404 phases.phasenames[maxphase], maxsub))
405 ui.warn(_("warning: changes are committed in"
405 ui.warn(_("warning: changes are committed in"
406 " %s phase from subrepository %s\n") %
406 " %s phase from subrepository %s\n") %
407 (phases.phasenames[maxphase], maxsub))
407 (phases.phasenames[maxphase], maxsub))
408 return maxphase
408 return maxphase
409 return commitphase
409 return commitphase
410
410
411 # subrepo classes need to implement the following abstract class:
411 # subrepo classes need to implement the following abstract class:
412
412
413 class abstractsubrepo(object):
413 class abstractsubrepo(object):
414
414
415 def __init__(self, ctx, path):
415 def __init__(self, ctx, path):
416 """Initialize abstractsubrepo part
416 """Initialize abstractsubrepo part
417
417
418 ``ctx`` is the context referring this subrepository in the
418 ``ctx`` is the context referring this subrepository in the
419 parent repository.
419 parent repository.
420
420
421 ``path`` is the path to this subrepository as seen from
421 ``path`` is the path to this subrepository as seen from
422 innermost repository.
422 innermost repository.
423 """
423 """
424 self.ui = ctx.repo().ui
424 self.ui = ctx.repo().ui
425 self._ctx = ctx
425 self._ctx = ctx
426 self._path = path
426 self._path = path
427
427
428 def storeclean(self, path):
428 def storeclean(self, path):
429 """
429 """
430 returns true if the repository has not changed since it was last
430 returns true if the repository has not changed since it was last
431 cloned from or pushed to a given repository.
431 cloned from or pushed to a given repository.
432 """
432 """
433 return False
433 return False
434
434
435 def dirty(self, ignoreupdate=False):
435 def dirty(self, ignoreupdate=False):
436 """returns true if the dirstate of the subrepo is dirty or does not
436 """returns true if the dirstate of the subrepo is dirty or does not
437 match current stored state. If ignoreupdate is true, only check
437 match current stored state. If ignoreupdate is true, only check
438 whether the subrepo has uncommitted changes in its dirstate.
438 whether the subrepo has uncommitted changes in its dirstate.
439 """
439 """
440 raise NotImplementedError
440 raise NotImplementedError
441
441
442 def dirtyreason(self, ignoreupdate=False):
442 def dirtyreason(self, ignoreupdate=False):
443 """return reason string if it is ``dirty()``
443 """return reason string if it is ``dirty()``
444
444
445 Returned string should have enough information for the message
445 Returned string should have enough information for the message
446 of exception.
446 of exception.
447
447
448 This returns None, otherwise.
448 This returns None, otherwise.
449 """
449 """
450 if self.dirty(ignoreupdate=ignoreupdate):
450 if self.dirty(ignoreupdate=ignoreupdate):
451 return _("uncommitted changes in subrepository '%s'"
451 return _("uncommitted changes in subrepository '%s'"
452 ) % subrelpath(self)
452 ) % subrelpath(self)
453
453
454 def bailifchanged(self, ignoreupdate=False):
454 def bailifchanged(self, ignoreupdate=False):
455 """raise Abort if subrepository is ``dirty()``
455 """raise Abort if subrepository is ``dirty()``
456 """
456 """
457 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
457 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
458 if dirtyreason:
458 if dirtyreason:
459 raise error.Abort(dirtyreason)
459 raise error.Abort(dirtyreason)
460
460
461 def basestate(self):
461 def basestate(self):
462 """current working directory base state, disregarding .hgsubstate
462 """current working directory base state, disregarding .hgsubstate
463 state and working directory modifications"""
463 state and working directory modifications"""
464 raise NotImplementedError
464 raise NotImplementedError
465
465
466 def checknested(self, path):
466 def checknested(self, path):
467 """check if path is a subrepository within this repository"""
467 """check if path is a subrepository within this repository"""
468 return False
468 return False
469
469
470 def commit(self, text, user, date):
470 def commit(self, text, user, date):
471 """commit the current changes to the subrepo with the given
471 """commit the current changes to the subrepo with the given
472 log message. Use given user and date if possible. Return the
472 log message. Use given user and date if possible. Return the
473 new state of the subrepo.
473 new state of the subrepo.
474 """
474 """
475 raise NotImplementedError
475 raise NotImplementedError
476
476
477 def phase(self, state):
477 def phase(self, state):
478 """returns phase of specified state in the subrepository.
478 """returns phase of specified state in the subrepository.
479 """
479 """
480 return phases.public
480 return phases.public
481
481
482 def remove(self):
482 def remove(self):
483 """remove the subrepo
483 """remove the subrepo
484
484
485 (should verify the dirstate is not dirty first)
485 (should verify the dirstate is not dirty first)
486 """
486 """
487 raise NotImplementedError
487 raise NotImplementedError
488
488
489 def get(self, state, overwrite=False):
489 def get(self, state, overwrite=False):
490 """run whatever commands are needed to put the subrepo into
490 """run whatever commands are needed to put the subrepo into
491 this state
491 this state
492 """
492 """
493 raise NotImplementedError
493 raise NotImplementedError
494
494
495 def merge(self, state):
495 def merge(self, state):
496 """merge currently-saved state with the new state."""
496 """merge currently-saved state with the new state."""
497 raise NotImplementedError
497 raise NotImplementedError
498
498
499 def push(self, opts):
499 def push(self, opts):
500 """perform whatever action is analogous to 'hg push'
500 """perform whatever action is analogous to 'hg push'
501
501
502 This may be a no-op on some systems.
502 This may be a no-op on some systems.
503 """
503 """
504 raise NotImplementedError
504 raise NotImplementedError
505
505
506 def add(self, ui, match, prefix, explicitonly, **opts):
506 def add(self, ui, match, prefix, explicitonly, **opts):
507 return []
507 return []
508
508
509 def addremove(self, matcher, prefix, opts, dry_run, similarity):
509 def addremove(self, matcher, prefix, opts, dry_run, similarity):
510 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
510 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
511 return 1
511 return 1
512
512
513 def cat(self, match, prefix, **opts):
513 def cat(self, match, prefix, **opts):
514 return 1
514 return 1
515
515
516 def status(self, rev2, **opts):
516 def status(self, rev2, **opts):
517 return scmutil.status([], [], [], [], [], [], [])
517 return scmutil.status([], [], [], [], [], [], [])
518
518
519 def diff(self, ui, diffopts, node2, match, prefix, **opts):
519 def diff(self, ui, diffopts, node2, match, prefix, **opts):
520 pass
520 pass
521
521
522 def outgoing(self, ui, dest, opts):
522 def outgoing(self, ui, dest, opts):
523 return 1
523 return 1
524
524
525 def incoming(self, ui, source, opts):
525 def incoming(self, ui, source, opts):
526 return 1
526 return 1
527
527
528 def files(self):
528 def files(self):
529 """return filename iterator"""
529 """return filename iterator"""
530 raise NotImplementedError
530 raise NotImplementedError
531
531
532 def filedata(self, name):
532 def filedata(self, name):
533 """return file data"""
533 """return file data"""
534 raise NotImplementedError
534 raise NotImplementedError
535
535
536 def fileflags(self, name):
536 def fileflags(self, name):
537 """return file flags"""
537 """return file flags"""
538 return ''
538 return ''
539
539
540 def getfileset(self, expr):
540 def getfileset(self, expr):
541 """Resolve the fileset expression for this repo"""
541 """Resolve the fileset expression for this repo"""
542 return set()
542 return set()
543
543
544 def printfiles(self, ui, m, fm, fmt, subrepos):
544 def printfiles(self, ui, m, fm, fmt, subrepos):
545 """handle the files command for this subrepo"""
545 """handle the files command for this subrepo"""
546 return 1
546 return 1
547
547
548 def archive(self, archiver, prefix, match=None):
548 def archive(self, archiver, prefix, match=None):
549 if match is not None:
549 if match is not None:
550 files = [f for f in self.files() if match(f)]
550 files = [f for f in self.files() if match(f)]
551 else:
551 else:
552 files = self.files()
552 files = self.files()
553 total = len(files)
553 total = len(files)
554 relpath = subrelpath(self)
554 relpath = subrelpath(self)
555 self.ui.progress(_('archiving (%s)') % relpath, 0,
555 self.ui.progress(_('archiving (%s)') % relpath, 0,
556 unit=_('files'), total=total)
556 unit=_('files'), total=total)
557 for i, name in enumerate(files):
557 for i, name in enumerate(files):
558 flags = self.fileflags(name)
558 flags = self.fileflags(name)
559 mode = 'x' in flags and 0o755 or 0o644
559 mode = 'x' in flags and 0o755 or 0o644
560 symlink = 'l' in flags
560 symlink = 'l' in flags
561 archiver.addfile(prefix + self._path + '/' + name,
561 archiver.addfile(prefix + self._path + '/' + name,
562 mode, symlink, self.filedata(name))
562 mode, symlink, self.filedata(name))
563 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
563 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
564 unit=_('files'), total=total)
564 unit=_('files'), total=total)
565 self.ui.progress(_('archiving (%s)') % relpath, None)
565 self.ui.progress(_('archiving (%s)') % relpath, None)
566 return total
566 return total
567
567
568 def walk(self, match):
568 def walk(self, match):
569 '''
569 '''
570 walk recursively through the directory tree, finding all files
570 walk recursively through the directory tree, finding all files
571 matched by the match function
571 matched by the match function
572 '''
572 '''
573 pass
573 pass
574
574
575 def forget(self, match, prefix):
575 def forget(self, match, prefix):
576 return ([], [])
576 return ([], [])
577
577
578 def removefiles(self, matcher, prefix, after, force, subrepos):
578 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
579 """remove the matched files from the subrepository and the filesystem,
579 """remove the matched files from the subrepository and the filesystem,
580 possibly by force and/or after the file has been removed from the
580 possibly by force and/or after the file has been removed from the
581 filesystem. Return 0 on success, 1 on any warning.
581 filesystem. Return 0 on success, 1 on any warning.
582 """
582 """
583 warnings.append(_("warning: removefiles not implemented (%s)")
584 % self._path)
583 return 1
585 return 1
584
586
585 def revert(self, substate, *pats, **opts):
587 def revert(self, substate, *pats, **opts):
586 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
588 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
587 % (substate[0], substate[2]))
589 % (substate[0], substate[2]))
588 return []
590 return []
589
591
590 def shortid(self, revid):
592 def shortid(self, revid):
591 return revid
593 return revid
592
594
593 def verify(self):
595 def verify(self):
594 '''verify the integrity of the repository. Return 0 on success or
596 '''verify the integrity of the repository. Return 0 on success or
595 warning, 1 on any error.
597 warning, 1 on any error.
596 '''
598 '''
597 return 0
599 return 0
598
600
599 @propertycache
601 @propertycache
600 def wvfs(self):
602 def wvfs(self):
601 """return vfs to access the working directory of this subrepository
603 """return vfs to access the working directory of this subrepository
602 """
604 """
603 return scmutil.vfs(self._ctx.repo().wvfs.join(self._path))
605 return scmutil.vfs(self._ctx.repo().wvfs.join(self._path))
604
606
605 @propertycache
607 @propertycache
606 def _relpath(self):
608 def _relpath(self):
607 """return path to this subrepository as seen from outermost repository
609 """return path to this subrepository as seen from outermost repository
608 """
610 """
609 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
611 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
610
612
611 class hgsubrepo(abstractsubrepo):
613 class hgsubrepo(abstractsubrepo):
612 def __init__(self, ctx, path, state):
614 def __init__(self, ctx, path, state):
613 super(hgsubrepo, self).__init__(ctx, path)
615 super(hgsubrepo, self).__init__(ctx, path)
614 self._state = state
616 self._state = state
615 r = ctx.repo()
617 r = ctx.repo()
616 root = r.wjoin(path)
618 root = r.wjoin(path)
617 create = not r.wvfs.exists('%s/.hg' % path)
619 create = not r.wvfs.exists('%s/.hg' % path)
618 self._repo = hg.repository(r.baseui, root, create=create)
620 self._repo = hg.repository(r.baseui, root, create=create)
619
621
620 # Propagate the parent's --hidden option
622 # Propagate the parent's --hidden option
621 if r is r.unfiltered():
623 if r is r.unfiltered():
622 self._repo = self._repo.unfiltered()
624 self._repo = self._repo.unfiltered()
623
625
624 self.ui = self._repo.ui
626 self.ui = self._repo.ui
625 for s, k in [('ui', 'commitsubrepos')]:
627 for s, k in [('ui', 'commitsubrepos')]:
626 v = r.ui.config(s, k)
628 v = r.ui.config(s, k)
627 if v:
629 if v:
628 self.ui.setconfig(s, k, v, 'subrepo')
630 self.ui.setconfig(s, k, v, 'subrepo')
629 # internal config: ui._usedassubrepo
631 # internal config: ui._usedassubrepo
630 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
632 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
631 self._initrepo(r, state[0], create)
633 self._initrepo(r, state[0], create)
632
634
633 def storeclean(self, path):
635 def storeclean(self, path):
634 with self._repo.lock():
636 with self._repo.lock():
635 return self._storeclean(path)
637 return self._storeclean(path)
636
638
637 def _storeclean(self, path):
639 def _storeclean(self, path):
638 clean = True
640 clean = True
639 itercache = self._calcstorehash(path)
641 itercache = self._calcstorehash(path)
640 for filehash in self._readstorehashcache(path):
642 for filehash in self._readstorehashcache(path):
641 if filehash != next(itercache, None):
643 if filehash != next(itercache, None):
642 clean = False
644 clean = False
643 break
645 break
644 if clean:
646 if clean:
645 # if not empty:
647 # if not empty:
646 # the cached and current pull states have a different size
648 # the cached and current pull states have a different size
647 clean = next(itercache, None) is None
649 clean = next(itercache, None) is None
648 return clean
650 return clean
649
651
650 def _calcstorehash(self, remotepath):
652 def _calcstorehash(self, remotepath):
651 '''calculate a unique "store hash"
653 '''calculate a unique "store hash"
652
654
653 This method is used to to detect when there are changes that may
655 This method is used to to detect when there are changes that may
654 require a push to a given remote path.'''
656 require a push to a given remote path.'''
655 # sort the files that will be hashed in increasing (likely) file size
657 # sort the files that will be hashed in increasing (likely) file size
656 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
658 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
657 yield '# %s\n' % _expandedabspath(remotepath)
659 yield '# %s\n' % _expandedabspath(remotepath)
658 vfs = self._repo.vfs
660 vfs = self._repo.vfs
659 for relname in filelist:
661 for relname in filelist:
660 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
662 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
661 yield '%s = %s\n' % (relname, filehash)
663 yield '%s = %s\n' % (relname, filehash)
662
664
663 @propertycache
665 @propertycache
664 def _cachestorehashvfs(self):
666 def _cachestorehashvfs(self):
665 return scmutil.vfs(self._repo.join('cache/storehash'))
667 return scmutil.vfs(self._repo.join('cache/storehash'))
666
668
667 def _readstorehashcache(self, remotepath):
669 def _readstorehashcache(self, remotepath):
668 '''read the store hash cache for a given remote repository'''
670 '''read the store hash cache for a given remote repository'''
669 cachefile = _getstorehashcachename(remotepath)
671 cachefile = _getstorehashcachename(remotepath)
670 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
672 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
671
673
672 def _cachestorehash(self, remotepath):
674 def _cachestorehash(self, remotepath):
673 '''cache the current store hash
675 '''cache the current store hash
674
676
675 Each remote repo requires its own store hash cache, because a subrepo
677 Each remote repo requires its own store hash cache, because a subrepo
676 store may be "clean" versus a given remote repo, but not versus another
678 store may be "clean" versus a given remote repo, but not versus another
677 '''
679 '''
678 cachefile = _getstorehashcachename(remotepath)
680 cachefile = _getstorehashcachename(remotepath)
679 with self._repo.lock():
681 with self._repo.lock():
680 storehash = list(self._calcstorehash(remotepath))
682 storehash = list(self._calcstorehash(remotepath))
681 vfs = self._cachestorehashvfs
683 vfs = self._cachestorehashvfs
682 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
684 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
683
685
684 def _getctx(self):
686 def _getctx(self):
685 '''fetch the context for this subrepo revision, possibly a workingctx
687 '''fetch the context for this subrepo revision, possibly a workingctx
686 '''
688 '''
687 if self._ctx.rev() is None:
689 if self._ctx.rev() is None:
688 return self._repo[None] # workingctx if parent is workingctx
690 return self._repo[None] # workingctx if parent is workingctx
689 else:
691 else:
690 rev = self._state[1]
692 rev = self._state[1]
691 return self._repo[rev]
693 return self._repo[rev]
692
694
693 @annotatesubrepoerror
695 @annotatesubrepoerror
694 def _initrepo(self, parentrepo, source, create):
696 def _initrepo(self, parentrepo, source, create):
695 self._repo._subparent = parentrepo
697 self._repo._subparent = parentrepo
696 self._repo._subsource = source
698 self._repo._subsource = source
697
699
698 if create:
700 if create:
699 lines = ['[paths]\n']
701 lines = ['[paths]\n']
700
702
701 def addpathconfig(key, value):
703 def addpathconfig(key, value):
702 if value:
704 if value:
703 lines.append('%s = %s\n' % (key, value))
705 lines.append('%s = %s\n' % (key, value))
704 self.ui.setconfig('paths', key, value, 'subrepo')
706 self.ui.setconfig('paths', key, value, 'subrepo')
705
707
706 defpath = _abssource(self._repo, abort=False)
708 defpath = _abssource(self._repo, abort=False)
707 defpushpath = _abssource(self._repo, True, abort=False)
709 defpushpath = _abssource(self._repo, True, abort=False)
708 addpathconfig('default', defpath)
710 addpathconfig('default', defpath)
709 if defpath != defpushpath:
711 if defpath != defpushpath:
710 addpathconfig('default-push', defpushpath)
712 addpathconfig('default-push', defpushpath)
711
713
712 fp = self._repo.vfs("hgrc", "w", text=True)
714 fp = self._repo.vfs("hgrc", "w", text=True)
713 try:
715 try:
714 fp.write(''.join(lines))
716 fp.write(''.join(lines))
715 finally:
717 finally:
716 fp.close()
718 fp.close()
717
719
718 @annotatesubrepoerror
720 @annotatesubrepoerror
719 def add(self, ui, match, prefix, explicitonly, **opts):
721 def add(self, ui, match, prefix, explicitonly, **opts):
720 return cmdutil.add(ui, self._repo, match,
722 return cmdutil.add(ui, self._repo, match,
721 self.wvfs.reljoin(prefix, self._path),
723 self.wvfs.reljoin(prefix, self._path),
722 explicitonly, **opts)
724 explicitonly, **opts)
723
725
724 @annotatesubrepoerror
726 @annotatesubrepoerror
725 def addremove(self, m, prefix, opts, dry_run, similarity):
727 def addremove(self, m, prefix, opts, dry_run, similarity):
726 # In the same way as sub directories are processed, once in a subrepo,
728 # In the same way as sub directories are processed, once in a subrepo,
727 # always entry any of its subrepos. Don't corrupt the options that will
729 # always entry any of its subrepos. Don't corrupt the options that will
728 # be used to process sibling subrepos however.
730 # be used to process sibling subrepos however.
729 opts = copy.copy(opts)
731 opts = copy.copy(opts)
730 opts['subrepos'] = True
732 opts['subrepos'] = True
731 return scmutil.addremove(self._repo, m,
733 return scmutil.addremove(self._repo, m,
732 self.wvfs.reljoin(prefix, self._path), opts,
734 self.wvfs.reljoin(prefix, self._path), opts,
733 dry_run, similarity)
735 dry_run, similarity)
734
736
735 @annotatesubrepoerror
737 @annotatesubrepoerror
736 def cat(self, match, prefix, **opts):
738 def cat(self, match, prefix, **opts):
737 rev = self._state[1]
739 rev = self._state[1]
738 ctx = self._repo[rev]
740 ctx = self._repo[rev]
739 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
741 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
740
742
741 @annotatesubrepoerror
743 @annotatesubrepoerror
742 def status(self, rev2, **opts):
744 def status(self, rev2, **opts):
743 try:
745 try:
744 rev1 = self._state[1]
746 rev1 = self._state[1]
745 ctx1 = self._repo[rev1]
747 ctx1 = self._repo[rev1]
746 ctx2 = self._repo[rev2]
748 ctx2 = self._repo[rev2]
747 return self._repo.status(ctx1, ctx2, **opts)
749 return self._repo.status(ctx1, ctx2, **opts)
748 except error.RepoLookupError as inst:
750 except error.RepoLookupError as inst:
749 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
751 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
750 % (inst, subrelpath(self)))
752 % (inst, subrelpath(self)))
751 return scmutil.status([], [], [], [], [], [], [])
753 return scmutil.status([], [], [], [], [], [], [])
752
754
753 @annotatesubrepoerror
755 @annotatesubrepoerror
754 def diff(self, ui, diffopts, node2, match, prefix, **opts):
756 def diff(self, ui, diffopts, node2, match, prefix, **opts):
755 try:
757 try:
756 node1 = node.bin(self._state[1])
758 node1 = node.bin(self._state[1])
757 # We currently expect node2 to come from substate and be
759 # We currently expect node2 to come from substate and be
758 # in hex format
760 # in hex format
759 if node2 is not None:
761 if node2 is not None:
760 node2 = node.bin(node2)
762 node2 = node.bin(node2)
761 cmdutil.diffordiffstat(ui, self._repo, diffopts,
763 cmdutil.diffordiffstat(ui, self._repo, diffopts,
762 node1, node2, match,
764 node1, node2, match,
763 prefix=posixpath.join(prefix, self._path),
765 prefix=posixpath.join(prefix, self._path),
764 listsubrepos=True, **opts)
766 listsubrepos=True, **opts)
765 except error.RepoLookupError as inst:
767 except error.RepoLookupError as inst:
766 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
768 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
767 % (inst, subrelpath(self)))
769 % (inst, subrelpath(self)))
768
770
769 @annotatesubrepoerror
771 @annotatesubrepoerror
770 def archive(self, archiver, prefix, match=None):
772 def archive(self, archiver, prefix, match=None):
771 self._get(self._state + ('hg',))
773 self._get(self._state + ('hg',))
772 total = abstractsubrepo.archive(self, archiver, prefix, match)
774 total = abstractsubrepo.archive(self, archiver, prefix, match)
773 rev = self._state[1]
775 rev = self._state[1]
774 ctx = self._repo[rev]
776 ctx = self._repo[rev]
775 for subpath in ctx.substate:
777 for subpath in ctx.substate:
776 s = subrepo(ctx, subpath, True)
778 s = subrepo(ctx, subpath, True)
777 submatch = matchmod.subdirmatcher(subpath, match)
779 submatch = matchmod.subdirmatcher(subpath, match)
778 total += s.archive(archiver, prefix + self._path + '/', submatch)
780 total += s.archive(archiver, prefix + self._path + '/', submatch)
779 return total
781 return total
780
782
781 @annotatesubrepoerror
783 @annotatesubrepoerror
782 def dirty(self, ignoreupdate=False):
784 def dirty(self, ignoreupdate=False):
783 r = self._state[1]
785 r = self._state[1]
784 if r == '' and not ignoreupdate: # no state recorded
786 if r == '' and not ignoreupdate: # no state recorded
785 return True
787 return True
786 w = self._repo[None]
788 w = self._repo[None]
787 if r != w.p1().hex() and not ignoreupdate:
789 if r != w.p1().hex() and not ignoreupdate:
788 # different version checked out
790 # different version checked out
789 return True
791 return True
790 return w.dirty() # working directory changed
792 return w.dirty() # working directory changed
791
793
792 def basestate(self):
794 def basestate(self):
793 return self._repo['.'].hex()
795 return self._repo['.'].hex()
794
796
795 def checknested(self, path):
797 def checknested(self, path):
796 return self._repo._checknested(self._repo.wjoin(path))
798 return self._repo._checknested(self._repo.wjoin(path))
797
799
798 @annotatesubrepoerror
800 @annotatesubrepoerror
799 def commit(self, text, user, date):
801 def commit(self, text, user, date):
800 # don't bother committing in the subrepo if it's only been
802 # don't bother committing in the subrepo if it's only been
801 # updated
803 # updated
802 if not self.dirty(True):
804 if not self.dirty(True):
803 return self._repo['.'].hex()
805 return self._repo['.'].hex()
804 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
806 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
805 n = self._repo.commit(text, user, date)
807 n = self._repo.commit(text, user, date)
806 if not n:
808 if not n:
807 return self._repo['.'].hex() # different version checked out
809 return self._repo['.'].hex() # different version checked out
808 return node.hex(n)
810 return node.hex(n)
809
811
810 @annotatesubrepoerror
812 @annotatesubrepoerror
811 def phase(self, state):
813 def phase(self, state):
812 return self._repo[state].phase()
814 return self._repo[state].phase()
813
815
814 @annotatesubrepoerror
816 @annotatesubrepoerror
815 def remove(self):
817 def remove(self):
816 # we can't fully delete the repository as it may contain
818 # we can't fully delete the repository as it may contain
817 # local-only history
819 # local-only history
818 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
820 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
819 hg.clean(self._repo, node.nullid, False)
821 hg.clean(self._repo, node.nullid, False)
820
822
821 def _get(self, state):
823 def _get(self, state):
822 source, revision, kind = state
824 source, revision, kind = state
823 if revision in self._repo.unfiltered():
825 if revision in self._repo.unfiltered():
824 return True
826 return True
825 self._repo._subsource = source
827 self._repo._subsource = source
826 srcurl = _abssource(self._repo)
828 srcurl = _abssource(self._repo)
827 other = hg.peer(self._repo, {}, srcurl)
829 other = hg.peer(self._repo, {}, srcurl)
828 if len(self._repo) == 0:
830 if len(self._repo) == 0:
829 self.ui.status(_('cloning subrepo %s from %s\n')
831 self.ui.status(_('cloning subrepo %s from %s\n')
830 % (subrelpath(self), srcurl))
832 % (subrelpath(self), srcurl))
831 parentrepo = self._repo._subparent
833 parentrepo = self._repo._subparent
832 # use self._repo.vfs instead of self.wvfs to remove .hg only
834 # use self._repo.vfs instead of self.wvfs to remove .hg only
833 self._repo.vfs.rmtree()
835 self._repo.vfs.rmtree()
834 other, cloned = hg.clone(self._repo._subparent.baseui, {},
836 other, cloned = hg.clone(self._repo._subparent.baseui, {},
835 other, self._repo.root,
837 other, self._repo.root,
836 update=False)
838 update=False)
837 self._repo = cloned.local()
839 self._repo = cloned.local()
838 self._initrepo(parentrepo, source, create=True)
840 self._initrepo(parentrepo, source, create=True)
839 self._cachestorehash(srcurl)
841 self._cachestorehash(srcurl)
840 else:
842 else:
841 self.ui.status(_('pulling subrepo %s from %s\n')
843 self.ui.status(_('pulling subrepo %s from %s\n')
842 % (subrelpath(self), srcurl))
844 % (subrelpath(self), srcurl))
843 cleansub = self.storeclean(srcurl)
845 cleansub = self.storeclean(srcurl)
844 exchange.pull(self._repo, other)
846 exchange.pull(self._repo, other)
845 if cleansub:
847 if cleansub:
846 # keep the repo clean after pull
848 # keep the repo clean after pull
847 self._cachestorehash(srcurl)
849 self._cachestorehash(srcurl)
848 return False
850 return False
849
851
850 @annotatesubrepoerror
852 @annotatesubrepoerror
851 def get(self, state, overwrite=False):
853 def get(self, state, overwrite=False):
852 inrepo = self._get(state)
854 inrepo = self._get(state)
853 source, revision, kind = state
855 source, revision, kind = state
854 repo = self._repo
856 repo = self._repo
855 repo.ui.debug("getting subrepo %s\n" % self._path)
857 repo.ui.debug("getting subrepo %s\n" % self._path)
856 if inrepo:
858 if inrepo:
857 urepo = repo.unfiltered()
859 urepo = repo.unfiltered()
858 ctx = urepo[revision]
860 ctx = urepo[revision]
859 if ctx.hidden():
861 if ctx.hidden():
860 urepo.ui.warn(
862 urepo.ui.warn(
861 _('revision %s in subrepo %s is hidden\n') \
863 _('revision %s in subrepo %s is hidden\n') \
862 % (revision[0:12], self._path))
864 % (revision[0:12], self._path))
863 repo = urepo
865 repo = urepo
864 hg.updaterepo(repo, revision, overwrite)
866 hg.updaterepo(repo, revision, overwrite)
865
867
866 @annotatesubrepoerror
868 @annotatesubrepoerror
867 def merge(self, state):
869 def merge(self, state):
868 self._get(state)
870 self._get(state)
869 cur = self._repo['.']
871 cur = self._repo['.']
870 dst = self._repo[state[1]]
872 dst = self._repo[state[1]]
871 anc = dst.ancestor(cur)
873 anc = dst.ancestor(cur)
872
874
873 def mergefunc():
875 def mergefunc():
874 if anc == cur and dst.branch() == cur.branch():
876 if anc == cur and dst.branch() == cur.branch():
875 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
877 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
876 hg.update(self._repo, state[1])
878 hg.update(self._repo, state[1])
877 elif anc == dst:
879 elif anc == dst:
878 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
880 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
879 else:
881 else:
880 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
882 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
881 hg.merge(self._repo, state[1], remind=False)
883 hg.merge(self._repo, state[1], remind=False)
882
884
883 wctx = self._repo[None]
885 wctx = self._repo[None]
884 if self.dirty():
886 if self.dirty():
885 if anc != dst:
887 if anc != dst:
886 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
888 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
887 mergefunc()
889 mergefunc()
888 else:
890 else:
889 mergefunc()
891 mergefunc()
890 else:
892 else:
891 mergefunc()
893 mergefunc()
892
894
893 @annotatesubrepoerror
895 @annotatesubrepoerror
894 def push(self, opts):
896 def push(self, opts):
895 force = opts.get('force')
897 force = opts.get('force')
896 newbranch = opts.get('new_branch')
898 newbranch = opts.get('new_branch')
897 ssh = opts.get('ssh')
899 ssh = opts.get('ssh')
898
900
899 # push subrepos depth-first for coherent ordering
901 # push subrepos depth-first for coherent ordering
900 c = self._repo['']
902 c = self._repo['']
901 subs = c.substate # only repos that are committed
903 subs = c.substate # only repos that are committed
902 for s in sorted(subs):
904 for s in sorted(subs):
903 if c.sub(s).push(opts) == 0:
905 if c.sub(s).push(opts) == 0:
904 return False
906 return False
905
907
906 dsturl = _abssource(self._repo, True)
908 dsturl = _abssource(self._repo, True)
907 if not force:
909 if not force:
908 if self.storeclean(dsturl):
910 if self.storeclean(dsturl):
909 self.ui.status(
911 self.ui.status(
910 _('no changes made to subrepo %s since last push to %s\n')
912 _('no changes made to subrepo %s since last push to %s\n')
911 % (subrelpath(self), dsturl))
913 % (subrelpath(self), dsturl))
912 return None
914 return None
913 self.ui.status(_('pushing subrepo %s to %s\n') %
915 self.ui.status(_('pushing subrepo %s to %s\n') %
914 (subrelpath(self), dsturl))
916 (subrelpath(self), dsturl))
915 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
917 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
916 res = exchange.push(self._repo, other, force, newbranch=newbranch)
918 res = exchange.push(self._repo, other, force, newbranch=newbranch)
917
919
918 # the repo is now clean
920 # the repo is now clean
919 self._cachestorehash(dsturl)
921 self._cachestorehash(dsturl)
920 return res.cgresult
922 return res.cgresult
921
923
922 @annotatesubrepoerror
924 @annotatesubrepoerror
923 def outgoing(self, ui, dest, opts):
925 def outgoing(self, ui, dest, opts):
924 if 'rev' in opts or 'branch' in opts:
926 if 'rev' in opts or 'branch' in opts:
925 opts = copy.copy(opts)
927 opts = copy.copy(opts)
926 opts.pop('rev', None)
928 opts.pop('rev', None)
927 opts.pop('branch', None)
929 opts.pop('branch', None)
928 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
930 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
929
931
930 @annotatesubrepoerror
932 @annotatesubrepoerror
931 def incoming(self, ui, source, opts):
933 def incoming(self, ui, source, opts):
932 if 'rev' in opts or 'branch' in opts:
934 if 'rev' in opts or 'branch' in opts:
933 opts = copy.copy(opts)
935 opts = copy.copy(opts)
934 opts.pop('rev', None)
936 opts.pop('rev', None)
935 opts.pop('branch', None)
937 opts.pop('branch', None)
936 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
938 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
937
939
938 @annotatesubrepoerror
940 @annotatesubrepoerror
939 def files(self):
941 def files(self):
940 rev = self._state[1]
942 rev = self._state[1]
941 ctx = self._repo[rev]
943 ctx = self._repo[rev]
942 return ctx.manifest().keys()
944 return ctx.manifest().keys()
943
945
944 def filedata(self, name):
946 def filedata(self, name):
945 rev = self._state[1]
947 rev = self._state[1]
946 return self._repo[rev][name].data()
948 return self._repo[rev][name].data()
947
949
948 def fileflags(self, name):
950 def fileflags(self, name):
949 rev = self._state[1]
951 rev = self._state[1]
950 ctx = self._repo[rev]
952 ctx = self._repo[rev]
951 return ctx.flags(name)
953 return ctx.flags(name)
952
954
953 @annotatesubrepoerror
955 @annotatesubrepoerror
954 def printfiles(self, ui, m, fm, fmt, subrepos):
956 def printfiles(self, ui, m, fm, fmt, subrepos):
955 # If the parent context is a workingctx, use the workingctx here for
957 # If the parent context is a workingctx, use the workingctx here for
956 # consistency.
958 # consistency.
957 if self._ctx.rev() is None:
959 if self._ctx.rev() is None:
958 ctx = self._repo[None]
960 ctx = self._repo[None]
959 else:
961 else:
960 rev = self._state[1]
962 rev = self._state[1]
961 ctx = self._repo[rev]
963 ctx = self._repo[rev]
962 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
964 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
963
965
964 @annotatesubrepoerror
966 @annotatesubrepoerror
965 def getfileset(self, expr):
967 def getfileset(self, expr):
966 if self._ctx.rev() is None:
968 if self._ctx.rev() is None:
967 ctx = self._repo[None]
969 ctx = self._repo[None]
968 else:
970 else:
969 rev = self._state[1]
971 rev = self._state[1]
970 ctx = self._repo[rev]
972 ctx = self._repo[rev]
971
973
972 files = ctx.getfileset(expr)
974 files = ctx.getfileset(expr)
973
975
974 for subpath in ctx.substate:
976 for subpath in ctx.substate:
975 sub = ctx.sub(subpath)
977 sub = ctx.sub(subpath)
976
978
977 try:
979 try:
978 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
980 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
979 except error.LookupError:
981 except error.LookupError:
980 self.ui.status(_("skipping missing subrepository: %s\n")
982 self.ui.status(_("skipping missing subrepository: %s\n")
981 % self.wvfs.reljoin(reporelpath(self), subpath))
983 % self.wvfs.reljoin(reporelpath(self), subpath))
982 return files
984 return files
983
985
984 def walk(self, match):
986 def walk(self, match):
985 ctx = self._repo[None]
987 ctx = self._repo[None]
986 return ctx.walk(match)
988 return ctx.walk(match)
987
989
988 @annotatesubrepoerror
990 @annotatesubrepoerror
989 def forget(self, match, prefix):
991 def forget(self, match, prefix):
990 return cmdutil.forget(self.ui, self._repo, match,
992 return cmdutil.forget(self.ui, self._repo, match,
991 self.wvfs.reljoin(prefix, self._path), True)
993 self.wvfs.reljoin(prefix, self._path), True)
992
994
993 @annotatesubrepoerror
995 @annotatesubrepoerror
994 def removefiles(self, matcher, prefix, after, force, subrepos):
996 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
995 return cmdutil.remove(self.ui, self._repo, matcher,
997 return cmdutil.remove(self.ui, self._repo, matcher,
996 self.wvfs.reljoin(prefix, self._path),
998 self.wvfs.reljoin(prefix, self._path),
997 after, force, subrepos)
999 after, force, subrepos)
998
1000
999 @annotatesubrepoerror
1001 @annotatesubrepoerror
1000 def revert(self, substate, *pats, **opts):
1002 def revert(self, substate, *pats, **opts):
1001 # reverting a subrepo is a 2 step process:
1003 # reverting a subrepo is a 2 step process:
1002 # 1. if the no_backup is not set, revert all modified
1004 # 1. if the no_backup is not set, revert all modified
1003 # files inside the subrepo
1005 # files inside the subrepo
1004 # 2. update the subrepo to the revision specified in
1006 # 2. update the subrepo to the revision specified in
1005 # the corresponding substate dictionary
1007 # the corresponding substate dictionary
1006 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1008 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1007 if not opts.get('no_backup'):
1009 if not opts.get('no_backup'):
1008 # Revert all files on the subrepo, creating backups
1010 # Revert all files on the subrepo, creating backups
1009 # Note that this will not recursively revert subrepos
1011 # Note that this will not recursively revert subrepos
1010 # We could do it if there was a set:subrepos() predicate
1012 # We could do it if there was a set:subrepos() predicate
1011 opts = opts.copy()
1013 opts = opts.copy()
1012 opts['date'] = None
1014 opts['date'] = None
1013 opts['rev'] = substate[1]
1015 opts['rev'] = substate[1]
1014
1016
1015 self.filerevert(*pats, **opts)
1017 self.filerevert(*pats, **opts)
1016
1018
1017 # Update the repo to the revision specified in the given substate
1019 # Update the repo to the revision specified in the given substate
1018 if not opts.get('dry_run'):
1020 if not opts.get('dry_run'):
1019 self.get(substate, overwrite=True)
1021 self.get(substate, overwrite=True)
1020
1022
1021 def filerevert(self, *pats, **opts):
1023 def filerevert(self, *pats, **opts):
1022 ctx = self._repo[opts['rev']]
1024 ctx = self._repo[opts['rev']]
1023 parents = self._repo.dirstate.parents()
1025 parents = self._repo.dirstate.parents()
1024 if opts.get('all'):
1026 if opts.get('all'):
1025 pats = ['set:modified()']
1027 pats = ['set:modified()']
1026 else:
1028 else:
1027 pats = []
1029 pats = []
1028 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1030 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1029
1031
1030 def shortid(self, revid):
1032 def shortid(self, revid):
1031 return revid[:12]
1033 return revid[:12]
1032
1034
1033 def verify(self):
1035 def verify(self):
1034 try:
1036 try:
1035 rev = self._state[1]
1037 rev = self._state[1]
1036 ctx = self._repo.unfiltered()[rev]
1038 ctx = self._repo.unfiltered()[rev]
1037 if ctx.hidden():
1039 if ctx.hidden():
1038 # Since hidden revisions aren't pushed/pulled, it seems worth an
1040 # Since hidden revisions aren't pushed/pulled, it seems worth an
1039 # explicit warning.
1041 # explicit warning.
1040 ui = self._repo.ui
1042 ui = self._repo.ui
1041 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1043 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1042 (self._relpath, node.short(self._ctx.node())))
1044 (self._relpath, node.short(self._ctx.node())))
1043 return 0
1045 return 0
1044 except error.RepoLookupError:
1046 except error.RepoLookupError:
1045 # A missing subrepo revision may be a case of needing to pull it, so
1047 # A missing subrepo revision may be a case of needing to pull it, so
1046 # don't treat this as an error.
1048 # don't treat this as an error.
1047 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1049 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1048 (self._relpath, node.short(self._ctx.node())))
1050 (self._relpath, node.short(self._ctx.node())))
1049 return 0
1051 return 0
1050
1052
1051 @propertycache
1053 @propertycache
1052 def wvfs(self):
1054 def wvfs(self):
1053 """return own wvfs for efficiency and consistency
1055 """return own wvfs for efficiency and consistency
1054 """
1056 """
1055 return self._repo.wvfs
1057 return self._repo.wvfs
1056
1058
1057 @propertycache
1059 @propertycache
1058 def _relpath(self):
1060 def _relpath(self):
1059 """return path to this subrepository as seen from outermost repository
1061 """return path to this subrepository as seen from outermost repository
1060 """
1062 """
1061 # Keep consistent dir separators by avoiding vfs.join(self._path)
1063 # Keep consistent dir separators by avoiding vfs.join(self._path)
1062 return reporelpath(self._repo)
1064 return reporelpath(self._repo)
1063
1065
1064 class svnsubrepo(abstractsubrepo):
1066 class svnsubrepo(abstractsubrepo):
1065 def __init__(self, ctx, path, state):
1067 def __init__(self, ctx, path, state):
1066 super(svnsubrepo, self).__init__(ctx, path)
1068 super(svnsubrepo, self).__init__(ctx, path)
1067 self._state = state
1069 self._state = state
1068 self._exe = util.findexe('svn')
1070 self._exe = util.findexe('svn')
1069 if not self._exe:
1071 if not self._exe:
1070 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1072 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1071 % self._path)
1073 % self._path)
1072
1074
1073 def _svncommand(self, commands, filename='', failok=False):
1075 def _svncommand(self, commands, filename='', failok=False):
1074 cmd = [self._exe]
1076 cmd = [self._exe]
1075 extrakw = {}
1077 extrakw = {}
1076 if not self.ui.interactive():
1078 if not self.ui.interactive():
1077 # Making stdin be a pipe should prevent svn from behaving
1079 # Making stdin be a pipe should prevent svn from behaving
1078 # interactively even if we can't pass --non-interactive.
1080 # interactively even if we can't pass --non-interactive.
1079 extrakw['stdin'] = subprocess.PIPE
1081 extrakw['stdin'] = subprocess.PIPE
1080 # Starting in svn 1.5 --non-interactive is a global flag
1082 # Starting in svn 1.5 --non-interactive is a global flag
1081 # instead of being per-command, but we need to support 1.4 so
1083 # instead of being per-command, but we need to support 1.4 so
1082 # we have to be intelligent about what commands take
1084 # we have to be intelligent about what commands take
1083 # --non-interactive.
1085 # --non-interactive.
1084 if commands[0] in ('update', 'checkout', 'commit'):
1086 if commands[0] in ('update', 'checkout', 'commit'):
1085 cmd.append('--non-interactive')
1087 cmd.append('--non-interactive')
1086 cmd.extend(commands)
1088 cmd.extend(commands)
1087 if filename is not None:
1089 if filename is not None:
1088 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1090 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1089 self._path, filename)
1091 self._path, filename)
1090 cmd.append(path)
1092 cmd.append(path)
1091 env = dict(os.environ)
1093 env = dict(os.environ)
1092 # Avoid localized output, preserve current locale for everything else.
1094 # Avoid localized output, preserve current locale for everything else.
1093 lc_all = env.get('LC_ALL')
1095 lc_all = env.get('LC_ALL')
1094 if lc_all:
1096 if lc_all:
1095 env['LANG'] = lc_all
1097 env['LANG'] = lc_all
1096 del env['LC_ALL']
1098 del env['LC_ALL']
1097 env['LC_MESSAGES'] = 'C'
1099 env['LC_MESSAGES'] = 'C'
1098 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1100 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1099 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1101 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1100 universal_newlines=True, env=env, **extrakw)
1102 universal_newlines=True, env=env, **extrakw)
1101 stdout, stderr = p.communicate()
1103 stdout, stderr = p.communicate()
1102 stderr = stderr.strip()
1104 stderr = stderr.strip()
1103 if not failok:
1105 if not failok:
1104 if p.returncode:
1106 if p.returncode:
1105 raise error.Abort(stderr or 'exited with code %d'
1107 raise error.Abort(stderr or 'exited with code %d'
1106 % p.returncode)
1108 % p.returncode)
1107 if stderr:
1109 if stderr:
1108 self.ui.warn(stderr + '\n')
1110 self.ui.warn(stderr + '\n')
1109 return stdout, stderr
1111 return stdout, stderr
1110
1112
1111 @propertycache
1113 @propertycache
1112 def _svnversion(self):
1114 def _svnversion(self):
1113 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1115 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1114 m = re.search(r'^(\d+)\.(\d+)', output)
1116 m = re.search(r'^(\d+)\.(\d+)', output)
1115 if not m:
1117 if not m:
1116 raise error.Abort(_('cannot retrieve svn tool version'))
1118 raise error.Abort(_('cannot retrieve svn tool version'))
1117 return (int(m.group(1)), int(m.group(2)))
1119 return (int(m.group(1)), int(m.group(2)))
1118
1120
1119 def _wcrevs(self):
1121 def _wcrevs(self):
1120 # Get the working directory revision as well as the last
1122 # Get the working directory revision as well as the last
1121 # commit revision so we can compare the subrepo state with
1123 # commit revision so we can compare the subrepo state with
1122 # both. We used to store the working directory one.
1124 # both. We used to store the working directory one.
1123 output, err = self._svncommand(['info', '--xml'])
1125 output, err = self._svncommand(['info', '--xml'])
1124 doc = xml.dom.minidom.parseString(output)
1126 doc = xml.dom.minidom.parseString(output)
1125 entries = doc.getElementsByTagName('entry')
1127 entries = doc.getElementsByTagName('entry')
1126 lastrev, rev = '0', '0'
1128 lastrev, rev = '0', '0'
1127 if entries:
1129 if entries:
1128 rev = str(entries[0].getAttribute('revision')) or '0'
1130 rev = str(entries[0].getAttribute('revision')) or '0'
1129 commits = entries[0].getElementsByTagName('commit')
1131 commits = entries[0].getElementsByTagName('commit')
1130 if commits:
1132 if commits:
1131 lastrev = str(commits[0].getAttribute('revision')) or '0'
1133 lastrev = str(commits[0].getAttribute('revision')) or '0'
1132 return (lastrev, rev)
1134 return (lastrev, rev)
1133
1135
1134 def _wcrev(self):
1136 def _wcrev(self):
1135 return self._wcrevs()[0]
1137 return self._wcrevs()[0]
1136
1138
1137 def _wcchanged(self):
1139 def _wcchanged(self):
1138 """Return (changes, extchanges, missing) where changes is True
1140 """Return (changes, extchanges, missing) where changes is True
1139 if the working directory was changed, extchanges is
1141 if the working directory was changed, extchanges is
1140 True if any of these changes concern an external entry and missing
1142 True if any of these changes concern an external entry and missing
1141 is True if any change is a missing entry.
1143 is True if any change is a missing entry.
1142 """
1144 """
1143 output, err = self._svncommand(['status', '--xml'])
1145 output, err = self._svncommand(['status', '--xml'])
1144 externals, changes, missing = [], [], []
1146 externals, changes, missing = [], [], []
1145 doc = xml.dom.minidom.parseString(output)
1147 doc = xml.dom.minidom.parseString(output)
1146 for e in doc.getElementsByTagName('entry'):
1148 for e in doc.getElementsByTagName('entry'):
1147 s = e.getElementsByTagName('wc-status')
1149 s = e.getElementsByTagName('wc-status')
1148 if not s:
1150 if not s:
1149 continue
1151 continue
1150 item = s[0].getAttribute('item')
1152 item = s[0].getAttribute('item')
1151 props = s[0].getAttribute('props')
1153 props = s[0].getAttribute('props')
1152 path = e.getAttribute('path')
1154 path = e.getAttribute('path')
1153 if item == 'external':
1155 if item == 'external':
1154 externals.append(path)
1156 externals.append(path)
1155 elif item == 'missing':
1157 elif item == 'missing':
1156 missing.append(path)
1158 missing.append(path)
1157 if (item not in ('', 'normal', 'unversioned', 'external')
1159 if (item not in ('', 'normal', 'unversioned', 'external')
1158 or props not in ('', 'none', 'normal')):
1160 or props not in ('', 'none', 'normal')):
1159 changes.append(path)
1161 changes.append(path)
1160 for path in changes:
1162 for path in changes:
1161 for ext in externals:
1163 for ext in externals:
1162 if path == ext or path.startswith(ext + os.sep):
1164 if path == ext or path.startswith(ext + os.sep):
1163 return True, True, bool(missing)
1165 return True, True, bool(missing)
1164 return bool(changes), False, bool(missing)
1166 return bool(changes), False, bool(missing)
1165
1167
1166 def dirty(self, ignoreupdate=False):
1168 def dirty(self, ignoreupdate=False):
1167 if not self._wcchanged()[0]:
1169 if not self._wcchanged()[0]:
1168 if self._state[1] in self._wcrevs() or ignoreupdate:
1170 if self._state[1] in self._wcrevs() or ignoreupdate:
1169 return False
1171 return False
1170 return True
1172 return True
1171
1173
1172 def basestate(self):
1174 def basestate(self):
1173 lastrev, rev = self._wcrevs()
1175 lastrev, rev = self._wcrevs()
1174 if lastrev != rev:
1176 if lastrev != rev:
1175 # Last committed rev is not the same than rev. We would
1177 # Last committed rev is not the same than rev. We would
1176 # like to take lastrev but we do not know if the subrepo
1178 # like to take lastrev but we do not know if the subrepo
1177 # URL exists at lastrev. Test it and fallback to rev it
1179 # URL exists at lastrev. Test it and fallback to rev it
1178 # is not there.
1180 # is not there.
1179 try:
1181 try:
1180 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1182 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1181 return lastrev
1183 return lastrev
1182 except error.Abort:
1184 except error.Abort:
1183 pass
1185 pass
1184 return rev
1186 return rev
1185
1187
1186 @annotatesubrepoerror
1188 @annotatesubrepoerror
1187 def commit(self, text, user, date):
1189 def commit(self, text, user, date):
1188 # user and date are out of our hands since svn is centralized
1190 # user and date are out of our hands since svn is centralized
1189 changed, extchanged, missing = self._wcchanged()
1191 changed, extchanged, missing = self._wcchanged()
1190 if not changed:
1192 if not changed:
1191 return self.basestate()
1193 return self.basestate()
1192 if extchanged:
1194 if extchanged:
1193 # Do not try to commit externals
1195 # Do not try to commit externals
1194 raise error.Abort(_('cannot commit svn externals'))
1196 raise error.Abort(_('cannot commit svn externals'))
1195 if missing:
1197 if missing:
1196 # svn can commit with missing entries but aborting like hg
1198 # svn can commit with missing entries but aborting like hg
1197 # seems a better approach.
1199 # seems a better approach.
1198 raise error.Abort(_('cannot commit missing svn entries'))
1200 raise error.Abort(_('cannot commit missing svn entries'))
1199 commitinfo, err = self._svncommand(['commit', '-m', text])
1201 commitinfo, err = self._svncommand(['commit', '-m', text])
1200 self.ui.status(commitinfo)
1202 self.ui.status(commitinfo)
1201 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1203 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1202 if not newrev:
1204 if not newrev:
1203 if not commitinfo.strip():
1205 if not commitinfo.strip():
1204 # Sometimes, our definition of "changed" differs from
1206 # Sometimes, our definition of "changed" differs from
1205 # svn one. For instance, svn ignores missing files
1207 # svn one. For instance, svn ignores missing files
1206 # when committing. If there are only missing files, no
1208 # when committing. If there are only missing files, no
1207 # commit is made, no output and no error code.
1209 # commit is made, no output and no error code.
1208 raise error.Abort(_('failed to commit svn changes'))
1210 raise error.Abort(_('failed to commit svn changes'))
1209 raise error.Abort(commitinfo.splitlines()[-1])
1211 raise error.Abort(commitinfo.splitlines()[-1])
1210 newrev = newrev.groups()[0]
1212 newrev = newrev.groups()[0]
1211 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1213 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1212 return newrev
1214 return newrev
1213
1215
1214 @annotatesubrepoerror
1216 @annotatesubrepoerror
1215 def remove(self):
1217 def remove(self):
1216 if self.dirty():
1218 if self.dirty():
1217 self.ui.warn(_('not removing repo %s because '
1219 self.ui.warn(_('not removing repo %s because '
1218 'it has changes.\n') % self._path)
1220 'it has changes.\n') % self._path)
1219 return
1221 return
1220 self.ui.note(_('removing subrepo %s\n') % self._path)
1222 self.ui.note(_('removing subrepo %s\n') % self._path)
1221
1223
1222 self.wvfs.rmtree(forcibly=True)
1224 self.wvfs.rmtree(forcibly=True)
1223 try:
1225 try:
1224 pwvfs = self._ctx.repo().wvfs
1226 pwvfs = self._ctx.repo().wvfs
1225 pwvfs.removedirs(pwvfs.dirname(self._path))
1227 pwvfs.removedirs(pwvfs.dirname(self._path))
1226 except OSError:
1228 except OSError:
1227 pass
1229 pass
1228
1230
1229 @annotatesubrepoerror
1231 @annotatesubrepoerror
1230 def get(self, state, overwrite=False):
1232 def get(self, state, overwrite=False):
1231 if overwrite:
1233 if overwrite:
1232 self._svncommand(['revert', '--recursive'])
1234 self._svncommand(['revert', '--recursive'])
1233 args = ['checkout']
1235 args = ['checkout']
1234 if self._svnversion >= (1, 5):
1236 if self._svnversion >= (1, 5):
1235 args.append('--force')
1237 args.append('--force')
1236 # The revision must be specified at the end of the URL to properly
1238 # The revision must be specified at the end of the URL to properly
1237 # update to a directory which has since been deleted and recreated.
1239 # update to a directory which has since been deleted and recreated.
1238 args.append('%s@%s' % (state[0], state[1]))
1240 args.append('%s@%s' % (state[0], state[1]))
1239 status, err = self._svncommand(args, failok=True)
1241 status, err = self._svncommand(args, failok=True)
1240 _sanitize(self.ui, self.wvfs, '.svn')
1242 _sanitize(self.ui, self.wvfs, '.svn')
1241 if not re.search('Checked out revision [0-9]+.', status):
1243 if not re.search('Checked out revision [0-9]+.', status):
1242 if ('is already a working copy for a different URL' in err
1244 if ('is already a working copy for a different URL' in err
1243 and (self._wcchanged()[:2] == (False, False))):
1245 and (self._wcchanged()[:2] == (False, False))):
1244 # obstructed but clean working copy, so just blow it away.
1246 # obstructed but clean working copy, so just blow it away.
1245 self.remove()
1247 self.remove()
1246 self.get(state, overwrite=False)
1248 self.get(state, overwrite=False)
1247 return
1249 return
1248 raise error.Abort((status or err).splitlines()[-1])
1250 raise error.Abort((status or err).splitlines()[-1])
1249 self.ui.status(status)
1251 self.ui.status(status)
1250
1252
1251 @annotatesubrepoerror
1253 @annotatesubrepoerror
1252 def merge(self, state):
1254 def merge(self, state):
1253 old = self._state[1]
1255 old = self._state[1]
1254 new = state[1]
1256 new = state[1]
1255 wcrev = self._wcrev()
1257 wcrev = self._wcrev()
1256 if new != wcrev:
1258 if new != wcrev:
1257 dirty = old == wcrev or self._wcchanged()[0]
1259 dirty = old == wcrev or self._wcchanged()[0]
1258 if _updateprompt(self.ui, self, dirty, wcrev, new):
1260 if _updateprompt(self.ui, self, dirty, wcrev, new):
1259 self.get(state, False)
1261 self.get(state, False)
1260
1262
1261 def push(self, opts):
1263 def push(self, opts):
1262 # push is a no-op for SVN
1264 # push is a no-op for SVN
1263 return True
1265 return True
1264
1266
1265 @annotatesubrepoerror
1267 @annotatesubrepoerror
1266 def files(self):
1268 def files(self):
1267 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1269 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1268 doc = xml.dom.minidom.parseString(output)
1270 doc = xml.dom.minidom.parseString(output)
1269 paths = []
1271 paths = []
1270 for e in doc.getElementsByTagName('entry'):
1272 for e in doc.getElementsByTagName('entry'):
1271 kind = str(e.getAttribute('kind'))
1273 kind = str(e.getAttribute('kind'))
1272 if kind != 'file':
1274 if kind != 'file':
1273 continue
1275 continue
1274 name = ''.join(c.data for c
1276 name = ''.join(c.data for c
1275 in e.getElementsByTagName('name')[0].childNodes
1277 in e.getElementsByTagName('name')[0].childNodes
1276 if c.nodeType == c.TEXT_NODE)
1278 if c.nodeType == c.TEXT_NODE)
1277 paths.append(name.encode('utf-8'))
1279 paths.append(name.encode('utf-8'))
1278 return paths
1280 return paths
1279
1281
1280 def filedata(self, name):
1282 def filedata(self, name):
1281 return self._svncommand(['cat'], name)[0]
1283 return self._svncommand(['cat'], name)[0]
1282
1284
1283
1285
1284 class gitsubrepo(abstractsubrepo):
1286 class gitsubrepo(abstractsubrepo):
1285 def __init__(self, ctx, path, state):
1287 def __init__(self, ctx, path, state):
1286 super(gitsubrepo, self).__init__(ctx, path)
1288 super(gitsubrepo, self).__init__(ctx, path)
1287 self._state = state
1289 self._state = state
1288 self._abspath = ctx.repo().wjoin(path)
1290 self._abspath = ctx.repo().wjoin(path)
1289 self._subparent = ctx.repo()
1291 self._subparent = ctx.repo()
1290 self._ensuregit()
1292 self._ensuregit()
1291
1293
1292 def _ensuregit(self):
1294 def _ensuregit(self):
1293 try:
1295 try:
1294 self._gitexecutable = 'git'
1296 self._gitexecutable = 'git'
1295 out, err = self._gitnodir(['--version'])
1297 out, err = self._gitnodir(['--version'])
1296 except OSError as e:
1298 except OSError as e:
1297 genericerror = _("error executing git for subrepo '%s': %s")
1299 genericerror = _("error executing git for subrepo '%s': %s")
1298 notfoundhint = _("check git is installed and in your PATH")
1300 notfoundhint = _("check git is installed and in your PATH")
1299 if e.errno != errno.ENOENT:
1301 if e.errno != errno.ENOENT:
1300 raise error.Abort(genericerror % (self._path, e.strerror))
1302 raise error.Abort(genericerror % (self._path, e.strerror))
1301 elif os.name == 'nt':
1303 elif os.name == 'nt':
1302 try:
1304 try:
1303 self._gitexecutable = 'git.cmd'
1305 self._gitexecutable = 'git.cmd'
1304 out, err = self._gitnodir(['--version'])
1306 out, err = self._gitnodir(['--version'])
1305 except OSError as e2:
1307 except OSError as e2:
1306 if e2.errno == errno.ENOENT:
1308 if e2.errno == errno.ENOENT:
1307 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1309 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1308 " for subrepo '%s'") % self._path,
1310 " for subrepo '%s'") % self._path,
1309 hint=notfoundhint)
1311 hint=notfoundhint)
1310 else:
1312 else:
1311 raise error.Abort(genericerror % (self._path,
1313 raise error.Abort(genericerror % (self._path,
1312 e2.strerror))
1314 e2.strerror))
1313 else:
1315 else:
1314 raise error.Abort(_("couldn't find git for subrepo '%s'")
1316 raise error.Abort(_("couldn't find git for subrepo '%s'")
1315 % self._path, hint=notfoundhint)
1317 % self._path, hint=notfoundhint)
1316 versionstatus = self._checkversion(out)
1318 versionstatus = self._checkversion(out)
1317 if versionstatus == 'unknown':
1319 if versionstatus == 'unknown':
1318 self.ui.warn(_('cannot retrieve git version\n'))
1320 self.ui.warn(_('cannot retrieve git version\n'))
1319 elif versionstatus == 'abort':
1321 elif versionstatus == 'abort':
1320 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1322 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1321 elif versionstatus == 'warning':
1323 elif versionstatus == 'warning':
1322 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1324 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1323
1325
1324 @staticmethod
1326 @staticmethod
1325 def _gitversion(out):
1327 def _gitversion(out):
1326 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1328 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1327 if m:
1329 if m:
1328 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1330 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1329
1331
1330 m = re.search(r'^git version (\d+)\.(\d+)', out)
1332 m = re.search(r'^git version (\d+)\.(\d+)', out)
1331 if m:
1333 if m:
1332 return (int(m.group(1)), int(m.group(2)), 0)
1334 return (int(m.group(1)), int(m.group(2)), 0)
1333
1335
1334 return -1
1336 return -1
1335
1337
1336 @staticmethod
1338 @staticmethod
1337 def _checkversion(out):
1339 def _checkversion(out):
1338 '''ensure git version is new enough
1340 '''ensure git version is new enough
1339
1341
1340 >>> _checkversion = gitsubrepo._checkversion
1342 >>> _checkversion = gitsubrepo._checkversion
1341 >>> _checkversion('git version 1.6.0')
1343 >>> _checkversion('git version 1.6.0')
1342 'ok'
1344 'ok'
1343 >>> _checkversion('git version 1.8.5')
1345 >>> _checkversion('git version 1.8.5')
1344 'ok'
1346 'ok'
1345 >>> _checkversion('git version 1.4.0')
1347 >>> _checkversion('git version 1.4.0')
1346 'abort'
1348 'abort'
1347 >>> _checkversion('git version 1.5.0')
1349 >>> _checkversion('git version 1.5.0')
1348 'warning'
1350 'warning'
1349 >>> _checkversion('git version 1.9-rc0')
1351 >>> _checkversion('git version 1.9-rc0')
1350 'ok'
1352 'ok'
1351 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1353 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1352 'ok'
1354 'ok'
1353 >>> _checkversion('git version 1.9.0.GIT')
1355 >>> _checkversion('git version 1.9.0.GIT')
1354 'ok'
1356 'ok'
1355 >>> _checkversion('git version 12345')
1357 >>> _checkversion('git version 12345')
1356 'unknown'
1358 'unknown'
1357 >>> _checkversion('no')
1359 >>> _checkversion('no')
1358 'unknown'
1360 'unknown'
1359 '''
1361 '''
1360 version = gitsubrepo._gitversion(out)
1362 version = gitsubrepo._gitversion(out)
1361 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1363 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1362 # despite the docstring comment. For now, error on 1.4.0, warn on
1364 # despite the docstring comment. For now, error on 1.4.0, warn on
1363 # 1.5.0 but attempt to continue.
1365 # 1.5.0 but attempt to continue.
1364 if version == -1:
1366 if version == -1:
1365 return 'unknown'
1367 return 'unknown'
1366 if version < (1, 5, 0):
1368 if version < (1, 5, 0):
1367 return 'abort'
1369 return 'abort'
1368 elif version < (1, 6, 0):
1370 elif version < (1, 6, 0):
1369 return 'warning'
1371 return 'warning'
1370 return 'ok'
1372 return 'ok'
1371
1373
1372 def _gitcommand(self, commands, env=None, stream=False):
1374 def _gitcommand(self, commands, env=None, stream=False):
1373 return self._gitdir(commands, env=env, stream=stream)[0]
1375 return self._gitdir(commands, env=env, stream=stream)[0]
1374
1376
1375 def _gitdir(self, commands, env=None, stream=False):
1377 def _gitdir(self, commands, env=None, stream=False):
1376 return self._gitnodir(commands, env=env, stream=stream,
1378 return self._gitnodir(commands, env=env, stream=stream,
1377 cwd=self._abspath)
1379 cwd=self._abspath)
1378
1380
1379 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1381 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1380 """Calls the git command
1382 """Calls the git command
1381
1383
1382 The methods tries to call the git command. versions prior to 1.6.0
1384 The methods tries to call the git command. versions prior to 1.6.0
1383 are not supported and very probably fail.
1385 are not supported and very probably fail.
1384 """
1386 """
1385 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1387 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1386 # unless ui.quiet is set, print git's stderr,
1388 # unless ui.quiet is set, print git's stderr,
1387 # which is mostly progress and useful info
1389 # which is mostly progress and useful info
1388 errpipe = None
1390 errpipe = None
1389 if self.ui.quiet:
1391 if self.ui.quiet:
1390 errpipe = open(os.devnull, 'w')
1392 errpipe = open(os.devnull, 'w')
1391 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1393 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1392 cwd=cwd, env=env, close_fds=util.closefds,
1394 cwd=cwd, env=env, close_fds=util.closefds,
1393 stdout=subprocess.PIPE, stderr=errpipe)
1395 stdout=subprocess.PIPE, stderr=errpipe)
1394 if stream:
1396 if stream:
1395 return p.stdout, None
1397 return p.stdout, None
1396
1398
1397 retdata = p.stdout.read().strip()
1399 retdata = p.stdout.read().strip()
1398 # wait for the child to exit to avoid race condition.
1400 # wait for the child to exit to avoid race condition.
1399 p.wait()
1401 p.wait()
1400
1402
1401 if p.returncode != 0 and p.returncode != 1:
1403 if p.returncode != 0 and p.returncode != 1:
1402 # there are certain error codes that are ok
1404 # there are certain error codes that are ok
1403 command = commands[0]
1405 command = commands[0]
1404 if command in ('cat-file', 'symbolic-ref'):
1406 if command in ('cat-file', 'symbolic-ref'):
1405 return retdata, p.returncode
1407 return retdata, p.returncode
1406 # for all others, abort
1408 # for all others, abort
1407 raise error.Abort('git %s error %d in %s' %
1409 raise error.Abort('git %s error %d in %s' %
1408 (command, p.returncode, self._relpath))
1410 (command, p.returncode, self._relpath))
1409
1411
1410 return retdata, p.returncode
1412 return retdata, p.returncode
1411
1413
1412 def _gitmissing(self):
1414 def _gitmissing(self):
1413 return not self.wvfs.exists('.git')
1415 return not self.wvfs.exists('.git')
1414
1416
1415 def _gitstate(self):
1417 def _gitstate(self):
1416 return self._gitcommand(['rev-parse', 'HEAD'])
1418 return self._gitcommand(['rev-parse', 'HEAD'])
1417
1419
1418 def _gitcurrentbranch(self):
1420 def _gitcurrentbranch(self):
1419 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1421 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1420 if err:
1422 if err:
1421 current = None
1423 current = None
1422 return current
1424 return current
1423
1425
1424 def _gitremote(self, remote):
1426 def _gitremote(self, remote):
1425 out = self._gitcommand(['remote', 'show', '-n', remote])
1427 out = self._gitcommand(['remote', 'show', '-n', remote])
1426 line = out.split('\n')[1]
1428 line = out.split('\n')[1]
1427 i = line.index('URL: ') + len('URL: ')
1429 i = line.index('URL: ') + len('URL: ')
1428 return line[i:]
1430 return line[i:]
1429
1431
1430 def _githavelocally(self, revision):
1432 def _githavelocally(self, revision):
1431 out, code = self._gitdir(['cat-file', '-e', revision])
1433 out, code = self._gitdir(['cat-file', '-e', revision])
1432 return code == 0
1434 return code == 0
1433
1435
1434 def _gitisancestor(self, r1, r2):
1436 def _gitisancestor(self, r1, r2):
1435 base = self._gitcommand(['merge-base', r1, r2])
1437 base = self._gitcommand(['merge-base', r1, r2])
1436 return base == r1
1438 return base == r1
1437
1439
1438 def _gitisbare(self):
1440 def _gitisbare(self):
1439 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1441 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1440
1442
1441 def _gitupdatestat(self):
1443 def _gitupdatestat(self):
1442 """This must be run before git diff-index.
1444 """This must be run before git diff-index.
1443 diff-index only looks at changes to file stat;
1445 diff-index only looks at changes to file stat;
1444 this command looks at file contents and updates the stat."""
1446 this command looks at file contents and updates the stat."""
1445 self._gitcommand(['update-index', '-q', '--refresh'])
1447 self._gitcommand(['update-index', '-q', '--refresh'])
1446
1448
1447 def _gitbranchmap(self):
1449 def _gitbranchmap(self):
1448 '''returns 2 things:
1450 '''returns 2 things:
1449 a map from git branch to revision
1451 a map from git branch to revision
1450 a map from revision to branches'''
1452 a map from revision to branches'''
1451 branch2rev = {}
1453 branch2rev = {}
1452 rev2branch = {}
1454 rev2branch = {}
1453
1455
1454 out = self._gitcommand(['for-each-ref', '--format',
1456 out = self._gitcommand(['for-each-ref', '--format',
1455 '%(objectname) %(refname)'])
1457 '%(objectname) %(refname)'])
1456 for line in out.split('\n'):
1458 for line in out.split('\n'):
1457 revision, ref = line.split(' ')
1459 revision, ref = line.split(' ')
1458 if (not ref.startswith('refs/heads/') and
1460 if (not ref.startswith('refs/heads/') and
1459 not ref.startswith('refs/remotes/')):
1461 not ref.startswith('refs/remotes/')):
1460 continue
1462 continue
1461 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1463 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1462 continue # ignore remote/HEAD redirects
1464 continue # ignore remote/HEAD redirects
1463 branch2rev[ref] = revision
1465 branch2rev[ref] = revision
1464 rev2branch.setdefault(revision, []).append(ref)
1466 rev2branch.setdefault(revision, []).append(ref)
1465 return branch2rev, rev2branch
1467 return branch2rev, rev2branch
1466
1468
1467 def _gittracking(self, branches):
1469 def _gittracking(self, branches):
1468 'return map of remote branch to local tracking branch'
1470 'return map of remote branch to local tracking branch'
1469 # assumes no more than one local tracking branch for each remote
1471 # assumes no more than one local tracking branch for each remote
1470 tracking = {}
1472 tracking = {}
1471 for b in branches:
1473 for b in branches:
1472 if b.startswith('refs/remotes/'):
1474 if b.startswith('refs/remotes/'):
1473 continue
1475 continue
1474 bname = b.split('/', 2)[2]
1476 bname = b.split('/', 2)[2]
1475 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1477 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1476 if remote:
1478 if remote:
1477 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1479 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1478 tracking['refs/remotes/%s/%s' %
1480 tracking['refs/remotes/%s/%s' %
1479 (remote, ref.split('/', 2)[2])] = b
1481 (remote, ref.split('/', 2)[2])] = b
1480 return tracking
1482 return tracking
1481
1483
1482 def _abssource(self, source):
1484 def _abssource(self, source):
1483 if '://' not in source:
1485 if '://' not in source:
1484 # recognize the scp syntax as an absolute source
1486 # recognize the scp syntax as an absolute source
1485 colon = source.find(':')
1487 colon = source.find(':')
1486 if colon != -1 and '/' not in source[:colon]:
1488 if colon != -1 and '/' not in source[:colon]:
1487 return source
1489 return source
1488 self._subsource = source
1490 self._subsource = source
1489 return _abssource(self)
1491 return _abssource(self)
1490
1492
1491 def _fetch(self, source, revision):
1493 def _fetch(self, source, revision):
1492 if self._gitmissing():
1494 if self._gitmissing():
1493 source = self._abssource(source)
1495 source = self._abssource(source)
1494 self.ui.status(_('cloning subrepo %s from %s\n') %
1496 self.ui.status(_('cloning subrepo %s from %s\n') %
1495 (self._relpath, source))
1497 (self._relpath, source))
1496 self._gitnodir(['clone', source, self._abspath])
1498 self._gitnodir(['clone', source, self._abspath])
1497 if self._githavelocally(revision):
1499 if self._githavelocally(revision):
1498 return
1500 return
1499 self.ui.status(_('pulling subrepo %s from %s\n') %
1501 self.ui.status(_('pulling subrepo %s from %s\n') %
1500 (self._relpath, self._gitremote('origin')))
1502 (self._relpath, self._gitremote('origin')))
1501 # try only origin: the originally cloned repo
1503 # try only origin: the originally cloned repo
1502 self._gitcommand(['fetch'])
1504 self._gitcommand(['fetch'])
1503 if not self._githavelocally(revision):
1505 if not self._githavelocally(revision):
1504 raise error.Abort(_("revision %s does not exist in subrepo %s\n") %
1506 raise error.Abort(_("revision %s does not exist in subrepo %s\n") %
1505 (revision, self._relpath))
1507 (revision, self._relpath))
1506
1508
1507 @annotatesubrepoerror
1509 @annotatesubrepoerror
1508 def dirty(self, ignoreupdate=False):
1510 def dirty(self, ignoreupdate=False):
1509 if self._gitmissing():
1511 if self._gitmissing():
1510 return self._state[1] != ''
1512 return self._state[1] != ''
1511 if self._gitisbare():
1513 if self._gitisbare():
1512 return True
1514 return True
1513 if not ignoreupdate and self._state[1] != self._gitstate():
1515 if not ignoreupdate and self._state[1] != self._gitstate():
1514 # different version checked out
1516 # different version checked out
1515 return True
1517 return True
1516 # check for staged changes or modified files; ignore untracked files
1518 # check for staged changes or modified files; ignore untracked files
1517 self._gitupdatestat()
1519 self._gitupdatestat()
1518 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1520 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1519 return code == 1
1521 return code == 1
1520
1522
1521 def basestate(self):
1523 def basestate(self):
1522 return self._gitstate()
1524 return self._gitstate()
1523
1525
1524 @annotatesubrepoerror
1526 @annotatesubrepoerror
1525 def get(self, state, overwrite=False):
1527 def get(self, state, overwrite=False):
1526 source, revision, kind = state
1528 source, revision, kind = state
1527 if not revision:
1529 if not revision:
1528 self.remove()
1530 self.remove()
1529 return
1531 return
1530 self._fetch(source, revision)
1532 self._fetch(source, revision)
1531 # if the repo was set to be bare, unbare it
1533 # if the repo was set to be bare, unbare it
1532 if self._gitisbare():
1534 if self._gitisbare():
1533 self._gitcommand(['config', 'core.bare', 'false'])
1535 self._gitcommand(['config', 'core.bare', 'false'])
1534 if self._gitstate() == revision:
1536 if self._gitstate() == revision:
1535 self._gitcommand(['reset', '--hard', 'HEAD'])
1537 self._gitcommand(['reset', '--hard', 'HEAD'])
1536 return
1538 return
1537 elif self._gitstate() == revision:
1539 elif self._gitstate() == revision:
1538 if overwrite:
1540 if overwrite:
1539 # first reset the index to unmark new files for commit, because
1541 # first reset the index to unmark new files for commit, because
1540 # reset --hard will otherwise throw away files added for commit,
1542 # reset --hard will otherwise throw away files added for commit,
1541 # not just unmark them.
1543 # not just unmark them.
1542 self._gitcommand(['reset', 'HEAD'])
1544 self._gitcommand(['reset', 'HEAD'])
1543 self._gitcommand(['reset', '--hard', 'HEAD'])
1545 self._gitcommand(['reset', '--hard', 'HEAD'])
1544 return
1546 return
1545 branch2rev, rev2branch = self._gitbranchmap()
1547 branch2rev, rev2branch = self._gitbranchmap()
1546
1548
1547 def checkout(args):
1549 def checkout(args):
1548 cmd = ['checkout']
1550 cmd = ['checkout']
1549 if overwrite:
1551 if overwrite:
1550 # first reset the index to unmark new files for commit, because
1552 # first reset the index to unmark new files for commit, because
1551 # the -f option will otherwise throw away files added for
1553 # the -f option will otherwise throw away files added for
1552 # commit, not just unmark them.
1554 # commit, not just unmark them.
1553 self._gitcommand(['reset', 'HEAD'])
1555 self._gitcommand(['reset', 'HEAD'])
1554 cmd.append('-f')
1556 cmd.append('-f')
1555 self._gitcommand(cmd + args)
1557 self._gitcommand(cmd + args)
1556 _sanitize(self.ui, self.wvfs, '.git')
1558 _sanitize(self.ui, self.wvfs, '.git')
1557
1559
1558 def rawcheckout():
1560 def rawcheckout():
1559 # no branch to checkout, check it out with no branch
1561 # no branch to checkout, check it out with no branch
1560 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1562 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1561 self._relpath)
1563 self._relpath)
1562 self.ui.warn(_('check out a git branch if you intend '
1564 self.ui.warn(_('check out a git branch if you intend '
1563 'to make changes\n'))
1565 'to make changes\n'))
1564 checkout(['-q', revision])
1566 checkout(['-q', revision])
1565
1567
1566 if revision not in rev2branch:
1568 if revision not in rev2branch:
1567 rawcheckout()
1569 rawcheckout()
1568 return
1570 return
1569 branches = rev2branch[revision]
1571 branches = rev2branch[revision]
1570 firstlocalbranch = None
1572 firstlocalbranch = None
1571 for b in branches:
1573 for b in branches:
1572 if b == 'refs/heads/master':
1574 if b == 'refs/heads/master':
1573 # master trumps all other branches
1575 # master trumps all other branches
1574 checkout(['refs/heads/master'])
1576 checkout(['refs/heads/master'])
1575 return
1577 return
1576 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1578 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1577 firstlocalbranch = b
1579 firstlocalbranch = b
1578 if firstlocalbranch:
1580 if firstlocalbranch:
1579 checkout([firstlocalbranch])
1581 checkout([firstlocalbranch])
1580 return
1582 return
1581
1583
1582 tracking = self._gittracking(branch2rev.keys())
1584 tracking = self._gittracking(branch2rev.keys())
1583 # choose a remote branch already tracked if possible
1585 # choose a remote branch already tracked if possible
1584 remote = branches[0]
1586 remote = branches[0]
1585 if remote not in tracking:
1587 if remote not in tracking:
1586 for b in branches:
1588 for b in branches:
1587 if b in tracking:
1589 if b in tracking:
1588 remote = b
1590 remote = b
1589 break
1591 break
1590
1592
1591 if remote not in tracking:
1593 if remote not in tracking:
1592 # create a new local tracking branch
1594 # create a new local tracking branch
1593 local = remote.split('/', 3)[3]
1595 local = remote.split('/', 3)[3]
1594 checkout(['-b', local, remote])
1596 checkout(['-b', local, remote])
1595 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1597 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1596 # When updating to a tracked remote branch,
1598 # When updating to a tracked remote branch,
1597 # if the local tracking branch is downstream of it,
1599 # if the local tracking branch is downstream of it,
1598 # a normal `git pull` would have performed a "fast-forward merge"
1600 # a normal `git pull` would have performed a "fast-forward merge"
1599 # which is equivalent to updating the local branch to the remote.
1601 # which is equivalent to updating the local branch to the remote.
1600 # Since we are only looking at branching at update, we need to
1602 # Since we are only looking at branching at update, we need to
1601 # detect this situation and perform this action lazily.
1603 # detect this situation and perform this action lazily.
1602 if tracking[remote] != self._gitcurrentbranch():
1604 if tracking[remote] != self._gitcurrentbranch():
1603 checkout([tracking[remote]])
1605 checkout([tracking[remote]])
1604 self._gitcommand(['merge', '--ff', remote])
1606 self._gitcommand(['merge', '--ff', remote])
1605 _sanitize(self.ui, self.wvfs, '.git')
1607 _sanitize(self.ui, self.wvfs, '.git')
1606 else:
1608 else:
1607 # a real merge would be required, just checkout the revision
1609 # a real merge would be required, just checkout the revision
1608 rawcheckout()
1610 rawcheckout()
1609
1611
1610 @annotatesubrepoerror
1612 @annotatesubrepoerror
1611 def commit(self, text, user, date):
1613 def commit(self, text, user, date):
1612 if self._gitmissing():
1614 if self._gitmissing():
1613 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1615 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1614 cmd = ['commit', '-a', '-m', text]
1616 cmd = ['commit', '-a', '-m', text]
1615 env = os.environ.copy()
1617 env = os.environ.copy()
1616 if user:
1618 if user:
1617 cmd += ['--author', user]
1619 cmd += ['--author', user]
1618 if date:
1620 if date:
1619 # git's date parser silently ignores when seconds < 1e9
1621 # git's date parser silently ignores when seconds < 1e9
1620 # convert to ISO8601
1622 # convert to ISO8601
1621 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1623 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1622 '%Y-%m-%dT%H:%M:%S %1%2')
1624 '%Y-%m-%dT%H:%M:%S %1%2')
1623 self._gitcommand(cmd, env=env)
1625 self._gitcommand(cmd, env=env)
1624 # make sure commit works otherwise HEAD might not exist under certain
1626 # make sure commit works otherwise HEAD might not exist under certain
1625 # circumstances
1627 # circumstances
1626 return self._gitstate()
1628 return self._gitstate()
1627
1629
1628 @annotatesubrepoerror
1630 @annotatesubrepoerror
1629 def merge(self, state):
1631 def merge(self, state):
1630 source, revision, kind = state
1632 source, revision, kind = state
1631 self._fetch(source, revision)
1633 self._fetch(source, revision)
1632 base = self._gitcommand(['merge-base', revision, self._state[1]])
1634 base = self._gitcommand(['merge-base', revision, self._state[1]])
1633 self._gitupdatestat()
1635 self._gitupdatestat()
1634 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1636 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1635
1637
1636 def mergefunc():
1638 def mergefunc():
1637 if base == revision:
1639 if base == revision:
1638 self.get(state) # fast forward merge
1640 self.get(state) # fast forward merge
1639 elif base != self._state[1]:
1641 elif base != self._state[1]:
1640 self._gitcommand(['merge', '--no-commit', revision])
1642 self._gitcommand(['merge', '--no-commit', revision])
1641 _sanitize(self.ui, self.wvfs, '.git')
1643 _sanitize(self.ui, self.wvfs, '.git')
1642
1644
1643 if self.dirty():
1645 if self.dirty():
1644 if self._gitstate() != revision:
1646 if self._gitstate() != revision:
1645 dirty = self._gitstate() == self._state[1] or code != 0
1647 dirty = self._gitstate() == self._state[1] or code != 0
1646 if _updateprompt(self.ui, self, dirty,
1648 if _updateprompt(self.ui, self, dirty,
1647 self._state[1][:7], revision[:7]):
1649 self._state[1][:7], revision[:7]):
1648 mergefunc()
1650 mergefunc()
1649 else:
1651 else:
1650 mergefunc()
1652 mergefunc()
1651
1653
1652 @annotatesubrepoerror
1654 @annotatesubrepoerror
1653 def push(self, opts):
1655 def push(self, opts):
1654 force = opts.get('force')
1656 force = opts.get('force')
1655
1657
1656 if not self._state[1]:
1658 if not self._state[1]:
1657 return True
1659 return True
1658 if self._gitmissing():
1660 if self._gitmissing():
1659 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1661 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1660 # if a branch in origin contains the revision, nothing to do
1662 # if a branch in origin contains the revision, nothing to do
1661 branch2rev, rev2branch = self._gitbranchmap()
1663 branch2rev, rev2branch = self._gitbranchmap()
1662 if self._state[1] in rev2branch:
1664 if self._state[1] in rev2branch:
1663 for b in rev2branch[self._state[1]]:
1665 for b in rev2branch[self._state[1]]:
1664 if b.startswith('refs/remotes/origin/'):
1666 if b.startswith('refs/remotes/origin/'):
1665 return True
1667 return True
1666 for b, revision in branch2rev.iteritems():
1668 for b, revision in branch2rev.iteritems():
1667 if b.startswith('refs/remotes/origin/'):
1669 if b.startswith('refs/remotes/origin/'):
1668 if self._gitisancestor(self._state[1], revision):
1670 if self._gitisancestor(self._state[1], revision):
1669 return True
1671 return True
1670 # otherwise, try to push the currently checked out branch
1672 # otherwise, try to push the currently checked out branch
1671 cmd = ['push']
1673 cmd = ['push']
1672 if force:
1674 if force:
1673 cmd.append('--force')
1675 cmd.append('--force')
1674
1676
1675 current = self._gitcurrentbranch()
1677 current = self._gitcurrentbranch()
1676 if current:
1678 if current:
1677 # determine if the current branch is even useful
1679 # determine if the current branch is even useful
1678 if not self._gitisancestor(self._state[1], current):
1680 if not self._gitisancestor(self._state[1], current):
1679 self.ui.warn(_('unrelated git branch checked out '
1681 self.ui.warn(_('unrelated git branch checked out '
1680 'in subrepo %s\n') % self._relpath)
1682 'in subrepo %s\n') % self._relpath)
1681 return False
1683 return False
1682 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1684 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1683 (current.split('/', 2)[2], self._relpath))
1685 (current.split('/', 2)[2], self._relpath))
1684 ret = self._gitdir(cmd + ['origin', current])
1686 ret = self._gitdir(cmd + ['origin', current])
1685 return ret[1] == 0
1687 return ret[1] == 0
1686 else:
1688 else:
1687 self.ui.warn(_('no branch checked out in subrepo %s\n'
1689 self.ui.warn(_('no branch checked out in subrepo %s\n'
1688 'cannot push revision %s\n') %
1690 'cannot push revision %s\n') %
1689 (self._relpath, self._state[1]))
1691 (self._relpath, self._state[1]))
1690 return False
1692 return False
1691
1693
1692 @annotatesubrepoerror
1694 @annotatesubrepoerror
1693 def add(self, ui, match, prefix, explicitonly, **opts):
1695 def add(self, ui, match, prefix, explicitonly, **opts):
1694 if self._gitmissing():
1696 if self._gitmissing():
1695 return []
1697 return []
1696
1698
1697 (modified, added, removed,
1699 (modified, added, removed,
1698 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1700 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1699 clean=True)
1701 clean=True)
1700
1702
1701 tracked = set()
1703 tracked = set()
1702 # dirstates 'amn' warn, 'r' is added again
1704 # dirstates 'amn' warn, 'r' is added again
1703 for l in (modified, added, deleted, clean):
1705 for l in (modified, added, deleted, clean):
1704 tracked.update(l)
1706 tracked.update(l)
1705
1707
1706 # Unknown files not of interest will be rejected by the matcher
1708 # Unknown files not of interest will be rejected by the matcher
1707 files = unknown
1709 files = unknown
1708 files.extend(match.files())
1710 files.extend(match.files())
1709
1711
1710 rejected = []
1712 rejected = []
1711
1713
1712 files = [f for f in sorted(set(files)) if match(f)]
1714 files = [f for f in sorted(set(files)) if match(f)]
1713 for f in files:
1715 for f in files:
1714 exact = match.exact(f)
1716 exact = match.exact(f)
1715 command = ["add"]
1717 command = ["add"]
1716 if exact:
1718 if exact:
1717 command.append("-f") #should be added, even if ignored
1719 command.append("-f") #should be added, even if ignored
1718 if ui.verbose or not exact:
1720 if ui.verbose or not exact:
1719 ui.status(_('adding %s\n') % match.rel(f))
1721 ui.status(_('adding %s\n') % match.rel(f))
1720
1722
1721 if f in tracked: # hg prints 'adding' even if already tracked
1723 if f in tracked: # hg prints 'adding' even if already tracked
1722 if exact:
1724 if exact:
1723 rejected.append(f)
1725 rejected.append(f)
1724 continue
1726 continue
1725 if not opts.get('dry_run'):
1727 if not opts.get('dry_run'):
1726 self._gitcommand(command + [f])
1728 self._gitcommand(command + [f])
1727
1729
1728 for f in rejected:
1730 for f in rejected:
1729 ui.warn(_("%s already tracked!\n") % match.abs(f))
1731 ui.warn(_("%s already tracked!\n") % match.abs(f))
1730
1732
1731 return rejected
1733 return rejected
1732
1734
1733 @annotatesubrepoerror
1735 @annotatesubrepoerror
1734 def remove(self):
1736 def remove(self):
1735 if self._gitmissing():
1737 if self._gitmissing():
1736 return
1738 return
1737 if self.dirty():
1739 if self.dirty():
1738 self.ui.warn(_('not removing repo %s because '
1740 self.ui.warn(_('not removing repo %s because '
1739 'it has changes.\n') % self._relpath)
1741 'it has changes.\n') % self._relpath)
1740 return
1742 return
1741 # we can't fully delete the repository as it may contain
1743 # we can't fully delete the repository as it may contain
1742 # local-only history
1744 # local-only history
1743 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1745 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1744 self._gitcommand(['config', 'core.bare', 'true'])
1746 self._gitcommand(['config', 'core.bare', 'true'])
1745 for f, kind in self.wvfs.readdir():
1747 for f, kind in self.wvfs.readdir():
1746 if f == '.git':
1748 if f == '.git':
1747 continue
1749 continue
1748 if kind == stat.S_IFDIR:
1750 if kind == stat.S_IFDIR:
1749 self.wvfs.rmtree(f)
1751 self.wvfs.rmtree(f)
1750 else:
1752 else:
1751 self.wvfs.unlink(f)
1753 self.wvfs.unlink(f)
1752
1754
1753 def archive(self, archiver, prefix, match=None):
1755 def archive(self, archiver, prefix, match=None):
1754 total = 0
1756 total = 0
1755 source, revision = self._state
1757 source, revision = self._state
1756 if not revision:
1758 if not revision:
1757 return total
1759 return total
1758 self._fetch(source, revision)
1760 self._fetch(source, revision)
1759
1761
1760 # Parse git's native archive command.
1762 # Parse git's native archive command.
1761 # This should be much faster than manually traversing the trees
1763 # This should be much faster than manually traversing the trees
1762 # and objects with many subprocess calls.
1764 # and objects with many subprocess calls.
1763 tarstream = self._gitcommand(['archive', revision], stream=True)
1765 tarstream = self._gitcommand(['archive', revision], stream=True)
1764 tar = tarfile.open(fileobj=tarstream, mode='r|')
1766 tar = tarfile.open(fileobj=tarstream, mode='r|')
1765 relpath = subrelpath(self)
1767 relpath = subrelpath(self)
1766 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1768 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1767 for i, info in enumerate(tar):
1769 for i, info in enumerate(tar):
1768 if info.isdir():
1770 if info.isdir():
1769 continue
1771 continue
1770 if match and not match(info.name):
1772 if match and not match(info.name):
1771 continue
1773 continue
1772 if info.issym():
1774 if info.issym():
1773 data = info.linkname
1775 data = info.linkname
1774 else:
1776 else:
1775 data = tar.extractfile(info).read()
1777 data = tar.extractfile(info).read()
1776 archiver.addfile(prefix + self._path + '/' + info.name,
1778 archiver.addfile(prefix + self._path + '/' + info.name,
1777 info.mode, info.issym(), data)
1779 info.mode, info.issym(), data)
1778 total += 1
1780 total += 1
1779 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1781 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1780 unit=_('files'))
1782 unit=_('files'))
1781 self.ui.progress(_('archiving (%s)') % relpath, None)
1783 self.ui.progress(_('archiving (%s)') % relpath, None)
1782 return total
1784 return total
1783
1785
1784
1786
1785 @annotatesubrepoerror
1787 @annotatesubrepoerror
1786 def cat(self, match, prefix, **opts):
1788 def cat(self, match, prefix, **opts):
1787 rev = self._state[1]
1789 rev = self._state[1]
1788 if match.anypats():
1790 if match.anypats():
1789 return 1 #No support for include/exclude yet
1791 return 1 #No support for include/exclude yet
1790
1792
1791 if not match.files():
1793 if not match.files():
1792 return 1
1794 return 1
1793
1795
1794 for f in match.files():
1796 for f in match.files():
1795 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1797 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1796 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1798 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1797 self._ctx.node(),
1799 self._ctx.node(),
1798 pathname=self.wvfs.reljoin(prefix, f))
1800 pathname=self.wvfs.reljoin(prefix, f))
1799 fp.write(output)
1801 fp.write(output)
1800 fp.close()
1802 fp.close()
1801 return 0
1803 return 0
1802
1804
1803
1805
1804 @annotatesubrepoerror
1806 @annotatesubrepoerror
1805 def status(self, rev2, **opts):
1807 def status(self, rev2, **opts):
1806 rev1 = self._state[1]
1808 rev1 = self._state[1]
1807 if self._gitmissing() or not rev1:
1809 if self._gitmissing() or not rev1:
1808 # if the repo is missing, return no results
1810 # if the repo is missing, return no results
1809 return scmutil.status([], [], [], [], [], [], [])
1811 return scmutil.status([], [], [], [], [], [], [])
1810 modified, added, removed = [], [], []
1812 modified, added, removed = [], [], []
1811 self._gitupdatestat()
1813 self._gitupdatestat()
1812 if rev2:
1814 if rev2:
1813 command = ['diff-tree', '-r', rev1, rev2]
1815 command = ['diff-tree', '-r', rev1, rev2]
1814 else:
1816 else:
1815 command = ['diff-index', rev1]
1817 command = ['diff-index', rev1]
1816 out = self._gitcommand(command)
1818 out = self._gitcommand(command)
1817 for line in out.split('\n'):
1819 for line in out.split('\n'):
1818 tab = line.find('\t')
1820 tab = line.find('\t')
1819 if tab == -1:
1821 if tab == -1:
1820 continue
1822 continue
1821 status, f = line[tab - 1], line[tab + 1:]
1823 status, f = line[tab - 1], line[tab + 1:]
1822 if status == 'M':
1824 if status == 'M':
1823 modified.append(f)
1825 modified.append(f)
1824 elif status == 'A':
1826 elif status == 'A':
1825 added.append(f)
1827 added.append(f)
1826 elif status == 'D':
1828 elif status == 'D':
1827 removed.append(f)
1829 removed.append(f)
1828
1830
1829 deleted, unknown, ignored, clean = [], [], [], []
1831 deleted, unknown, ignored, clean = [], [], [], []
1830
1832
1831 command = ['status', '--porcelain', '-z']
1833 command = ['status', '--porcelain', '-z']
1832 if opts.get('unknown'):
1834 if opts.get('unknown'):
1833 command += ['--untracked-files=all']
1835 command += ['--untracked-files=all']
1834 if opts.get('ignored'):
1836 if opts.get('ignored'):
1835 command += ['--ignored']
1837 command += ['--ignored']
1836 out = self._gitcommand(command)
1838 out = self._gitcommand(command)
1837
1839
1838 changedfiles = set()
1840 changedfiles = set()
1839 changedfiles.update(modified)
1841 changedfiles.update(modified)
1840 changedfiles.update(added)
1842 changedfiles.update(added)
1841 changedfiles.update(removed)
1843 changedfiles.update(removed)
1842 for line in out.split('\0'):
1844 for line in out.split('\0'):
1843 if not line:
1845 if not line:
1844 continue
1846 continue
1845 st = line[0:2]
1847 st = line[0:2]
1846 #moves and copies show 2 files on one line
1848 #moves and copies show 2 files on one line
1847 if line.find('\0') >= 0:
1849 if line.find('\0') >= 0:
1848 filename1, filename2 = line[3:].split('\0')
1850 filename1, filename2 = line[3:].split('\0')
1849 else:
1851 else:
1850 filename1 = line[3:]
1852 filename1 = line[3:]
1851 filename2 = None
1853 filename2 = None
1852
1854
1853 changedfiles.add(filename1)
1855 changedfiles.add(filename1)
1854 if filename2:
1856 if filename2:
1855 changedfiles.add(filename2)
1857 changedfiles.add(filename2)
1856
1858
1857 if st == '??':
1859 if st == '??':
1858 unknown.append(filename1)
1860 unknown.append(filename1)
1859 elif st == '!!':
1861 elif st == '!!':
1860 ignored.append(filename1)
1862 ignored.append(filename1)
1861
1863
1862 if opts.get('clean'):
1864 if opts.get('clean'):
1863 out = self._gitcommand(['ls-files'])
1865 out = self._gitcommand(['ls-files'])
1864 for f in out.split('\n'):
1866 for f in out.split('\n'):
1865 if not f in changedfiles:
1867 if not f in changedfiles:
1866 clean.append(f)
1868 clean.append(f)
1867
1869
1868 return scmutil.status(modified, added, removed, deleted,
1870 return scmutil.status(modified, added, removed, deleted,
1869 unknown, ignored, clean)
1871 unknown, ignored, clean)
1870
1872
1871 @annotatesubrepoerror
1873 @annotatesubrepoerror
1872 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1874 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1873 node1 = self._state[1]
1875 node1 = self._state[1]
1874 cmd = ['diff']
1876 cmd = ['diff']
1875 if opts['stat']:
1877 if opts['stat']:
1876 cmd.append('--stat')
1878 cmd.append('--stat')
1877 else:
1879 else:
1878 # for Git, this also implies '-p'
1880 # for Git, this also implies '-p'
1879 cmd.append('-U%d' % diffopts.context)
1881 cmd.append('-U%d' % diffopts.context)
1880
1882
1881 gitprefix = self.wvfs.reljoin(prefix, self._path)
1883 gitprefix = self.wvfs.reljoin(prefix, self._path)
1882
1884
1883 if diffopts.noprefix:
1885 if diffopts.noprefix:
1884 cmd.extend(['--src-prefix=%s/' % gitprefix,
1886 cmd.extend(['--src-prefix=%s/' % gitprefix,
1885 '--dst-prefix=%s/' % gitprefix])
1887 '--dst-prefix=%s/' % gitprefix])
1886 else:
1888 else:
1887 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1889 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1888 '--dst-prefix=b/%s/' % gitprefix])
1890 '--dst-prefix=b/%s/' % gitprefix])
1889
1891
1890 if diffopts.ignorews:
1892 if diffopts.ignorews:
1891 cmd.append('--ignore-all-space')
1893 cmd.append('--ignore-all-space')
1892 if diffopts.ignorewsamount:
1894 if diffopts.ignorewsamount:
1893 cmd.append('--ignore-space-change')
1895 cmd.append('--ignore-space-change')
1894 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1896 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1895 and diffopts.ignoreblanklines:
1897 and diffopts.ignoreblanklines:
1896 cmd.append('--ignore-blank-lines')
1898 cmd.append('--ignore-blank-lines')
1897
1899
1898 cmd.append(node1)
1900 cmd.append(node1)
1899 if node2:
1901 if node2:
1900 cmd.append(node2)
1902 cmd.append(node2)
1901
1903
1902 output = ""
1904 output = ""
1903 if match.always():
1905 if match.always():
1904 output += self._gitcommand(cmd) + '\n'
1906 output += self._gitcommand(cmd) + '\n'
1905 else:
1907 else:
1906 st = self.status(node2)[:3]
1908 st = self.status(node2)[:3]
1907 files = [f for sublist in st for f in sublist]
1909 files = [f for sublist in st for f in sublist]
1908 for f in files:
1910 for f in files:
1909 if match(f):
1911 if match(f):
1910 output += self._gitcommand(cmd + ['--', f]) + '\n'
1912 output += self._gitcommand(cmd + ['--', f]) + '\n'
1911
1913
1912 if output.strip():
1914 if output.strip():
1913 ui.write(output)
1915 ui.write(output)
1914
1916
1915 @annotatesubrepoerror
1917 @annotatesubrepoerror
1916 def revert(self, substate, *pats, **opts):
1918 def revert(self, substate, *pats, **opts):
1917 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1919 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1918 if not opts.get('no_backup'):
1920 if not opts.get('no_backup'):
1919 status = self.status(None)
1921 status = self.status(None)
1920 names = status.modified
1922 names = status.modified
1921 for name in names:
1923 for name in names:
1922 bakname = scmutil.origpath(self.ui, self._subparent, name)
1924 bakname = scmutil.origpath(self.ui, self._subparent, name)
1923 self.ui.note(_('saving current version of %s as %s\n') %
1925 self.ui.note(_('saving current version of %s as %s\n') %
1924 (name, bakname))
1926 (name, bakname))
1925 self.wvfs.rename(name, bakname)
1927 self.wvfs.rename(name, bakname)
1926
1928
1927 if not opts.get('dry_run'):
1929 if not opts.get('dry_run'):
1928 self.get(substate, overwrite=True)
1930 self.get(substate, overwrite=True)
1929 return []
1931 return []
1930
1932
1931 def shortid(self, revid):
1933 def shortid(self, revid):
1932 return revid[:7]
1934 return revid[:7]
1933
1935
1934 types = {
1936 types = {
1935 'hg': hgsubrepo,
1937 'hg': hgsubrepo,
1936 'svn': svnsubrepo,
1938 'svn': svnsubrepo,
1937 'git': gitsubrepo,
1939 'git': gitsubrepo,
1938 }
1940 }
@@ -1,350 +1,350 b''
1 $ remove() {
1 $ remove() {
2 > hg rm $@
2 > hg rm $@
3 > echo "exit code: $?"
3 > echo "exit code: $?"
4 > hg st
4 > hg st
5 > # do not use ls -R, which recurses in .hg subdirs on Mac OS X 10.5
5 > # do not use ls -R, which recurses in .hg subdirs on Mac OS X 10.5
6 > find . -name .hg -prune -o -type f -print | sort
6 > find . -name .hg -prune -o -type f -print | sort
7 > hg up -C
7 > hg up -C
8 > }
8 > }
9
9
10 $ cat >> $HGRCPATH <<EOF
10 $ cat >> $HGRCPATH <<EOF
11 > [progress]
11 > [progress]
12 > disable=False
12 > disable=False
13 > assume-tty = 1
13 > assume-tty = 1
14 > delay = 0
14 > delay = 0
15 > # set changedelay really large so we don't see nested topics
15 > # set changedelay really large so we don't see nested topics
16 > changedelay = 30000
16 > changedelay = 30000
17 > format = topic bar number
17 > format = topic bar number
18 > refresh = 0
18 > refresh = 0
19 > width = 60
19 > width = 60
20 > EOF
20 > EOF
21
21
22 $ hg init a
22 $ hg init a
23 $ cd a
23 $ cd a
24 $ echo a > foo
24 $ echo a > foo
25
25
26 file not managed
26 file not managed
27
27
28 $ remove foo
28 $ remove foo
29 not removing foo: file is untracked
29 not removing foo: file is untracked
30 exit code: 1
30 exit code: 1
31 ? foo
31 ? foo
32 ./foo
32 ./foo
33 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
34
34
35 $ hg add foo
35 $ hg add foo
36 $ hg commit -m1
36 $ hg commit -m1
37
37
38 the table cases
38 the table cases
39 00 state added, options none
39 00 state added, options none
40
40
41 $ echo b > bar
41 $ echo b > bar
42 $ hg add bar
42 $ hg add bar
43 $ remove bar
43 $ remove bar
44 not removing bar: file has been marked for add (use forget to undo)
44 not removing bar: file has been marked for add (use forget to undo)
45 exit code: 1
45 exit code: 1
46 A bar
46 A bar
47 ./bar
47 ./bar
48 ./foo
48 ./foo
49 \r (no-eol) (esc)
49 \r (no-eol) (esc)
50 updating [===========================================>] 1/1\r (no-eol) (esc)
50 updating [===========================================>] 1/1\r (no-eol) (esc)
51 \r (no-eol) (esc)
51 \r (no-eol) (esc)
52 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
53
53
54 01 state clean, options none
54 01 state clean, options none
55
55
56 $ remove foo
56 $ remove foo
57 exit code: 0
57 exit code: 0
58 R foo
58 R foo
59 ? bar
59 ? bar
60 ./bar
60 ./bar
61 \r (no-eol) (esc)
61 \r (no-eol) (esc)
62 updating [===========================================>] 1/1\r (no-eol) (esc)
62 updating [===========================================>] 1/1\r (no-eol) (esc)
63 \r (no-eol) (esc)
63 \r (no-eol) (esc)
64 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
65
65
66 02 state modified, options none
66 02 state modified, options none
67
67
68 $ echo b >> foo
68 $ echo b >> foo
69 $ remove foo
69 $ remove foo
70 not removing foo: file is modified (use -f to force removal)
70 not removing foo: file is modified (use -f to force removal)
71 exit code: 1
71 exit code: 1
72 M foo
72 M foo
73 ? bar
73 ? bar
74 ./bar
74 ./bar
75 ./foo
75 ./foo
76 \r (no-eol) (esc)
76 \r (no-eol) (esc)
77 updating [===========================================>] 1/1\r (no-eol) (esc)
77 updating [===========================================>] 1/1\r (no-eol) (esc)
78 \r (no-eol) (esc)
78 \r (no-eol) (esc)
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80
80
81 03 state missing, options none
81 03 state missing, options none
82
82
83 $ rm foo
83 $ rm foo
84 $ remove foo
84 $ remove foo
85 exit code: 0
85 exit code: 0
86 R foo
86 R foo
87 ? bar
87 ? bar
88 ./bar
88 ./bar
89 \r (no-eol) (esc)
89 \r (no-eol) (esc)
90 updating [===========================================>] 1/1\r (no-eol) (esc)
90 updating [===========================================>] 1/1\r (no-eol) (esc)
91 \r (no-eol) (esc)
91 \r (no-eol) (esc)
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
93
93
94 10 state added, options -f
94 10 state added, options -f
95
95
96 $ echo b > bar
96 $ echo b > bar
97 $ hg add bar
97 $ hg add bar
98 $ remove -f bar
98 $ remove -f bar
99 exit code: 0
99 exit code: 0
100 ? bar
100 ? bar
101 ./bar
101 ./bar
102 ./foo
102 ./foo
103 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
104 $ rm bar
104 $ rm bar
105
105
106 11 state clean, options -f
106 11 state clean, options -f
107
107
108 $ remove -f foo
108 $ remove -f foo
109 exit code: 0
109 exit code: 0
110 R foo
110 R foo
111 \r (no-eol) (esc)
111 \r (no-eol) (esc)
112 updating [===========================================>] 1/1\r (no-eol) (esc)
112 updating [===========================================>] 1/1\r (no-eol) (esc)
113 \r (no-eol) (esc)
113 \r (no-eol) (esc)
114 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
115
115
116 12 state modified, options -f
116 12 state modified, options -f
117
117
118 $ echo b >> foo
118 $ echo b >> foo
119 $ remove -f foo
119 $ remove -f foo
120 exit code: 0
120 exit code: 0
121 R foo
121 R foo
122 \r (no-eol) (esc)
122 \r (no-eol) (esc)
123 updating [===========================================>] 1/1\r (no-eol) (esc)
123 updating [===========================================>] 1/1\r (no-eol) (esc)
124 \r (no-eol) (esc)
124 \r (no-eol) (esc)
125 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
125 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
126
126
127 13 state missing, options -f
127 13 state missing, options -f
128
128
129 $ rm foo
129 $ rm foo
130 $ remove -f foo
130 $ remove -f foo
131 exit code: 0
131 exit code: 0
132 R foo
132 R foo
133 \r (no-eol) (esc)
133 \r (no-eol) (esc)
134 updating [===========================================>] 1/1\r (no-eol) (esc)
134 updating [===========================================>] 1/1\r (no-eol) (esc)
135 \r (no-eol) (esc)
135 \r (no-eol) (esc)
136 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
136 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
137
137
138 20 state added, options -A
138 20 state added, options -A
139
139
140 $ echo b > bar
140 $ echo b > bar
141 $ hg add bar
141 $ hg add bar
142 $ remove -A bar
142 $ remove -A bar
143 not removing bar: file still exists
143 not removing bar: file still exists
144 exit code: 1
144 exit code: 1
145 A bar
145 A bar
146 ./bar
146 ./bar
147 ./foo
147 ./foo
148 \r (no-eol) (esc)
148 \r (no-eol) (esc)
149 updating [===========================================>] 1/1\r (no-eol) (esc)
149 updating [===========================================>] 1/1\r (no-eol) (esc)
150 \r (no-eol) (esc)
150 \r (no-eol) (esc)
151 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
152
152
153 21 state clean, options -A
153 21 state clean, options -A
154
154
155 $ remove -A foo
155 $ remove -A foo
156 not removing foo: file still exists
156 not removing foo: file still exists
157 exit code: 1
157 exit code: 1
158 ? bar
158 ? bar
159 ./bar
159 ./bar
160 ./foo
160 ./foo
161 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
161 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
162
162
163 22 state modified, options -A
163 22 state modified, options -A
164
164
165 $ echo b >> foo
165 $ echo b >> foo
166 $ remove -A foo
166 $ remove -A foo
167 not removing foo: file still exists
167 not removing foo: file still exists
168 exit code: 1
168 exit code: 1
169 M foo
169 M foo
170 ? bar
170 ? bar
171 ./bar
171 ./bar
172 ./foo
172 ./foo
173 \r (no-eol) (esc)
173 \r (no-eol) (esc)
174 updating [===========================================>] 1/1\r (no-eol) (esc)
174 updating [===========================================>] 1/1\r (no-eol) (esc)
175 \r (no-eol) (esc)
175 \r (no-eol) (esc)
176 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
177
177
178 23 state missing, options -A
178 23 state missing, options -A
179
179
180 $ rm foo
180 $ rm foo
181 $ remove -A foo
181 $ remove -A foo
182 exit code: 0
182 exit code: 0
183 R foo
183 R foo
184 ? bar
184 ? bar
185 ./bar
185 ./bar
186 \r (no-eol) (esc)
186 \r (no-eol) (esc)
187 updating [===========================================>] 1/1\r (no-eol) (esc)
187 updating [===========================================>] 1/1\r (no-eol) (esc)
188 \r (no-eol) (esc)
188 \r (no-eol) (esc)
189 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
190
190
191 30 state added, options -Af
191 30 state added, options -Af
192
192
193 $ echo b > bar
193 $ echo b > bar
194 $ hg add bar
194 $ hg add bar
195 $ remove -Af bar
195 $ remove -Af bar
196 exit code: 0
196 exit code: 0
197 ? bar
197 ? bar
198 ./bar
198 ./bar
199 ./foo
199 ./foo
200 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
200 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
201 $ rm bar
201 $ rm bar
202
202
203 31 state clean, options -Af
203 31 state clean, options -Af
204
204
205 $ remove -Af foo
205 $ remove -Af foo
206 exit code: 0
206 exit code: 0
207 R foo
207 R foo
208 ./foo
208 ./foo
209 \r (no-eol) (esc)
209 \r (no-eol) (esc)
210 updating [===========================================>] 1/1\r (no-eol) (esc)
210 updating [===========================================>] 1/1\r (no-eol) (esc)
211 \r (no-eol) (esc)
211 \r (no-eol) (esc)
212 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
212 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
213
213
214 32 state modified, options -Af
214 32 state modified, options -Af
215
215
216 $ echo b >> foo
216 $ echo b >> foo
217 $ remove -Af foo
217 $ remove -Af foo
218 exit code: 0
218 exit code: 0
219 R foo
219 R foo
220 ./foo
220 ./foo
221 \r (no-eol) (esc)
221 \r (no-eol) (esc)
222 updating [===========================================>] 1/1\r (no-eol) (esc)
222 updating [===========================================>] 1/1\r (no-eol) (esc)
223 \r (no-eol) (esc)
223 \r (no-eol) (esc)
224 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
225
225
226 33 state missing, options -Af
226 33 state missing, options -Af
227
227
228 $ rm foo
228 $ rm foo
229 $ remove -Af foo
229 $ remove -Af foo
230 exit code: 0
230 exit code: 0
231 R foo
231 R foo
232 \r (no-eol) (esc)
232 \r (no-eol) (esc)
233 updating [===========================================>] 1/1\r (no-eol) (esc)
233 updating [===========================================>] 1/1\r (no-eol) (esc)
234 \r (no-eol) (esc)
234 \r (no-eol) (esc)
235 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
235 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
236
236
237 test some directory stuff
237 test some directory stuff
238
238
239 $ mkdir test
239 $ mkdir test
240 $ echo a > test/foo
240 $ echo a > test/foo
241 $ echo b > test/bar
241 $ echo b > test/bar
242 $ hg ci -Am2
242 $ hg ci -Am2
243 adding test/bar
243 adding test/bar
244 adding test/foo
244 adding test/foo
245
245
246 dir, options none
246 dir, options none
247
247
248 $ rm test/bar
248 $ rm test/bar
249 $ remove test
249 $ remove test
250 removing test/bar (glob)
250 removing test/bar (glob)
251 removing test/foo (glob)
251 removing test/foo (glob)
252 exit code: 0
252 exit code: 0
253 R test/bar
253 R test/bar
254 R test/foo
254 R test/foo
255 ./foo
255 ./foo
256 \r (no-eol) (esc)
256 \r (no-eol) (esc)
257 updating [===========================================>] 2/2\r (no-eol) (esc)
257 updating [===========================================>] 2/2\r (no-eol) (esc)
258 \r (no-eol) (esc)
258 \r (no-eol) (esc)
259 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
260
260
261 dir, options -f
261 dir, options -f
262
262
263 $ rm test/bar
263 $ rm test/bar
264 $ remove -f test
264 $ remove -f test
265 removing test/bar (glob)
265 removing test/bar (glob)
266 removing test/foo (glob)
266 removing test/foo (glob)
267 exit code: 0
267 exit code: 0
268 R test/bar
268 R test/bar
269 R test/foo
269 R test/foo
270 ./foo
270 ./foo
271 \r (no-eol) (esc)
271 \r (no-eol) (esc)
272 updating [===========================================>] 2/2\r (no-eol) (esc)
272 updating [===========================================>] 2/2\r (no-eol) (esc)
273 \r (no-eol) (esc)
273 \r (no-eol) (esc)
274 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
274 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
275
275
276 dir, options -A
276 dir, options -A
277
277
278 $ rm test/bar
278 $ rm test/bar
279 $ remove -A test
279 $ remove -A test
280 removing test/bar (glob)
280 not removing test/foo: file still exists (glob)
281 not removing test/foo: file still exists (glob)
281 removing test/bar (glob)
282 exit code: 1
282 exit code: 1
283 R test/bar
283 R test/bar
284 ./foo
284 ./foo
285 ./test/foo
285 ./test/foo
286 \r (no-eol) (esc)
286 \r (no-eol) (esc)
287 updating [===========================================>] 1/1\r (no-eol) (esc)
287 updating [===========================================>] 1/1\r (no-eol) (esc)
288 \r (no-eol) (esc)
288 \r (no-eol) (esc)
289 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
289 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
290
290
291 dir, options -Af
291 dir, options -Af
292
292
293 $ rm test/bar
293 $ rm test/bar
294 $ remove -Af test
294 $ remove -Af test
295 removing test/bar (glob)
295 removing test/bar (glob)
296 removing test/foo (glob)
296 removing test/foo (glob)
297 exit code: 0
297 exit code: 0
298 R test/bar
298 R test/bar
299 R test/foo
299 R test/foo
300 ./foo
300 ./foo
301 ./test/foo
301 ./test/foo
302 \r (no-eol) (esc)
302 \r (no-eol) (esc)
303 updating [===========================================>] 2/2\r (no-eol) (esc)
303 updating [===========================================>] 2/2\r (no-eol) (esc)
304 \r (no-eol) (esc)
304 \r (no-eol) (esc)
305 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
305 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
306
306
307 test remove dropping empty trees (issue1861)
307 test remove dropping empty trees (issue1861)
308
308
309 $ mkdir -p issue1861/b/c
309 $ mkdir -p issue1861/b/c
310 $ echo x > issue1861/x
310 $ echo x > issue1861/x
311 $ echo y > issue1861/b/c/y
311 $ echo y > issue1861/b/c/y
312 $ hg ci -Am add
312 $ hg ci -Am add
313 adding issue1861/b/c/y
313 adding issue1861/b/c/y
314 adding issue1861/x
314 adding issue1861/x
315 $ hg rm issue1861/b
315 $ hg rm issue1861/b
316 removing issue1861/b/c/y (glob)
316 removing issue1861/b/c/y (glob)
317 $ hg ci -m remove
317 $ hg ci -m remove
318 $ ls issue1861
318 $ ls issue1861
319 x
319 x
320
320
321 test that commit does not crash if the user removes a newly added file
321 test that commit does not crash if the user removes a newly added file
322
322
323 $ touch f1
323 $ touch f1
324 $ hg add f1
324 $ hg add f1
325 $ rm f1
325 $ rm f1
326 $ hg ci -A -mx
326 $ hg ci -A -mx
327 removing f1
327 removing f1
328 nothing changed
328 nothing changed
329 [1]
329 [1]
330
330
331 handling of untracked directories and missing files
331 handling of untracked directories and missing files
332
332
333 $ mkdir d1
333 $ mkdir d1
334 $ echo a > d1/a
334 $ echo a > d1/a
335 $ hg rm --after d1
335 $ hg rm --after d1
336 not removing d1: no tracked files
336 not removing d1: no tracked files
337 [1]
337 [1]
338 $ hg add d1/a
338 $ hg add d1/a
339 $ rm d1/a
339 $ rm d1/a
340 $ hg rm --after d1
340 $ hg rm --after d1
341 removing d1/a (glob)
341 removing d1/a (glob)
342 #if windows
342 #if windows
343 $ hg rm --after nosuch
343 $ hg rm --after nosuch
344 nosuch: * (glob)
344 nosuch: * (glob)
345 [1]
345 [1]
346 #else
346 #else
347 $ hg rm --after nosuch
347 $ hg rm --after nosuch
348 nosuch: No such file or directory
348 nosuch: No such file or directory
349 [1]
349 [1]
350 #endif
350 #endif
General Comments 0
You need to be logged in to leave comments. Login now